diff --git a/Samples/WindowsML/README.md b/Samples/WindowsML/README.md index 011301bb2..f848a09a6 100644 --- a/Samples/WindowsML/README.md +++ b/Samples/WindowsML/README.md @@ -35,6 +35,12 @@ Windows ML enables high-performance, reliable inferencing of machine learning mo |--------|-------------|--------------| | [cpp-abi](cpp-abi/) | Direct ABI implementation using raw COM interfaces | Automatic ABI header generation, no projections | +### CMake Samples + +| Sample | Description | Key Features | +|--------|-------------|--------------| +| [cmake/WinMLEpCatalog](cmake/WinMLEpCatalog/) | WinMLEpCatalog Native C API with CMake/vcpkg | Native C API, no WinRT dependencies, interactive shell | + ### C# Samples #### Console Applications diff --git a/Samples/WindowsML/cmake/WinMLEpCatalog/.clang-format b/Samples/WindowsML/cmake/WinMLEpCatalog/.clang-format new file mode 100644 index 000000000..e371afbc6 --- /dev/null +++ b/Samples/WindowsML/cmake/WinMLEpCatalog/.clang-format @@ -0,0 +1,49 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +# Clang-format configuration for WinML EP Catalog Sample + +BasedOnStyle: Microsoft +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ColumnLimit: 120 + +# Braces +BreakBeforeBraces: Allman +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false + +# Indentation +NamespaceIndentation: None +IndentCaseLabels: false + +# Alignment +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: true +AlignTrailingComments: true + +# Line breaks +AllowAllParametersOfDeclarationOnNextLine: true +BinPackArguments: false +BinPackParameters: false + +# Includes +SortIncludes: true +IncludeBlocks: Preserve + +# Pointer alignment +PointerAlignment: Left + +# Spaces +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false diff --git a/Samples/WindowsML/cmake/WinMLEpCatalog/.gitignore b/Samples/WindowsML/cmake/WinMLEpCatalog/.gitignore new file mode 100644 index 000000000..a6544aed7 --- /dev/null +++ b/Samples/WindowsML/cmake/WinMLEpCatalog/.gitignore @@ -0,0 +1,22 @@ +# Build outputs +out/ +build/ + +# CMake cache +CMakeCache.txt +CMakeFiles/ + +# Local package overrides (for development/testing) +local_packages/ + +# IDE files +.vs/ +.vscode/ +*.user + +# Compiled files +*.exe +*.dll +*.pdb +*.obj +*.ilk diff --git a/Samples/WindowsML/cmake/WinMLEpCatalog/CMakeLists.txt b/Samples/WindowsML/cmake/WinMLEpCatalog/CMakeLists.txt new file mode 100644 index 000000000..3de80e79e --- /dev/null +++ b/Samples/WindowsML/cmake/WinMLEpCatalog/CMakeLists.txt @@ -0,0 +1,98 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +cmake_minimum_required(VERSION 3.21) + +project(WinMLEpCatalogSample + VERSION 1.0.0 + DESCRIPTION "Windows ML Execution Provider Catalog Sample with ONNX Runtime Integration" + LANGUAGES CXX +) + +# Local NuGet package configuration +set(WINML_NUGET_PACKAGE "${CMAKE_CURRENT_SOURCE_DIR}/Microsoft.WindowsAppSDK.ML.2.0.246-experimental.nupkg" + CACHE FILEPATH "Path to Microsoft.WindowsAppSDK.ML nupkg") +set(WINML_LOCAL_PACKAGES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/local_packages" + CACHE PATH "Directory where the nupkg is extracted") + +# Auto-extract the local nupkg (if needed) and wire CMake package discovery. +# Users can still override microsoft.windows.ai.machinelearning_DIR directly. +if(NOT DEFINED microsoft.windows.ai.machinelearning_DIR) + if(EXISTS "${WINML_NUGET_PACKAGE}") + get_filename_component(_winml_nupkg_filename "${WINML_NUGET_PACKAGE}" NAME) + string(REGEX REPLACE "\\.nupkg$" "" _winml_nupkg_name "${_winml_nupkg_filename}") + set(_winml_extract_dir "${WINML_LOCAL_PACKAGES_DIR}/${_winml_nupkg_name}") + set(_winml_config_file "${_winml_extract_dir}/build/cmake/microsoft.windows.ai.machinelearning-config.cmake") + + if(NOT EXISTS "${_winml_config_file}") + file(MAKE_DIRECTORY "${WINML_LOCAL_PACKAGES_DIR}") + message(STATUS "Extracting ${WINML_NUGET_PACKAGE} -> ${_winml_extract_dir}") + file(ARCHIVE_EXTRACT + INPUT "${WINML_NUGET_PACKAGE}" + DESTINATION "${_winml_extract_dir}") + endif() + + set(microsoft.windows.ai.machinelearning_DIR "${_winml_extract_dir}/build/cmake" + CACHE PATH "Path to microsoft.windows.ai.machinelearning CMake config" FORCE) + else() + message(FATAL_ERROR + "Windows ML NuGet package not found: ${WINML_NUGET_PACKAGE}. " + "Place the nupkg at that location or set microsoft.windows.ai.machinelearning_DIR.") + endif() +endif() + +# Find the Windows ML package from the extracted NuGet package CMake config. +find_package(microsoft.windows.ai.machinelearning CONFIG REQUIRED) + +# C++20 standard for modern features (std::format, etc.) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Create the executable +add_executable(WinMLEpCatalogSample + main.cpp +) + +# WindowsML::Api - WinMLEpCatalog* C API for discovering execution providers +# WindowsML::OnnxRuntime - ONNX Runtime C/C++ API (OrtEnv, session creation, etc.) +target_link_libraries(WinMLEpCatalogSample + PRIVATE + WindowsML::Api + WindowsML::OnnxRuntime +) + +# Copy runtime DLLs (onnxruntime.dll, Microsoft.Windows.AI.MachineLearning.dll) +# to the executable output directory so the sample can run in-place. +add_custom_command(TARGET WinMLEpCatalogSample POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ $ + COMMAND_EXPAND_LISTS + VERBATIM +) + +# DirectML.dll has no import library, so it is not included in TARGET_RUNTIME_DLLS. +# Copy it explicitly if your application uses the DirectML execution provider. +add_custom_command(TARGET WinMLEpCatalogSample POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${WINML_DIRECTML_DLL}" $ +) + +# Set Windows-specific compiler options +if(MSVC) + target_compile_options(WinMLEpCatalogSample PRIVATE + /W4 # Warning level 4 + /permissive- # Strict C++ conformance + /Zc:__cplusplus # Report correct __cplusplus value + /EHsc # Enable C++ exception handling + ) + + # Use static runtime for easier deployment (no MSVC runtime dependency) + set_property(TARGET WinMLEpCatalogSample PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" + ) +endif() + +# Set subsystem to console (not Windows GUI) +if(WIN32) + set_target_properties(WinMLEpCatalogSample PROPERTIES + WIN32_EXECUTABLE FALSE + ) +endif() diff --git a/Samples/WindowsML/cmake/WinMLEpCatalog/CMakePresets.json b/Samples/WindowsML/cmake/WinMLEpCatalog/CMakePresets.json new file mode 100644 index 000000000..e682b9764 --- /dev/null +++ b/Samples/WindowsML/cmake/WinMLEpCatalog/CMakePresets.json @@ -0,0 +1,28 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "nuget", + "displayName": "NuGet (Direct)", + "description": "Configure using local .nupkg extraction and direct CMake package files", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + } + ], + "buildPresets": [ + { + "name": "nuget", + "configurePreset": "nuget" + } + ] +} diff --git a/Samples/WindowsML/cmake/WinMLEpCatalog/README.md b/Samples/WindowsML/cmake/WinMLEpCatalog/README.md new file mode 100644 index 000000000..5814cdcc2 --- /dev/null +++ b/Samples/WindowsML/cmake/WinMLEpCatalog/README.md @@ -0,0 +1,91 @@ +# Windows ML EP Catalog Sample (CMake/WinML C API) + +This sample demonstrates the **WinMLEpCatalog WinML C API** for discovering and managing hardware-accelerated execution providers (EPs) for machine learning inference on Windows. + +The CMake project is configured to use the Windows ML NuGet package directly and auto-extract it during configure. + +## Overview + +This sample showcases: + +- Creating an ONNX Runtime environment (`Ort::Env`) +- Creating and releasing a catalog handle +- Enumerating registered execution providers +- Inspecting provider metadata (name, version, state, certification) +- Preparing providers with `EnsureReady` +- Registering prepared providers with ONNX Runtime + +## Prerequisites + +- **Visual Studio 2022** with C++ workload +- **CMake** 3.21 or later +- **Ninja** (optional; only needed when using Ninja generator) +- `Microsoft.WindowsAppSDK.ML.2.0.246-experimental.nupkg` in this folder + +Example install commands: + +```powershell +winget install --exact --id Kitware.CMake +winget install --exact --id Ninja-build.Ninja +``` + +## Build (Recommended) + +Use the helper script: + +```powershell +# RelWithDebInfo for host architecture +.\build.ps1 + +# Debug with Visual Studio generator +.\build.ps1 -Generator VisualStudio -Configuration Debug + +# Release for ARM64 +.\build.ps1 -Configuration Release -Platform arm64 + +# Clean and rebuild +.\build.ps1 -Clean +``` + +During configure, CMake automatically: + +1. Finds the local `.nupkg` +2. Extracts it to `local_packages//` (if not already extracted) +3. Sets `microsoft.windows.ai.machinelearning_DIR` to the extracted `build/cmake` folder + +## Manual Build + +```powershell +# Configure (auto-extracts nupkg if needed) +cmake --preset nuget + +# Build +cmake --build out/build/nuget --config RelWithDebInfo + +# Run +.\out\build\nuget\WinMLEpCatalogSample.exe +``` + +## Package Version Override + +To use a different package file: + +```powershell +cmake --preset nuget -DWINML_NUGET_PACKAGE=.\Microsoft.WindowsAppSDK.ML..nupkg +``` + +For a full refresh, delete `local_packages/` and `out/` and configure again. + +## Troubleshooting + +### "Windows ML NuGet package not found" + +Ensure the package exists in this directory, or pass `-DWINML_NUGET_PACKAGE=`. + +### "Failed to create EP catalog" + +Check that runtime DLLs were copied to the executable output directory and the target OS is supported. + +## License + +Copyright (C) Microsoft Corporation. All rights reserved. diff --git a/Samples/WindowsML/cmake/WinMLEpCatalog/build.cmd b/Samples/WindowsML/cmake/WinMLEpCatalog/build.cmd new file mode 100644 index 000000000..e2e2a4a12 --- /dev/null +++ b/Samples/WindowsML/cmake/WinMLEpCatalog/build.cmd @@ -0,0 +1,20 @@ +@echo off +REM Copyright (C) Microsoft Corporation. All rights reserved. +REM +REM Simple batch wrapper for build.ps1 +REM Usage: build.cmd [Debug|Release|RelWithDebInfo|MinSizeRel] [x64|arm64] [Ninja|VisualStudio] +REM + +setlocal + +set CONFIG=%1 +set PLATFORM=%2 +set GENERATOR=%3 + +if "%CONFIG%"=="" set CONFIG=Debug + +if "%GENERATOR%"=="" ( + powershell -ExecutionPolicy Bypass -File "%~dp0build.ps1" -Configuration %CONFIG% -Platform %PLATFORM% +) else ( + powershell -ExecutionPolicy Bypass -File "%~dp0build.ps1" -Configuration %CONFIG% -Platform %PLATFORM% -Generator %GENERATOR% +) diff --git a/Samples/WindowsML/cmake/WinMLEpCatalog/build.ps1 b/Samples/WindowsML/cmake/WinMLEpCatalog/build.ps1 new file mode 100644 index 000000000..cdfda3c07 --- /dev/null +++ b/Samples/WindowsML/cmake/WinMLEpCatalog/build.ps1 @@ -0,0 +1,286 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +<# +.SYNOPSIS + Builds the WinML EP Catalog Sample using CMake and a local NuGet package. + +.DESCRIPTION + This script automates the build process for the WinMLEpCatalog sample. + It handles: + - Checking prerequisites (CMake, Visual Studio) + - Setting up Visual Studio developer environment + - Configuring and building the project using the NuGet CMake preset + +.PARAMETER Configuration + Build configuration: Debug, Release, RelWithDebInfo, MinSizeRel. Default: RelWithDebInfo + +.PARAMETER Generator + Build generator: Ninja or VisualStudio. Default: Ninja + +.PARAMETER NuGetPackage + Path to Microsoft.WindowsAppSDK.ML nupkg. + Default: .\Microsoft.WindowsAppSDK.ML.2.0.246-experimental.nupkg + +.PARAMETER Clean + If specified, reconfigures with `cmake --fresh` before building. + +.EXAMPLE + .\build.ps1 + # Builds RelWithDebInfo for the current platform + +.EXAMPLE + .\build.ps1 -Configuration Release -Platform arm64 + # Builds Release for ARM64 + +.EXAMPLE + .\build.ps1 -Generator VisualStudio -Configuration Debug + # Builds Debug using the Visual Studio generator + +.EXAMPLE + .\build.ps1 -Clean -Configuration Debug + # Cleans and rebuilds Debug configuration +#> + +[CmdletBinding()] +param( + [ValidateSet('Debug', 'Release', 'RelWithDebInfo', 'MinSizeRel')] + [string]$Configuration = 'RelWithDebInfo', + + [ValidateSet('x64', 'arm64')] + [string]$Platform, + + [ValidateSet('Ninja', 'VisualStudio')] + [string]$Generator = 'Ninja', + + [string]$NuGetPackage = 'Microsoft.WindowsAppSDK.ML.2.0.246-experimental.nupkg', + + [switch]$Clean +) + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = 'Stop' + +# ============================================================================ +# Helper Functions +# ============================================================================ + +function Test-CommandExists { + param([string]$Command) + $null -ne (Get-Command $Command -ErrorAction SilentlyContinue) +} + +function Get-HostArchitecture { + $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture + switch ($arch) { + 'X64' { return 'x64' } + 'Arm64' { return 'arm64' } + default { return 'x64' } + } +} + +function Write-Header { + param([string]$Message) + Write-Host "" + Write-Host "========================================" -ForegroundColor Cyan + Write-Host " $Message" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan +} + +function Write-Step { + param([string]$Message) + Write-Host "" + Write-Host "[*] $Message" -ForegroundColor Yellow +} + +function Write-Success { + param([string]$Message) + Write-Host "[OK] $Message" -ForegroundColor Green +} + +function Write-ErrorMessage { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +function Enter-VsDevEnvironment { + param([string]$Arch) + + # Find Visual Studio installation + $vswherePath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswherePath)) { + $vswherePath = "${env:ProgramFiles}\Microsoft Visual Studio\Installer\vswhere.exe" + } + + if (-not (Test-Path $vswherePath)) { + Write-ErrorMessage "vswhere not found. Please install Visual Studio 2022 or later." + return $false + } + + $vsPath = & $vswherePath -latest -property installationPath + if (-not $vsPath) { + Write-ErrorMessage "Visual Studio installation not found." + return $false + } + + $devShellModule = Join-Path $vsPath "Common7\Tools\Microsoft.VisualStudio.DevShell.dll" + if (-not (Test-Path $devShellModule)) { + Write-ErrorMessage "VS DevShell module not found at: $devShellModule" + return $false + } + + Write-Host " Visual Studio: $vsPath" + + # Import the DevShell module and enter the environment + Import-Module $devShellModule + Enter-VsDevShell -VsInstallPath $vsPath -DevCmdArguments "-arch=$Arch" -SkipAutomaticLocation + + return $true +} + +# ============================================================================ +# Main Script +# ============================================================================ + +Write-Header "WinML EP Catalog Sample Build" + +# Auto-detect platform if not specified +if (-not $Platform) { + $Platform = Get-HostArchitecture + Write-Host "Auto-detected platform: $Platform" +} + +# Determine preset name +$PresetName = "nuget" +Write-Host "Build preset: $PresetName" +Write-Host "Generator: $Generator" + +# ============================================================================ +# Check Prerequisites +# ============================================================================ + +Write-Step "Checking prerequisites..." + +# Check CMake +if (-not (Test-CommandExists 'cmake')) { + Write-ErrorMessage "CMake not found. Please install CMake 3.21 or later." + Write-Host " Install with: winget install Kitware.CMake" -ForegroundColor Gray + exit 1 +} + +$cmakeVersion = (cmake --version | Select-Object -First 1) +Write-Host " CMake: $cmakeVersion" + +# Check Ninja (only required for Ninja generator) +if ($Generator -eq 'Ninja') { + if (Test-CommandExists 'ninja') { + $ninjaVersion = (ninja --version) + Write-Host " Ninja: $ninjaVersion" + } + else { + Write-ErrorMessage "Ninja not found. Install Ninja or use -Generator VisualStudio." + exit 1 + } +} + +# Check NuGet package +$resolvedNuGetPackage = Resolve-Path (Join-Path $PSScriptRoot $NuGetPackage) -ErrorAction SilentlyContinue +if (-not $resolvedNuGetPackage) { + $resolvedNuGetPackage = Resolve-Path $NuGetPackage -ErrorAction SilentlyContinue +} + +if (-not $resolvedNuGetPackage) { + Write-ErrorMessage "NuGet package not found: $NuGetPackage" + Write-Host " Place Microsoft.WindowsAppSDK.ML.2.0.246-experimental.nupkg in this folder or pass -NuGetPackage." -ForegroundColor Gray + exit 1 +} + +Write-Host " NuGet package: $resolvedNuGetPackage" + +Write-Success "Prerequisites check passed" + +# ============================================================================ +# Set up Visual Studio Developer Environment +# ============================================================================ + +Write-Step "Setting up Visual Studio Developer environment..." + +if (-not (Enter-VsDevEnvironment -Arch $Platform)) { + Write-ErrorMessage "Failed to set up Visual Studio environment" + exit 1 +} + +Write-Success "VS Developer environment configured for $Platform" + +# ============================================================================ +# Clean (if requested) +# ============================================================================ + +if ($Clean) { + Write-Step "Clean requested: configure will run with --fresh" +} + +# ============================================================================ +# Configure +# ============================================================================ + +Write-Step "Configuring with CMake preset: $PresetName" + +$buildDir = Join-Path $PSScriptRoot "out/build/$PresetName" +$configureArgs = @( + '--preset', $PresetName, + '-S', $PSScriptRoot, + '-B', $buildDir, + "-DWINML_NUGET_PACKAGE=$($resolvedNuGetPackage.Path)" +) + +if ($Clean) { + $configureArgs += '--fresh' +} + +if ($Generator -eq 'VisualStudio') { + $configureArgs += @('-G', 'Visual Studio 17 2022', '-A', $Platform) +} +else { + $configureArgs += @('-G', 'Ninja', "-DCMAKE_BUILD_TYPE=$Configuration") +} + +& cmake @configureArgs +if ($LASTEXITCODE -ne 0) { + Write-ErrorMessage "CMake configuration failed" + exit 1 +} +Write-Success "Configuration complete" + +# ============================================================================ +# Build +# ============================================================================ + +Write-Step "Building..." + +& cmake --build $buildDir --config $Configuration +if ($LASTEXITCODE -ne 0) { + Write-ErrorMessage "Build failed" + exit 1 +} +Write-Success "Build complete" + +# ============================================================================ +# Output Information +# ============================================================================ + +Write-Header "Build Successful" + +$exePath = if ($Generator -eq 'VisualStudio') { + Join-Path $PSScriptRoot "out\build\$PresetName\$Configuration\WinMLEpCatalogSample.exe" +} else { + Join-Path $PSScriptRoot "out\build\$PresetName\WinMLEpCatalogSample.exe" +} +if (Test-Path $exePath) { + Write-Host "" + Write-Host "Output: $exePath" -ForegroundColor Green + Write-Host "" + Write-Host "To run the sample:" -ForegroundColor Cyan + Write-Host " $exePath" + Write-Host "" +} + +exit 0 diff --git a/Samples/WindowsML/cmake/WinMLEpCatalog/main.cpp b/Samples/WindowsML/cmake/WinMLEpCatalog/main.cpp new file mode 100644 index 000000000..e5108576e --- /dev/null +++ b/Samples/WindowsML/cmake/WinMLEpCatalog/main.cpp @@ -0,0 +1,139 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +// +// Windows ML Execution Provider Catalog Sample +// +// Demonstrates discovering certified ML execution providers, preparing them for use, +// and registering them with ONNX Runtime. This mirrors the behavior of the +// ExecutionProviderCatalog::RegisterCertifiedProviders WinRT API. +// + +#include +#include + +#include + +#include +#include +#include +#include +#include + +struct RegistrationContext +{ + Ort::Env* env; + std::vector registeredProviders; +}; + +static BOOL CALLBACK RegisterCertifiedProvider(WinMLEpHandle ep, const WinMLEpInfo* info, void* context) +{ + if (!info || !info->name) + { + return TRUE; + } + + auto* ctx = static_cast(context); + + std::cout << std::format("Provider: {}\n", info->name); + + if (info->certification != WinMLEpCertification_Certified) + { + std::cout << " Skipping (not certified)\n\n"; + return TRUE; + } + + HRESULT hr = WinMLEpEnsureReady(ep); + if (FAILED(hr)) + { + std::cout << std::format(" EnsureReady failed: 0x{:08X}\n\n", static_cast(hr)); + return TRUE; + } + + size_t pathSize = 0; + hr = WinMLEpGetLibraryPathSize(ep, &pathSize); + if (FAILED(hr) || pathSize == 0) + { + std::cout << " Failed to get library path\n\n"; + return TRUE; + } + + std::string libraryPathUtf8(pathSize, '\0'); + size_t used = 0; + hr = WinMLEpGetLibraryPath(ep, pathSize, libraryPathUtf8.data(), &used); + if (FAILED(hr)) + { + std::cout << " Failed to get library path\n\n"; + return TRUE; + } + libraryPathUtf8.resize(used > 0 ? used - 1 : 0); + + std::cout << std::format(" Library: {}\n", libraryPathUtf8); + + try + { + std::filesystem::path libraryPath(libraryPathUtf8); + ctx->env->RegisterExecutionProviderLibrary(info->name, libraryPath.wstring()); + ctx->registeredProviders.push_back(info->name); + std::cout << " Registered!\n\n"; + } + catch (const Ort::Exception& e) + { + std::cout << std::format(" Registration failed: {}\n\n", e.what()); + } + + return TRUE; +} + +int main() +{ + std::cout << "Windows ML Execution Provider Catalog Sample\n\n"; + + try + { + Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "WinMLEpCatalogSample"); + + WinMLEpCatalogHandle catalog = nullptr; + HRESULT hr = WinMLEpCatalogCreate(&catalog); + if (FAILED(hr)) + { + std::cerr << std::format("Failed to create catalog: 0x{:08X}\n", static_cast(hr)); + return 1; + } + + RegistrationContext ctx{&env, {}}; + hr = WinMLEpCatalogEnumProviders(catalog, RegisterCertifiedProvider, &ctx); + if (FAILED(hr)) + { + std::cerr << std::format("Enumeration failed: 0x{:08X}\n", static_cast(hr)); + } + + auto epDevices = env.GetEpDevices(); + std::cout << std::format("Available EP devices ({}):\n", epDevices.size()); + for (const auto& device : epDevices) + { + const char* epName = device.EpName(); + if (epName) + { + std::cout << std::format(" - {}\n", epName); + } + } + + WinMLEpCatalogRelease(catalog); + + std::cout << std::format("\nRegistered {} certified provider(s).\n", ctx.registeredProviders.size()); + + // Unregister providers before env goes out of scope + for (const auto& name : ctx.registeredProviders) + { + env.UnregisterExecutionProviderLibrary(name.c_str()); + } + } + catch (const Ort::Exception& e) + { + std::cerr << std::format("ORT error: {}\n", e.what()); + return 1; + } + + return 0; +} +