Skapa ett WinML-tillägg

Den här guiden visar hur du skapar ett internt C#-tillägg som använder Windows strojové učenie (WinML) i din Electron-app. Med WinML kan du köra machine learning modeller (ONNX-format) lokalt på Windows-enheter för uppgifter som bildklassificering, objektidentifiering med mera.

Förutsättningar

Kontrollera att du har gjort så här innan du startar den här guiden:

  • Installationen av utvecklingsmiljön har slutförts
  • Windows 11 eller Windows 10 (version 1809 eller senare)

Anmärkning

WinML körs på valfri Windows 10 (1809+) eller Windows 11 enhet. För bästa prestanda rekommenderas enheter med GPU:er eller NPU:er, men API:et fungerar även på CPU.

Viktigt!

WinML-tillägget kräver experimental Windows App SDK. Om du valde "Stabila SDK:er" under winapp init konfigurationsguiden måste du uppdatera SDK-versionen. Redigera winapp.yaml och ändra versionen Microsoft.WindowsAppSDK till 2.0.0-experimental3 och kör sedan npx winapp restore för att uppdatera.

Steg 1: Skapa ett internt C#-tillägg

Nu ska vi skapa ett internt tillägg som använder WinML-API:er. Vi använder en C#-mall som använder node-api-dotnet för att överbrygga JavaScript och C#.

npx winapp node create-addon --template cs --name winMlAddon

Då skapas en winMlAddon/ mapp med:

  • addon.cs – Din C#-kod som anropar WinML-API:er
  • winMlAddon.csproj – Project fil med referenser till Windows SDK och Windows App SDK
  • README.md – Dokumentation om hur du använder tillägget

Kommandot lägger också till ett build-winMlAddon skript i din package.json för byggande av tillägget och ett clean-winMlAddon skript för rensning av byggartefakter.

{
  "scripts": {
    "build-winMlAddon": "dotnet publish ./winMlAddon/winMlAddon.csproj -c Release",
    "clean-winMlAddon": "dotnet clean ./winMlAddon/winMlAddon.csproj"
  }
}

Mallen innehåller automatiskt referenser till båda SDK:erna, så att du omedelbart kan börja anropa Windows API:er!

Nu ska vi kontrollera att allt har konfigurerats korrekt genom att skapa tillägget:

# Build the C# addon
npm run build-winMlAddon

Anmärkning

Du kan också skapa ett C++-tillägg med ( npx winapp node create-addon utan --template flaggan). C++-tillägg använder node-addon-api och ger direkt åtkomst till Windows API:er med maximal prestanda. Se guiden C++-meddelandetillägg för en genomgång eller den fullständiga kommandodokumentationen för fler alternativ.

Steg 2: Ladda ned SqueezeNet-modellen och Hämta exempelkod

Vi använder exemplet Klassificera bild från AI Dev-galleriet som referens. Det här exemplet använder SqueezeNet 1.1-modellen för bildklassificering.

2.1. Ladda ned modellen

  1. Installera AI Dev-galleriet
  2. Gå till exempel på Klassificera bild
  3. Ladda ned SqueezeNet 1.1-modellen (den stöder CPU, GPU och NPU)
  4. Klicka på Öppna innehållande mapp för att hitta .onnx filen

Ladda ned SqueezeNet från AI Dev Gallery

  1. Kopiera filen squeezenet1.1.onnx till mappen models/ i din project-rot

Anmärkning

Modellen kan också laddas ned direkt från ONNX Model Zoo GitHub lagringsplatsen

Steg 3: Lägg till nödvändiga NuGet-paket

Innan vi lägger till WinML-koden måste vi lägga till ytterligare NuGet-paket som krävs för bildbearbetning, ONNX Runtime och GenAI-stöd.

3.1. Uppdatera Directory.packages.props

Lägg till följande paketversioner i Directory.packages.props-filen som finns i roten av ditt projekt (den borde ha skapats när du skapade tillägget):

<Project>
  <PropertyGroup>
    <!-- Enable central package versioning -->
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Microsoft.JavaScript.NodeApi" Version="0.9.17" />
    <PackageVersion Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.9.17" />
    <!-- Add these packages for WinML -->
+   <PackageVersion Include="Microsoft.ML.OnnxRuntime.Extensions" Version="0.14.0" />
+   <PackageVersion Include="System.Drawing.Common" Version="9.0.9" />
+   <PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
+   <PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI.Managed" Version="0.10.1" />
+   <PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI.WinML" Version="0.10.1" />
    
    <!-- These versions may be updated automatically during restore to match yaml -->
    <PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.0-experimental3" />
    <PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
  </ItemGroup>
