本指南介绍如何在 Electron 应用中创建使用 Windows 机器学习(WinML)的 C# 本机加载项。 使用 WinML,可以在 Windows 设备上本地运行machine learning模型(ONNX 格式),以便执行图像分类、对象检测等任务。
先决条件
在开始本指南之前,请确保已:
- 已完成 开发环境设置
- Windows 11或Windows 10(版本 1809 或更高版本)
注释
WinML 在任何Windows 10(1809+)或Windows 11设备上运行。 为了获得最佳性能,建议使用 GPU 或 NPU 的设备,但 API 也适用于 CPU。
Important
WinML 加载项需要 experimental Windows 应用 SDK。 如果在安装指南的winapp init期间选择了“稳定 SDKs”,则需要更新 SDK 版本。 编辑 winapp.yaml并将 Microsoft.WindowsAppSDK 版本更改为 2.0.0-experimental3,然后运行 npx winapp restore 进行更新。
步骤 1:创建 C# 原生插件
让我们创建一个将使用 WinML API 的本机加载项。 我们将使用利用 node-api-dotnet 桥接 JavaScript 和 C# 的 C# 模板。
npx winapp node create-addon --template cs --name winMlAddon
这将创建一个 winMlAddon/ 文件夹,其中包含:
-
addon.cs- 将调用 WinML API 的 C# 代码 -
winMlAddon.csproj- 引用 Windows SDK 和 Windows 应用 SDK 的项目文件 -
README.md- 有关如何使用加载项的文档
该命令还会将一个build-winMlAddon脚本添加到您的package.json中,用于构建加载项,同时添加一个clean-winMlAddon脚本用于清理构建产物。
{
"scripts": {
"build-winMlAddon": "dotnet publish ./winMlAddon/winMlAddon.csproj -c Release",
"clean-winMlAddon": "dotnet clean ./winMlAddon/winMlAddon.csproj"
}
}
该模板会自动包含对两个 SDK 的引用,因此你可以立即开始调用Windows API!
让我们通过构建加载项来验证所有设置是否正确。
# Build the C# addon
npm run build-winMlAddon
注释
还可以使用 npx winapp node create-addon (不使用 --template 标志)创建 C++ 加载项。 C++ 加载项使用 node-addon-api并提供对具有最佳性能的 Windows API 的直接访问。 请参阅 C++ 通知加载项指南 以了解演练,或查看 完整的命令文档 以获取更多选项。
步骤 2:下载 SqueezeNet 模型并获取示例代码
我们将使用 AI 开发库中的分类图像示例作为参考。 此示例使用 SqueezeNet 1.1 模型进行图像分类。
2.1. 下载模型
- 安装 AI 开发画廊
- 导航到 分类图像示例
- 下载 SqueezeNet 1.1 模型(它支持 CPU、GPU 和 NPU)
- 单击“ 打开包含文件夹 ”以找到
.onnx该文件
- 将
squeezenet1.1.onnx文件复制到 project 根目录中的models/文件夹中
注释
步骤 3:添加所需的 NuGet 包
在添加 WinML 代码之前,我们需要添加图像处理、ONNX 运行时和 GenAI 支持所需的其他 NuGet 包。
3.1. 更新 Directory.packages.props
将以下包版本添加到 Directory.packages.props 项目的根目录中的文件(应在创建加载项时创建):
<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. 更新 winMlAddon.csproj
打开 winMlAddon/winMlAddon.csproj 包,并将包引用添加到 <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>
这些包执行的操作:
- Microsoft.ML.OnnxRuntime.Extensions - 为 ONNX 运行时提供其他运算符和实用工具
- System.Drawing.Common - 为预处理启用图像加载和操作
- Microsoft。Extensions.AI - 适用于.NET的 AI 抽象
- Microsoft.ML.OnnxRuntimeGenAI.Managed - ONNX Runtime GenAI 的托管绑定
- Microsoft.ML.OnnxRuntimeGenAI.WinML - ONNX Runtime GenAI 的 WinML 集成
步骤 4:添加示例代码
AI 开发库显示了使用 SqueezeNet 进行图像分类的完整实现:
我们已为 Electron 改编了此代码,你可以在 电子 winml 示例中找到完整的实现。 该 winMlAddon/ 文件夹包含 AI 开发库中修改的代码。
将整个 winMlAddon/ 文件夹从 samples/electron-winml/winMlAddon/ 复制到项目根目录,替换步骤 1 中创建的文件夹。 示例包括多于addon.cs的多个文件(Utils/中的帮助类、聊天客户端等),这些文件是构建和运行插件所必需的。
Important
必须复制 整个文件夹,而不仅仅是 addon.cs。 加载项依赖于子文件夹中的帮助程序文件 Utils/(Prediction.cs、ImageNet.cs、BitmapFunctions.cs 等等)。
关键实现详细信息
重点介绍实现的重要部分,以及与 AI Dev Gallery 代码的主要区别:
1. Project根路径要求
与 AI 开发库代码不同,我们的 Electron 加载项需要 JavaScript 代码才能传递 项目根路径。 这是必要的,因为:
- 加载项需要在文件夹中找到 ONNX 模型文件
models/ - 需要从特定目录加载本机依赖项(DLL)
[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;
}
这会根据设备功能自动选择最佳执行提供程序(CPU、GPU 或 NPU)。
2. 预加载原生依赖项
该加载项包含一种 PreloadNativeDependencies() 方法,用于加载所需的 DLL。 此方法适用于 开发和生产 方案,无需将 DLL 复制到项目根目录:
private void PreloadNativeDependencies()
{
// Loads required DLLs from the winMlAddon build output
// This ensures dependencies are available regardless of the execution context
}
在加载模型之前,在初始化期间调用此方法,确保所有本机库都可用。
3. 配置 Electron Forge 以进行打包
若要确保加载项在生产版本中正常工作,需要将打包程序配置为:
- 解压本地文件 - DLL、ONNX模型及.node文件必须在ASAR存档之外可访问
- 排除不必要的文件 - 通过排除生成项目和临时文件来保持包大小较小
对于 Electron Forge,请更新您的 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
};
此功能的作用:
asar.unpack- 将 DLL、可执行文件、.node 二进制文件和 ONNX 模型提取到app.asar.unpacked/- 这使得它们在运行时可通过文件系统路径进行访问
- JavaScript 代码自动调整路径(请参阅
app.asar上面的→app.asar.unpacked替换)
ignore- 从最终包中排除:-
.winapp/- SDK 包和标头(运行时不需要) -
.msix文件 - 打包输出 -
winMlAddon/源文件 - 仅保留包含已编译二进制文件的dist/文件夹
-
注释
如果使用其他打包工具(电子生成器等),则需要配置类似的设置来解压缩本机依赖项和排除开发文件。 查看打包程序的文档,了解 ASAR 解包选项。
4. 图像分类
该方法 ClassifyImage 处理图像并返回预测:
[JSExport]
public async Task<Prediction[]> ClassifyImage(string imagePath)
{
// Loads the image, preprocesses it, and runs inference
// Returns top predictions with labels and confidence scores
}
完整的实现管理以下内容:
- 图像加载和预处理(调整大小、规范化)
- 运行模型推理
- 处理后结果以获取具有标签和置信度分数的顶级预测
注释
完整的源代码包括图像预处理、张量创建和结果分析。 检查 示例实现 以了解所有详细信息。
了解代码
加载项提供以下主要函数:
- CreateAsync - 初始化加载项并加载 SqueezeNet 模型
- ClassificationImage - 获取图像路径并返回分类预测
WinML 根据可用性自动选择最佳执行设备(CPU、GPU 或 NPU)。
步骤 5:生成 C# 加载项
现在生成加载项:
npm run build-winMlAddon
这将使用 本机 AOT(提前编译)来编译 C# 代码:
- 创建
.node二进制(原生加载项格式) - 剪裁未使用的代码以缩小捆绑包大小
- 目标计算机上不需要.NET 运行时
- 提供原生性能
编译的加载项将位于 winMlAddon/dist/winMlAddon.node.
步骤 6:测试加载项
现在,让我们通过从主进程调用加载项来测试加载项的工作原理。 打开 src/main.js 并按照以下步骤操作:
6.1. 加载加载项
在顶部添加 require 语句:
const winMlAddon = require('../winMlAddon/dist/winMlAddon.node');
6.2。 创建测试函数
添加此函数以测试图像分类:
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);
}
};
要点:
- 路径调整(
app.asar→app.asar.unpacked)可确保代码在开发和打包应用中正常工作 - 这会访问
forge.config.js中配置的未打包本机文件。
6.3. 调用测试函数
在函数末尾 createWindow() 添加此行:
testWinML();
6.4. 准备测试映像
测试图像分类:
-
test-images/在项目根目录中创建文件夹 - 添加名为
sample.jpg的测试图像(代码需要此确切文件名) - SqueezeNet 模型可识别 1000 个不同的 ImageNet 类(动物、对象、场景等)
运行应用时,控制台中会显示分类结果!
Tip
有关包含 IPC 处理程序、文件选择对话框和用户界面的完整实现,请参阅 electron-winml 示例。
步骤 7:更新调试标识
为了确保加载Windows 应用 SDK并可供使用,我们需要确保设置调试标识,以确保每当应用运行时都会加载框架。 同样,每当修改 Package.appxmanifest 或更改清单中引用的资产(如应用图标),都需要更新应用的调试标识。 运行:
npx winapp node add-electron-debug-identity
此命令:
- 读取您的
Package.appxmanifest以获取应用的详细信息和功能 - 在你的
electron.exe中以临时身份注册node_modules - 使你无需完全 MSIX 打包即可测试身份验证所需的 API
注释
此命令已经是我们在安装指南中添加的 postinstall 脚本的一部分,因此它在 npm install 之后自动运行。 但是,无论何时,都需要手动运行它:
- 修改
Package.appxmanifest(更改功能、标识或属性) - 更新应用资产(图标、徽标等)
现在运行应用:
npm start
检查控制台输出 - 应会看到 WinML 测试结果!
⚠️ 已知问题:应用崩溃或空白窗口(单击以展开)
已知存在与稀疏打包相关的Windows bug,会导致Electron应用程序启动时崩溃或无法呈现Web内容。 此问题已在Windows中修复,但尚未传播到所有设备。
有关解决方法,请参阅 开发环境设置 。
后续步骤
祝贺! 你已成功创建一个本机加载项,可以使用 WinML 运行机器学习模型! 🎉
现在,你已准备好:
- 打包应用进行分发 - 创建可以分发的 MSIX 包
或者浏览其他指南:
- 创建 Phi 硅加载项 - 了解如何使用 Phi 硅 AI API
- 入门概述 - 返回到主指南
为您的模型进行自定义
若要完全集成 ONNX 模型,需要:
- 了解模型的输入 - 图像、张量、序列等。
- 创建正确的输入绑定 - 将数据转换为 WinML 所需的格式
- 处理输出 - 分析和解释模型的预测
- 正常处理错误 - 模型加载和推理可能会失败
其他资源
- WinML 文档 - 官方 WinML 文档
- winapp CLI 文档 - 全面的 CLI 参考指南
- 示例 Electron 应用 - 完整运行示例
- AI 开发库 - 所有 AI API 的示例库
- Windows 应用 SDK 示例 - Windows 应用 SDK示例集合
- node-api-dotnet - C# ↔ JavaScript 互操作库
故障排除
生成失败,错误 NU1010:PackageReference 项未定义相应的 PackageVersion
确保在winMlAddon.csproj中引用的所有包都在Directory.packages.props中有相应的匹配项。 有关所需包的完整列表,请参阅步骤 3。
加载加载项时,“不是有效的 Win32 应用程序”
这意味着加载项是为与 Node.js/Electron 运行时不同的体系结构构建的。 检查 Node.js 体系结构:
node -e "console.log(process.arch)"
然后使用匹配的目标重新生成加载项:
# 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
如果最近更改了 Node.js 安装,请重新安装 node_modules 以获取匹配的 Electron 二进制文件:
rm -rf node_modules package-lock.json
npm install
获取帮助
快乐的机器学习! 🤖