이 가이드에서는 Electron 앱에서 WinML(Windows Machine Learning)을 사용하는 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 필요합니다. 설치 가이드에서 "안정적인 SDK" winapp init 를 선택한 경우 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에, 빌드 아티팩트를 정리하기 위한 스크립트를 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
- project 루트의
squeezenet1.1.onnx폴더에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 런타임 GenAI에 대한 관리되는 바인딩
- Microsoft.ML.OnnxRuntimeGenAI.WinML - ONNX 런타임 GenAI용 WinML 통합
4단계: 샘플 코드 추가
AI 개발자 갤러리는 SqueezeNet을 사용하여 이미지 분류에 대한 전체 구현을 보여줍니다.
이 코드는 Electron에 맞게 조정되었으며 , Electron-winml 샘플에서 완전한 구현을 찾을 수 있습니다. 폴더에는 winMlAddon/ AI 개발자 갤러리의 수정된 코드가 포함되어 있습니다.
winMlAddon/에서 프로젝트 루트로 전체 폴더를 복사하여 1단계에서 만든 폴더를 대체합니다. 샘플에는 addon.cs를 넘어서 추가 기능을 빌드하고 실행하는 데 필요한 여러 파일(예: Utils/의 도우미 클래스, 채팅 클라이언트 등)이 포함됩니다.
Important
뿐만 아니라 addon.cs를 복사해야 합니다. 추가 기능은 하위 폴더의 도우미 파일 Utils/ (Prediction.cs, ImageNet.cs, BitmapFunctions.cs등)에 따라 달라집니다.
주요 구현 세부 정보
구현의 중요한 부분과 AI 개발자 갤러리 코드의 주요 차이점을 강조해 보겠습니다.
1. Project 루트 경로 요구 사항
AI 개발자 갤러리 코드와 달리 Electron 추가 기능에는 프로젝트 루트 경로를 전달하는 JavaScript 코드가 필요합니다. 이는 다음과 같은 이유로 필요합니다.
- 애드온은
models/폴더에서 ONNX 모델 파일을 찾아야 합니다. - 특정 디렉터리에서 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. 네이티브 종속성 미리 로드
추가 기능에는 필요한 DLL을 PreloadNativeDependencies() 로드하는 메서드가 포함되어 있습니다. 이 방법은 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 구성하기
프로덕션 빌드에서 추가 기능이 올바르게 작동하는지 확인하려면 다음을 수행하도록 패키지 관리자를 구성해야 합니다.
- 네이티브 파일 압축 풀기 - ASAR 보관 파일 외부에서 DLL, ONNX 모델 및 .node 파일에 액세스할 수 있어야 합니다.
- 불필요한 파일 제외 - 빌드 아티팩트 및 임시 파일을 제외하여 패키지 크기를 작게 유지
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 모델을 로드합니다.
- ClassifyImage - 이미지 경로를 사용하고 분류 예측을 반환합니다.
WinML은 가용성에 따라 최상의 실행 디바이스(CPU, GPU 또는 NPU)를 자동으로 선택합니다.
5단계: C# 추가 기능 빌드
이제 추가 기능을 빌드합니다.
npm run build-winMlAddon
네이티브 AOT (Ahead-of-Time 컴파일)을 사용하여 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. Test 함수 호출
함수의 끝에 다음 줄을 추가합니다.createWindow()
testWinML();
6.4. 테스트 이미지 준비
이미지 분류를 테스트하려면 다음을 수행합니다.
-
test-images/프로젝트 루트에 폴더 만들기 - 명명
sample.jpg된 테스트 이미지 추가(코드에 정확한 파일 이름이 필요합니다.) - SqueezeNet 모델은 1,000개의 다양한 ImageNet 클래스(동물, 개체, 장면 등)를 인식합니다.
앱을 실행하면 콘솔에 분류 결과가 표시됩니다.
Tip
IPC 처리기, 파일 선택 대화 상자 및 UI를 사용한 완전한 구현은 electron-winml 샘플을 참조하세요.
7단계: 디버그 ID 업데이트
Windows 앱 SDK 로드되고 사용할 수 있도록 하려면 앱이 실행될 때마다 프레임워크가 로드되도록 디버그 ID를 설정해야 합니다. 마찬가지로 매니페스트에서 참조되는 자산(예: 앱 아이콘)을 수정 Package.appxmanifest 하거나 변경할 때마다 앱의 디버그 ID를 업데이트해야 합니다. Run:
npx winapp node add-electron-debug-identity
이 명령은 다음과 같습니다.
-
Package.appxmanifest앱 세부 정보 및 기능을 가져오기 위해 읽습니다. -
electron.exe를 임시 ID를 사용하여node_modules에 등록합니다. - 전체 MSIX 패키징 없이 ID 필수 API를 테스트할 수 있습니다.
비고
이 명령은 설치 가이드에서 추가한 postinstall 스크립트의 일부이므로 npm install 이후에 자동으로 실행됩니다. 그러나 다음을 수행할 때마다 수동으로 실행해야 합니다.
- 수정
Package.appxmanifest(기능, ID 또는 속성 변경) - 앱 자산 업데이트(아이콘, 로고 등)
이제 앱을 실행합니다.
npm start
콘솔 출력을 확인합니다. WinML 테스트 결과가 표시됩니다.
⚠️ 알려진 문제: 앱 크래시 또는 빈 창(확장하려면 클릭)
Windows에서 스파스 패키징된 Electron 애플리케이션과 관련된 알려진 버그가 있으며, 이로 인해 애플리케이션이 시작 시 충돌하거나 웹 콘텐츠를 렌더링하지 못할 수 있습니다. 이 문제는 Windows 해결되었지만 아직 모든 디바이스에 전파되지 않았습니다.
해결 방법은 개발 환경 설정을 참조하세요.
다음 단계
축하합니다! WinML을 사용하여 기계 학습 모델을 실행할 수 있는 네이티브 추가 기능을 성공적으로 만들었습니다. 🎉
이제 다음을 수행할 준비가 되었습니다.
- 배포용 앱 패키지 - 배포할 수 있는 MSIX 패키지 만들기
또는 다른 가이드를 살펴보세요.
- Phi Silica Addon 만들기 - Phi Silica AI API를 사용하는 방법 알아보기
- 시작 개요 - 기본 가이드로 돌아가기
모델에 대한 사용자 지정
ONNX 모델을 완전히 통합하려면 다음을 수행해야 합니다.
- 이미지, 텐서, 시퀀스 등 모델의 입력을 이해합니다.
- 적절한 입력 바인딩 만들기 - 데이터를 WinML에서 예상하는 형식으로 변환
- 출력 처리 - 모델의 예측 구문 분석 및 해석
- 정상적으로 오류 처리 - 모델 로드 및 유추가 실패할 수 있음
추가 리소스
- WinML 설명서 - 공식 WinML 설명서
- winapp CLI 설명서 - 전체 CLI 참조
- 샘플 Electron 앱 - 완전한 동작 예제
- AI 개발자 갤러리 - 모든 AI API의 샘플 갤러리
- Windows 앱 SDK 샘플 - Windows 앱 SDK 샘플 컬렉션
- node-api-dotnet - C# ↔ JavaScript interop 라이브러리
Troubleshooting
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 설치를 최근에 변경한 경우, 일치하는 Electron 이진 파일을 얻기 위해 node_modules을 다시 설치하십시오.
rm -rf node_modules package-lock.json
npm install
지원 받기
행복한 기계 학습! 🤖
Windows developer