</Project>

3.2. Uppdatera winMlAddon.csproj

Öppna winMlAddon/winMlAddon.csproj och lägg till paketreferenserna i <ItemGroup>:

<ItemGroup>
  <PackageReference Include="Microsoft.JavaScript.NodeApi" />
  <PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" />
  <!-- Add these packages for WinML -->
+ <PackageReference Include="Microsoft.ML.OnnxRuntime.Extensions" />
+ <PackageReference Include="System.Drawing.Common" />
+ <PackageReference Include="Microsoft.Extensions.AI" />
+ <PackageReference Include="Microsoft.ML.OnnxRuntimeGenAI.Managed" />
+ <PackageReference Include="Microsoft.ML.OnnxRuntimeGenAI.WinML" />
  
  <PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
  <PackageReference Include="Microsoft.WindowsAppSDK" />
</ItemGroup>

Vad dessa paket gör:

  • Microsoft.ML.OnnxRuntime.Extensions – Tillhandahåller ytterligare operatorer och verktyg för ONNX Runtime
  • System.Drawing.Common – Aktiverar inläsning och manipulering av avbildningar för förbearbetning
  • Microsoft. Extensions.AI – AI-abstraktioner för .NET
  • Microsoft.ML.OnnxRuntimeGenAI.Managed – Hanterade bindningar för ONNX Runtime GenAI
  • Microsoft.ML.OnnxRuntimeGenAI.WinML – WinML-integrering för ONNX Runtime GenAI

Steg 4: Lägg till exempelkoden

AI Dev Gallery visar den fullständiga implementeringen för bildklassificering med SqueezeNet:

SqueezeNet-exempelkod

Vi har anpassat den här koden för Electron och du hittar den fullständiga implementeringen i elektron-winml-exemplet. Mappen winMlAddon/ innehåller den ändrade koden från AI Dev-galleriet.

Kopiera hela winMlAddon/ mappen från samples/electron-winml/winMlAddon/ till projektroten och ersätt den som skapades i steg 1. Exemplet innehåller flera filer utöver addon.cs (hjälpklasser i Utils/, en chattklient osv.) som krävs för att tillägget ska kunna byggas och köras.

Viktigt!

Du måste kopiera hela mappen, inte bara addon.cs. Tillägget är beroende av hjälpfiler i undermappen Utils/ (Prediction.cs, ImageNet.cs, BitmapFunctions.csosv.).

Viktig implementeringsinformation

Nu ska vi lyfta fram viktiga delar av implementeringen och viktiga skillnader från AI Dev Gallery-koden:

1. Krav på projektrotväg

Till skillnad från AI Dev Gallery-koden kräver vårt Electron-tillägg att JavaScript-koden skickar projektets rotsökväg. Detta är nödvändigt eftersom:

  • Tillägget måste hitta ONNX-modellfilen i models/ mappen
  • Inbyggda beroenden (DLL: er) måste läsas in från specifika kataloger
[JSExport]
public static async Task<Addon> CreateAsync(string projectRoot)
{
    if (!Path.Exists(projectRoot))
    {
        throw new Exception("Project root is invalid.");
    }

    var addon = new Addon(projectRoot);
    addon.PreloadNativeDependencies();

    string modelPath = Path.Join(projectRoot, "models", @"squeezenet1.1-7.onnx");
    await addon.InitModel(modelPath, ExecutionProviderDevicePolicy.DEFAULT, null, false, null);

    return addon;
}

Detta väljer automatiskt den bästa körningsprovidern (CPU, GPU eller NPU) baserat på enhetsfunktioner.

2. Förladda inbyggda beroenden

Tillägget innehåller en PreloadNativeDependencies() metod för att läsa in nödvändiga DLL:er. Den här metoden fungerar för både utvecklings- och produktionsscenarier utan att behöva kopiera DLL:er till projektroten:

private void PreloadNativeDependencies()
{
    // Loads required DLLs from the winMlAddon build output
    // This ensures dependencies are available regardless of the execution context
}

Detta anropas under initieringen innan modellen läses in, vilket säkerställer att alla interna bibliotek är tillgängliga.

3. Konfigurera Electron Forge för paketering

