Skip to content

Commit 8336385

Browse files
authored
Windows ML: ABI Execution Provider enumeration sample (#544)
* Windows ML: ABI Execution Provider enumeration sample Add Windows ML sample demonstrating execution provider discovery and management using direct ABI/COM interfaces with Microsoft.WindowsAppSDK.ML package. Key features: - Automatic ABI header generation via Microsoft.Windows.AbiWinRT - Complete provider lifecycle management (discovery → preparation → registration) - Event-driven async operations with progress reporting - Modular architecture with reusable components Technical implementation: - Direct COM interface programming with Microsoft::WRL - HANDLE-based async synchronization patterns - Self-contained Windows App SDK deployment, but nothing here precludes framework-based deployments. - Filtered metadata processing for Windows ML APIs Components added: - AbiUtils.h: HSTRING utilities and common helpers - AsyncHandlers: Progress and completion event handlers - ExecutionProvider: IExecutionProvider wrapper with state management - ExecutionProviderCatalog: Provider discovery and enumeration - ConsoleUI: Interactive user interface utilities - Application: Lifecycle management and orchestration This sample provides developers with production-ready patterns for direct ABI access to Windows ML capabilities, complementing existing C++/WinRT samples with a lower-level programming approach. * Update Nuget package directory path and enhance error reporting in ExecutionProviderCatalog
1 parent 146e58c commit 8336385

19 files changed

+1252
-0
lines changed

Samples/WindowsML/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ Windows ML enables high-performance, reliable inferencing of machine learning mo
2929
| [CppConsoleDll](cpp/CppConsoleDll/) | DLL usage pattern | WindowsML in shared library, memory management |
3030
| [CppResnetBuildDemo](cpp/CppResnetBuildDemo/) | ResNet model demo from the [Windows ML session](https://www.youtube.com/watch?v=AQjOq8qSsbE) at Build 2025 | Model conversion, EP compilation, detailed tutorial |
3131

32+
### C++ ABI Samples
33+
34+
| Sample | Description | Key Features |
35+
|--------|-------------|--------------|
36+
| [cpp-abi](cpp-abi/) | Direct ABI implementation using raw COM interfaces | Automatic ABI header generation, no projections |
37+
3238
### C# Samples
3339

3440
#### Console Applications

Samples/WindowsML/WindowsML-Samples.sln

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cs", "cs", "{E8F2C4D6-9A7B-
66
EndProject
77
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cpp", "cpp", "{A3B7D9E1-5C2F-4A8B-9E6D-3F7A1B8C5E9F}"
88
EndProject
9+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cpp-abi", "cpp-abi", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
10+
EndProject
911
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cs-winui", "cs-winui", "{F9A4C7E2-8D5B-4F3A-A1E6-7B9C2D5F8A3E}"
1012
EndProject
1113
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cs-winforms", "cs-winforms", "{B6E9A2D5-4C8F-4B7A-9F3E-5A8C1E4B7D9A}"
@@ -30,6 +32,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CppConsoleDesktop.Framework
3032
EndProject
3133
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CppConsoleDesktop.SelfContained", "cpp\CppConsoleDesktop.SelfContained\CppConsoleDesktop.SelfContained.vcxproj", "{2341505B-863B-4DF5-9A43-B84A635D1AE9}"
3234
EndProject
35+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CppAbiEPEnumerationSample", "cpp-abi\CppAbiEPEnumerationSample.vcxproj", "{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}"
36+
EndProject
3337
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsMLSampleForWPF", "cs-wpf\WindowsMLSampleForWPF.csproj", "{E6A9B3F5-C8D1-4E7F-9A2E-5C8F1B4D7A9E}"
3438
EndProject
3539
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloPhi", "cs\HelloPhi\HelloPhi.csproj", "{C4495933-0EFE-D24C-64FB-E581EA4E9909}"
@@ -116,6 +120,14 @@ Global
116120
{2341505B-863B-4DF5-9A43-B84A635D1AE9}.Release|ARM64.Build.0 = Release|ARM64
117121
{2341505B-863B-4DF5-9A43-B84A635D1AE9}.Release|x64.ActiveCfg = Release|x64
118122
{2341505B-863B-4DF5-9A43-B84A635D1AE9}.Release|x64.Build.0 = Release|x64
123+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}.Debug|ARM64.ActiveCfg = Debug|ARM64
124+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}.Debug|ARM64.Build.0 = Debug|ARM64
125+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}.Debug|x64.ActiveCfg = Debug|x64
126+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}.Debug|x64.Build.0 = Debug|x64
127+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}.Release|ARM64.ActiveCfg = Release|ARM64
128+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}.Release|ARM64.Build.0 = Release|ARM64
129+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}.Release|x64.ActiveCfg = Release|x64
130+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C}.Release|x64.Build.0 = Release|x64
119131
{E6A9B3F5-C8D1-4E7F-9A2E-5C8F1B4D7A9E}.Debug|ARM64.ActiveCfg = Debug|ARM64
120132
{E6A9B3F5-C8D1-4E7F-9A2E-5C8F1B4D7A9E}.Debug|ARM64.Build.0 = Debug|ARM64
121133
{E6A9B3F5-C8D1-4E7F-9A2E-5C8F1B4D7A9E}.Debug|x64.ActiveCfg = Debug|x64
@@ -170,6 +182,7 @@ Global
170182
{F5381E31-4C91-422C-8E60-0F63265BF4B5} = {B6E9A2D5-4C8F-4B7A-9F3E-5A8C1E4B7D9A}
171183
{3341505B-863B-4DF5-9A43-B84A635D1AE9} = {A3B7D9E1-5C2F-4A8B-9E6D-3F7A1B8C5E9F}
172184
{2341505B-863B-4DF5-9A43-B84A635D1AE9} = {A3B7D9E1-5C2F-4A8B-9E6D-3F7A1B8C5E9F}
185+
{4A7A8B12-4B8C-4D9E-A12F-3E4D5F6A7B8C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
173186
{E6A9B3F5-C8D1-4E7F-9A2E-5C8F1B4D7A9E} = {D8F3A6C1-7E2B-4A9F-8C5D-2E6A9B3F5C8A}
174187
{C4495933-0EFE-D24C-64FB-E581EA4E9909} = {E8F2C4D6-9A7B-4E5C-8D3A-1F6B9E2A7C5D}
175188
{7394B294-DE36-4E7B-B737-D3607D36E662} = {A3B7D9E1-5C2F-4A8B-9E6D-3F7A1B8C5E9F}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = crlf
5+
insert_final_newline = true
6+
7+
[*.md]
8+
indent_size = 4
9+
indent_style = space
10+
trim_trailing_whitespace = true
11+
12+
[{*.props,*.targets}]
13+
indent_size = 2
14+
indent_style = space
15+
16+
[{*.vcxproj,*.vcxproj.filters}]
17+
indent_size = 2
18+
indent_style = space
19+
insert_final_newline = false
20+
21+
[*.py]
22+
indent_size = 4
23+
indent_style = space
24+
trim_trailing_whitespace = true
25+
26+
[*.ps1]
27+
indent_size = 4
28+
indent_style = space
29+
trim_trailing_whitespace = true
30+
31+
[{*.xml,nuget.config}]
32+
indent_size = 2
33+
indent_style = space
34+
35+
[{*.h,*.cpp}]
36+
indent_size = 4
37+
indent_style = space
38+
trim_trailing_whitespace = true
39+
insert_final_newline = true
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE.md in the repo root for license information.
3+
#pragma once
4+
5+
//
6+
// ABI Utilities Header
7+
//
8+
// Common utilities for working with Windows Runtime ABI interfaces.
9+
// This header provides essential helper classes for ABI development.
10+
//
11+
12+
#include <windows.h>
13+
#include <combaseapi.h>
14+
#include <string>
15+
16+
namespace CppAbiEPEnumerationSample
17+
{
18+
19+
/// <summary>
20+
/// Simple RAII wrapper for HSTRING
21+
/// Provides automatic memory management and easy conversion to std::wstring
22+
/// </summary>
23+
class HStringWrapper {
24+
public:
25+
HStringWrapper() : m_hstr(nullptr) {}
26+
explicit HStringWrapper(HSTRING hstr) : m_hstr(hstr) {}
27+
28+
~HStringWrapper() {
29+
if (m_hstr) {
30+
WindowsDeleteString(m_hstr);
31+
}
32+
}
33+
34+
// Move constructor
35+
HStringWrapper(HStringWrapper&& other) noexcept : m_hstr(other.m_hstr) {
36+
other.m_hstr = nullptr;
37+
}
38+
39+
// Move assignment
40+
HStringWrapper& operator=(HStringWrapper&& other) noexcept {
41+
if (this != &other) {
42+
if (m_hstr) {
43+
WindowsDeleteString(m_hstr);
44+
}
45+
m_hstr = other.m_hstr;
46+
other.m_hstr = nullptr;
47+
}
48+
return *this;
49+
}
50+
51+
// Disable copy
52+
HStringWrapper(const HStringWrapper&) = delete;
53+
HStringWrapper& operator=(const HStringWrapper&) = delete;
54+
55+
HSTRING get() const { return m_hstr; }
56+
HSTRING* put() {
57+
if (m_hstr) {
58+
WindowsDeleteString(m_hstr);
59+
m_hstr = nullptr;
60+
}
61+
return &m_hstr;
62+
}
63+
64+
std::wstring ToString() const {
65+
if (!m_hstr) return L"";
66+
UINT32 length = 0;
67+
const wchar_t* buffer = WindowsGetStringRawBuffer(m_hstr, &length);
68+
return std::wstring(buffer, length);
69+
}
70+
71+
static HRESULT Create(const std::wstring& str, HSTRING* result) {
72+
return WindowsCreateString(str.c_str(), static_cast<UINT32>(str.length()), result);
73+
}
74+
75+
private:
76+
HSTRING m_hstr;
77+
};
78+
79+
} // namespace CppAbiEPEnumerationSample
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE.md in the repo root for license information.
3+
4+
//
5+
// Application Implementation
6+
//
7+
// Implementation of main application logic for the Windows ML ABI sample.
8+
//
9+
10+
#include "Application.h"
11+
#include "ConsoleUI.h"
12+
#include <roapi.h>
13+
#include <iostream>
14+
#include <stdexcept>
15+
16+
namespace CppAbiEPEnumerationSample
17+
{
18+
19+
void Application::Run()
20+
{
21+
// Initialize Windows Runtime outside try/catch for proper cleanup
22+
HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED);
23+
if (FAILED(hr))
24+
{
25+
std::wcout << L"Failed to initialize Windows Runtime. HRESULT: 0x"
26+
<< std::hex << hr << std::endl;
27+
return;
28+
}
29+
30+
try
31+
{
32+
RunInteractiveSession();
33+
}
34+
catch (const std::exception& e)
35+
{
36+
std::wcout << L"Error: " << std::wstring(e.what(), e.what() + strlen(e.what())) << std::endl;
37+
}
38+
39+
// Uninitialize Windows Runtime after all COM objects are destroyed
40+
RoUninitialize();
41+
}
42+
43+
void Application::RunInteractiveSession()
44+
{
45+
ConsoleUI::PrintHeader();
46+
47+
// Create catalog and find providers
48+
ExecutionProviderCatalog catalog;
49+
auto providers = catalog.FindAllProviders();
50+
51+
if (providers.empty())
52+
{
53+
std::wcout << L"No execution providers found." << std::endl;
54+
return;
55+
}
56+
57+
while (true)
58+
{
59+
ConsoleUI::PrintProviders(providers);
60+
61+
int selection = ConsoleUI::GetUserSelection(static_cast<int>(providers.size()));
62+
63+
if (selection == 0)
64+
{
65+
break;
66+
}
67+
68+
if (selection < 1 || selection > static_cast<int>(providers.size()))
69+
{
70+
std::wcout << L"Invalid selection." << std::endl;
71+
continue;
72+
}
73+
74+
const auto& selectedProvider = providers[selection - 1];
75+
ProcessProviderSelection(selectedProvider);
76+
77+
ConsoleUI::WaitForKeypress();
78+
}
79+
}
80+
81+
void Application::ProcessProviderSelection(const ExecutionProvider& provider)
82+
{
83+
std::wcout << L"Selected: " << provider.GetName() << std::endl;
84+
85+
auto currentState = provider.GetReadyState();
86+
if (currentState == ABI::Microsoft::Windows::AI::MachineLearning::ExecutionProviderReadyState_Ready)
87+
{
88+
std::wcout << L"Provider is already ready!" << std::endl;
89+
TryRegisterProvider(provider);
90+
}
91+
else
92+
{
93+
std::wcout << L"Provider current state: " << provider.GetReadyStateString(currentState) << std::endl;
94+
95+
if (currentState == ABI::Microsoft::Windows::AI::MachineLearning::ExecutionProviderReadyState_NotPresent ||
96+
currentState == ABI::Microsoft::Windows::AI::MachineLearning::ExecutionProviderReadyState_NotReady)
97+
{
98+
PrepareProvider(provider);
99+
}
100+
else
101+
{
102+
// Try to register for other states
103+
TryRegisterProvider(provider);
104+
}
105+
}
106+
}
107+
108+
void Application::PrepareProvider(const ExecutionProvider& provider)
109+
{
110+
std::wcout << L"Calling EnsureReadyAsync to prepare the provider..." << std::endl;
111+
112+
bool ensureReadySuccess = provider.EnsureReadyAsync();
113+
114+
if (ensureReadySuccess)
115+
{
116+
// Check the state again after EnsureReady
117+
auto newState = provider.GetReadyState();
118+
std::wcout << L"Provider state after EnsureReady: "
119+
<< provider.GetReadyStateString(newState) << std::endl;
120+
121+
if (newState == ABI::Microsoft::Windows::AI::MachineLearning::ExecutionProviderReadyState_Ready)
122+
{
123+
TryRegisterProvider(provider);
124+
}
125+
else
126+
{
127+
std::wcout << L"Provider is not ready even after EnsureReadyAsync." << std::endl;
128+
}
129+
}
130+
else
131+
{
132+
std::wcout << L"EnsureReadyAsync failed." << std::endl;
133+
}
134+
}
135+
136+
void Application::TryRegisterProvider(const ExecutionProvider& provider)
137+
{
138+
if (provider.TryRegister())
139+
{
140+
std::wcout << L"Provider registered successfully!" << std::endl;
141+
}
142+
else
143+
{
144+
std::wcout << L"Provider registration failed." << std::endl;
145+
}
146+
}
147+
148+
} // namespace CppAbiEPEnumerationSample
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE.md in the repo root for license information.
3+
#pragma once
4+
5+
//
6+
// Application Header
7+
//
8+
// Main application logic for the Windows ML ABI sample.
9+
// Orchestrates the interaction between providers, catalog, and user interface.
10+
//
11+
12+
#include "ExecutionProviderCatalog.h"
13+
#include "ExecutionProvider.h"
14+
15+
namespace CppAbiEPEnumerationSample
16+
{
17+
18+
/// <summary>
19+
/// Main application class that orchestrates the sample functionality
20+
/// Handles the application lifecycle and user interaction flow
21+
/// </summary>
22+
class Application
23+
{
24+
public:
25+
void Run();
26+
27+
private:
28+
void RunInteractiveSession();
29+
void ProcessProviderSelection(const ExecutionProvider& provider);
30+
void PrepareProvider(const ExecutionProvider& provider);
31+
void TryRegisterProvider(const ExecutionProvider& provider);
32+
};
33+
34+
} // namespace CppAbiEPEnumerationSample

0 commit comments

Comments
 (0)