För att säkerställa att tillägget fungerar korrekt i produktionsversioner måste du konfigurera paketeraren för att:

  1. Packa upp inbyggda filer – DLL:er, ONNX-modeller och .node-filer måste vara tillgängliga utanför ASAR-arkivet
  2. Exkludera onödiga filer – Håll paketstorleken liten genom att exkludera byggartefakter och temporära filer

För Electron Forge: uppdatera din forge.config.js.

// From samples/electron-winml/forge.config.js
module.exports = {
  packagerConfig: {
    asar: {
      // Unpack native files so they can be accessed by the addon
      unpack: "**/*.{dll,exe,node,onnx}"
    },
    ignore: [
      // Exclude .winapp folder (SDK packages and headers)
      /^\/.winapp\//,
      // Exclude MSIX packages
      "\\.msix$",
      // Exclude winMlAddon source files, but keep the dist folder
      /^\/winMlAddon\/(?!dist).+/
    ]
  },
  // ... rest of your config
};

Vad detta gör:

  1. asar.unpack – Extraherar DLL:er, körbara filer, .node-binärfiler och ONNX-modeller till app.asar.unpacked/

    • Detta gör dem tillgängliga under körning via filsystemsökvägar.
    • JavaScript-koden justerar sökvägar automatiskt (se app.asarapp.asar.unpacked ersättning ovan)
  2. ignore – Exkluderar från det slutliga paketet:

    • .winapp/ – SDK-paketen och huvudfilerna (behövs inte vid körning)
    • .msix files – Paketerade utdata
    • winMlAddon/ källfiler – Behåller endast dist/ mappen med kompilerade binärfiler

Anmärkning

Om du använder ett annat paketeringsverktyg (elektronbyggare osv.) måste du konfigurera liknande inställningar för att packa upp inbyggda beroenden och exkludera utvecklingsfiler. Kontrollera paketerarens dokumentation för asar-uppackningsalternativ.

4. Bildklassificering

Metoden ClassifyImage bearbetar en bild och returnerar förutsägelser:

[JSExport]
public async Task<Prediction[]> ClassifyImage(string imagePath)
{
    // Loads the image, preprocesses it, and runs inference
    // Returns top predictions with labels and confidence scores
}

Den fullständiga implementeringen hanterar:

  • Bildinläsning och förbearbetning (storleksändring, normalisering)
  • Utföra modellinferens
  • Efterbearbetningsresultat för att få de bästa förutsägelserna med etiketter och konfidenspoäng

Anmärkning

Den fullständiga källkoden innehåller förbearbetning av avbildningar, tensor-skapande och resultatparsing. Kontrollera exempelimplementeringen för all information.

Förstå koden

Tillägget innehåller följande huvudfunktioner:

  1. CreateAsync – Initierar tillägget och läser in SqueezeNet-modellen
  2. ClassifyImage – Tar en bildsökväg och returnerar klassificeringsförutsägelser

WinML väljer automatiskt den bästa körningsenheten (CPU, GPU eller NPU) baserat på tillgänglighet.

Steg 5: Skapa C#-tillägget

Skapa nu tillägget:

npm run build-winMlAddon

Detta kompilerar din C#-kod med Native AOT (förkompilering), som:

  • Skapar en .node binär fil (internt tilläggsformat)
  • Minskar oanvänd kod för mindre paket.
  • Kräver no .NET runtime på måldatorer
  • Ger inbyggda prestanda

Det kompilerade tillägget finns i winMlAddon/dist/winMlAddon.node.

Steg 6: Testa tillägget

Nu ska vi testa att tillägget fungerar genom att anropa det från huvudprocessen. Öppna src/main.js och följ dessa steg:

6.1. Läs in tillägget

Lägg till require-satserna överst:

const winMlAddon = require('../winMlAddon/dist/winMlAddon.node');

6.2. Skapa en testfunktion

Lägg till den här funktionen för att testa bildklassificering:

const testWinML = async () => {
  console.log('Testing WinML addon...');
  
  try {
    let projectRoot = path.join(__dirname, '..');
    // Adjust path for packaged apps
    if (projectRoot.includes('app.asar')) {
      projectRoot = projectRoot.replace('app.asar', 'app.asar.unpacked');
    }
    
    const addon = await winMlAddon.Addon.createAsync(projectRoot);
    console.log('Model loaded successfully!');
    
    // Classify a sample image
    const imagePath = path.join(projectRoot, 'test-images', 'sample.jpg');
    const predictions = await addon.classifyImage(imagePath);
    
    console.log('Top predictions:');
    predictions.slice(0, 5).forEach((pred, i) => {
      console.log(`${i + 1}. ${pred.label}: ${(pred.confidence * 100).toFixed(2)}%`);
    });
  } catch (error) {
    console.error('Error testing WinML:', error.message);
  }
};

Viktiga punkter:

  • Sökvägsjusteringen (app.asarapp.asar.unpacked) säkerställer att koden fungerar i både utvecklingsappar och paketerade appar
  • Detta kommer åt de uppackade inhemska filerna som konfigurerats i forge.config.js

6.3. Anropa testfunktionen

Lägg till den här raden i slutet av createWindow() funktionen:

testWinML();

6.4. Förbereda testbilder

Så här testar du bildklassificering:

  1. Skapa en test-images/ mapp i projektroten
  2. Lägg till en testbild med namnet sample.jpg (koden förväntar sig exakt det här filnamnet)
  3. SqueezeNet-modellen identifierar 1 000 olika ImageNet-klasser (djur, objekt, scener osv.)

När du kör appen visas klassificeringsresultaten i konsolen!

Tips/Råd

En fullständig implementering med IPC-hanterare, dialogrutor för filval och ett användargränssnitt finns i exemplet electron-winml.

Steg 7: Uppdatera felsökningsidentitet

För att säkerställa att Windows App SDK läses in och är tillgänglig för användning måste vi se till att vi konfigurerar felsökningsidentitet som säkerställer att ramverket läses in när vår app körs. På samma sätt måste du uppdatera appens felsökningsidentitet när du ändrar Package.appxmanifest eller ändrar tillgångar som refereras i manifestet (till exempel appikoner). Run:

npx winapp node add-electron-debug-identity

Det här kommandot:

  1. Läser din Package.appxmanifest för att hämta appinformation och funktioner
  2. electron.exe Registrerar sig i din node_modules med en tillfällig identitet
  3. Gör att du kan testa api:er som krävs för identitet utan fullständig MSIX-paketering

Anmärkning

Det här kommandot är redan en del av skriptet postinstall som vi lade till i installationsguiden, så det körs automatiskt efter npm install. Du måste dock köra den manuellt när du:

  • Ändra Package.appxmanifest (ändra funktioner, identitet eller egenskaper)
  • Uppdatera apptillgångar (ikoner, logotyper osv.)

Kör nu appen:

npm start

Kontrollera konsolens utdata – du bör se WinML-testresultatet!

⚠️ Känt problem: App kraschar eller tomt fönster (klicka för att expandera)

Det finns en känd Windows bugg med gles paketering av Elektronprogram som gör att appen kraschar vid start eller inte renderar webbinnehåll. Problemet har åtgärdats i Windows men har ännu inte spridits till alla enheter.

Se konfigurationen av utvecklingsmiljön för en lösning.

Nästa steg

Grattis! Du har skapat ett internt tillägg som kan köra maskininlärningsmodeller med WinML! 🎉

Nu är du redo att:

Eller utforska andra guider:

Anpassa för din modell

Om du vill integrera DIN ONNX-modell helt måste du:

  1. Förstå modellens indata – Bilder, tensorer, sekvenser osv.
  2. Skapa rätt indatabindningar – Konvertera dina data till det format som WinML förväntar sig
  3. Bearbeta utdata – Parsa och tolka modellens förutsägelser
  4. Hantera fel på ett korrekt sätt – Modellinläsning och slutsatsdragning kan misslyckas

Ytterligare resurser

Felsökning

Build misslyckas med NU1010: PackageReference-objekt definierar inte en motsvarande PackageVersion

Kontrollera att alla paket som refereras i winMlAddon.csproj har matchande poster i Directory.packages.props. Se Steg 3 för den fullständiga listan över nödvändiga paket.

"inte ett giltigt Win32-program" när tillägget läses in

Det innebär att tillägget har skapats för en annan arkitektur än din Node.js/Electron-körning. Kontrollera din Node.js arkitektur:

node -e "console.log(process.arch)"

Återskapa sedan tillägget med matchande mål:

# For x64 Node.js:
dotnet publish ./winMlAddon/winMlAddon.csproj -c Release -r win-x64

# For ARM64 Node.js:
dotnet publish ./winMlAddon/winMlAddon.csproj -c Release -r win-arm64

Om du nyligen har ändrat installationen av Node.js installerar du även om node_modules för att hämta den matchande elektronbinärfilen:

rm -rf node_modules package-lock.json
npm install

Få hjälp

Glad maskininlärning! 🤖