Skip to content

Commit f1f800a

Browse files
Add basic plugin EP library example in C# (#552)
* Basic project structure * Get it working * Clean up * Clean up batch script again * Address review comments * Update plugin_execution_providers/basic/csharp/Contoso.ML.OnnxRuntime.EP.Basic/BasicEp.cs * Update plugin_execution_providers/basic/csharp/SampleApp/Program.cs Co-authored-by: Edward Chen <[email protected]> * Address review comments * Throw exception if EP DLL file is invalid --------- Co-authored-by: Edward Chen <[email protected]>
1 parent d3076d3 commit f1f800a

File tree

11 files changed

+384
-0
lines changed

11 files changed

+384
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/.vs/
2+
/local_feed/
3+
/Contoso.ML.OnnxRuntime.EP.Basic/runtimes/**/native/*.dll
4+
*.nupkg
5+
*.snupkg
6+
**/packages/*
7+
!**/packages/build/
8+
**/bin/*
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31903.59
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contoso.ML.OnnxRuntime.EP.Basic", "Contoso.ML.OnnxRuntime.EP.Basic\Contoso.ML.OnnxRuntime.EP.Basic.csproj", "{07807D61-E3FC-401E-B947-328645A0A775}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "SampleApp\SampleApp.csproj", "{9725B6C8-5BB5-45C6-9F93-FA4322215154}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Debug|x64 = Debug|x64
14+
Debug|x86 = Debug|x86
15+
Release|Any CPU = Release|Any CPU
16+
Release|x64 = Release|x64
17+
Release|x86 = Release|x86
18+
EndGlobalSection
19+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20+
{07807D61-E3FC-401E-B947-328645A0A775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{07807D61-E3FC-401E-B947-328645A0A775}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{07807D61-E3FC-401E-B947-328645A0A775}.Debug|x64.ActiveCfg = Debug|Any CPU
23+
{07807D61-E3FC-401E-B947-328645A0A775}.Debug|x64.Build.0 = Debug|Any CPU
24+
{07807D61-E3FC-401E-B947-328645A0A775}.Debug|x86.ActiveCfg = Debug|Any CPU
25+
{07807D61-E3FC-401E-B947-328645A0A775}.Debug|x86.Build.0 = Debug|Any CPU
26+
{07807D61-E3FC-401E-B947-328645A0A775}.Release|Any CPU.ActiveCfg = Release|Any CPU
27+
{07807D61-E3FC-401E-B947-328645A0A775}.Release|Any CPU.Build.0 = Release|Any CPU
28+
{07807D61-E3FC-401E-B947-328645A0A775}.Release|x64.ActiveCfg = Release|Any CPU
29+
{07807D61-E3FC-401E-B947-328645A0A775}.Release|x64.Build.0 = Release|Any CPU
30+
{07807D61-E3FC-401E-B947-328645A0A775}.Release|x86.ActiveCfg = Release|Any CPU
31+
{07807D61-E3FC-401E-B947-328645A0A775}.Release|x86.Build.0 = Release|Any CPU
32+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Debug|Any CPU.Build.0 = Debug|Any CPU
34+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Debug|x64.ActiveCfg = Debug|Any CPU
35+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Debug|x64.Build.0 = Debug|Any CPU
36+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Debug|x86.ActiveCfg = Debug|Any CPU
37+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Debug|x86.Build.0 = Debug|Any CPU
38+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Release|Any CPU.ActiveCfg = Release|Any CPU
39+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Release|Any CPU.Build.0 = Release|Any CPU
40+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Release|x64.ActiveCfg = Release|Any CPU
41+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Release|x64.Build.0 = Release|Any CPU
42+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Release|x86.ActiveCfg = Release|Any CPU
43+
{9725B6C8-5BB5-45C6-9F93-FA4322215154}.Release|x86.Build.0 = Release|Any CPU
44+
EndGlobalSection
45+
GlobalSection(SolutionProperties) = preSolution
46+
HideSolutionNode = FALSE
47+
EndGlobalSection
48+
EndGlobal
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
3+
4+
namespace Contoso.ML.OnnxRuntime.EP.Basic;
5+
6+
public static class BasicEp
7+
{
8+
/// <summary>
9+
/// Returns the path to the plugin EP library DLL contained by this package.
10+
/// Can be passed to OrtEnv::RegisterExecutionProviderLibrary().
11+
///
12+
/// Note: It is recommended that plugin EP packages provide this information to applications.
13+
/// </summary>
14+
/// <returns>EP library path</returns>
15+
/// <exception cref="FileNotFoundException">If the EP DLL file path does not exist</exception>
16+
public static string GetLibraryPath()
17+
{
18+
string rootDir = GetNativeDirectory();
19+
string osArch = $"{GetOSTag()}-{GetArchTag()}";
20+
string epDllPath = Path.GetFullPath(Path.Combine(rootDir, "runtimes", osArch,
21+
"native", "basic_plugin_ep.dll"));
22+
23+
if (!File.Exists(epDllPath))
24+
{
25+
// This indicates a packaging error.
26+
throw new FileNotFoundException($"Did not find EP DLL file: {epDllPath}");
27+
}
28+
29+
return epDllPath;
30+
}
31+
32+
/// <summary>
33+
/// Returns the names of the EPs created by the plugin EP library.
34+
/// Can be used to select a OrtEpDevice from those returned by OrtEnv::GetEpDevices().
35+
///
36+
/// Note: It is recommended that plugin EP packages provide this information to applications.
37+
/// </summary>
38+
/// <returns>Array of EP names</returns>
39+
public static string[] GetEpNames()
40+
{
41+
return ["BasicPluginExecutionProvider"];
42+
}
43+
44+
/// <summary>
45+
/// Returns the name of the one EP supported by this plugin EP library.
46+
///
47+
/// Note: This is a convenience function exposed by plugin EP packages that only have one EP name.
48+
/// </summary>
49+
/// <returns></returns>
50+
public static string GetEpName()
51+
{
52+
return GetEpNames()[0];
53+
}
54+
55+
private static string GetNativeDirectory()
56+
{
57+
var assemblyDir = Path.GetDirectoryName(typeof(BasicEp).Assembly.Location);
58+
59+
// Try returning where this assembly lives (works for framework-dependent)
60+
if (!string.IsNullOrEmpty(assemblyDir) && Directory.Exists(assemblyDir))
61+
return assemblyDir;
62+
63+
// Fallback to AppContext.BaseDirectory (works for single-file/self-contained)
64+
return AppContext.BaseDirectory;
65+
}
66+
67+
private static string GetOSTag()
68+
{
69+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return "win";
70+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return "linux";
71+
return "unknown";
72+
}
73+
74+
private static string GetArchTag()
75+
{
76+
return RuntimeInformation.OSArchitecture switch
77+
{
78+
Architecture.X64 => "x64",
79+
Architecture.Arm64 => "arm64",
80+
_ => "unknown"
81+
};
82+
}
83+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<Runtime Condition=" $([MSBuild]::IsOsPlatform('Windows')) AND '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64' ">win-x64</Runtime>
9+
<Runtime Condition=" $([MSBuild]::IsOsPlatform('Windows')) AND '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64' ">win-arm64</Runtime>
10+
11+
<!-- Package info -->
12+
<PackageId>Contoso.ML.OnnxRuntime.EP.Basic</PackageId>
13+
<Version>1.0.0</Version>
14+
<Authors>ORT</Authors>
15+
<Company>Microsoft</Company>
16+
<Description>A minimal package for a basic plugin EP.</Description>
17+
<PackageReadmeFile>readme.md</PackageReadmeFile>
18+
19+
<!-- License/Repository -->
20+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
21+
<RepositoryUrl>https://github.com/microsoft/onnxruntime-inference-examples</RepositoryUrl>
22+
<RepositoryType>git</RepositoryType>
23+
24+
<!-- Include symbols/source for better debugging experience -->
25+
<IncludeSymbols>true</IncludeSymbols>
26+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
27+
28+
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
29+
</PropertyGroup>
30+
31+
<ItemGroup>
32+
<!-- Ensure README is included in the package -->
33+
<None Include="readme.md" Pack="true" PackagePath="" />
34+
</ItemGroup>
35+
36+
<!-- Copy EP DLL to output directory under runtimes/*/native/ -->
37+
<ItemGroup Condition="'$(Runtime)' == 'win-x64'">
38+
<None Include="runtimes\win-x64\native\basic_plugin_ep.dll"
39+
Pack="true"
40+
PackagePath="runtimes/win-x64/native/basic_plugin_ep.dll"
41+
CopyToOutputDirectory="PreserveNewest"/>
42+
</ItemGroup>
43+
44+
<ItemGroup Condition="'$(Runtime)' == 'win-arm64'">
45+
<None Include="runtimes\win-arm64\native\basic_plugin_ep.dll"
46+
Pack="true"
47+
PackagePath="runtimes/win-arm64/native/basic_plugin_ep.dll"
48+
CopyToOutputDirectory="PreserveNewest"/>
49+
</ItemGroup>
50+
51+
</Project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Contoso.ML.OnnxRuntime.EP.Basic
2+
3+
Example plugin EP library for use with ONNX Runtime (Microsoft.ML.OnnxRuntime).
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Contoso.ML.OnnxRuntime.EP.Basic;
2+
using Microsoft.ML.OnnxRuntime;
3+
4+
class Program
5+
{
6+
static void Main()
7+
{
8+
string epLibPath = BasicEp.GetLibraryPath();
9+
string epRegistrationName = "basic_ep_registration";
10+
string epName = BasicEp.GetEpName();
11+
12+
var env = OrtEnv.Instance();
13+
env.RegisterExecutionProviderLibrary(epRegistrationName, epLibPath);
14+
Console.WriteLine($"Registered EP library: {epLibPath}");
15+
16+
try
17+
{
18+
// Find the OrtEpDevice for the EP
19+
OrtEpDevice? epDevice = null;
20+
foreach (var d in env.GetEpDevices())
21+
{
22+
if (string.Equals(epName, d.EpName, StringComparison.OrdinalIgnoreCase))
23+
{
24+
epDevice = d;
25+
}
26+
}
27+
28+
if (epDevice == null)
29+
{
30+
Console.Error.WriteLine($"ERROR: Unable to find OrtEpDevice with name {epName}");
31+
return;
32+
}
33+
Console.WriteLine($"Found OrtEpDevice for EP: {epName}");
34+
35+
// Create session with EP
36+
using var sessionOptions = new SessionOptions();
37+
sessionOptions.AppendExecutionProvider(env, [epDevice], new Dictionary<string, string> { });
38+
sessionOptions.AddSessionConfigEntry("session.disable_cpu_ep_fallback", "1"); // Don't run on CPU EP
39+
40+
string inputModelPath = Path.Combine(AppContext.BaseDirectory, "mul.onnx");
41+
Console.WriteLine($"Loading model: {inputModelPath}");
42+
43+
using var session = new InferenceSession(inputModelPath, sessionOptions);
44+
45+
// Run model
46+
float[] inputData = [1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f];
47+
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory<float>(inputData, [2, 3]);
48+
var inputValues = new List<OrtValue> { inputOrtValue, inputOrtValue }.AsReadOnly();
49+
var inputNames = new List<string> { "x", "y" }.AsReadOnly();
50+
using var runOptions = new RunOptions();
51+
52+
using var outputs = session.Run(runOptions, inputNames, inputValues, session.OutputNames);
53+
54+
Console.WriteLine($"Input: {string.Join(", ", inputData)}");
55+
Console.WriteLine($"Output: {string.Join(", ", outputs[0].GetTensorDataAsSpan<float>().ToArray())}");
56+
}
57+
finally
58+
{
59+
env.UnregisterExecutionProviderLibrary(epRegistrationName);
60+
}
61+
}
62+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Contoso.ML.OnnxRuntime.EP.Basic" Version="1.0.0" />
12+
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.23.2" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<Content Include="mul.onnx">
17+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
18+
</Content>
19+
</ItemGroup>
20+
21+
<!-- Consume nuget package via project reference during development to get live code changes -->
22+
<!-- <ItemGroup>
23+
<ProjectReference Include="..\Contoso.ML.OnnxRuntime.EP.Basic\Contoso.ML.OnnxRuntime.EP.Basic.csproj" />
24+
</ItemGroup> -->
25+
26+
</Project>
120 Bytes
Binary file not shown.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<packageSources>
4+
<add key="local_feed" value="./local_feed" />
5+
</packageSources>
6+
</configuration>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Basic Plugin Execution Provider with C#
2+
3+
## Contents
4+
- `Contoso.ML.OnnxRuntime.EP.Basic/`: Contains files for the basic plugin EP C# NuGet package. `BasicEp.cs` provides helper functions to get the EP library path and the EP name.
5+
- `SampleApp/`: Contains a sample C# application showing example usage of the basic plugin EP C# NuGet package.
6+
- `setup.bat`: Batch script to generate the NuGet package.
7+
8+
## Build Instructions
9+
This example currently only supports Windows x64 and Windows ARM64.
10+
11+
### Build the native plugin EP library
12+
13+
Follow instructions [here](../readme.md#build-instructions) to build the native library.
14+
15+
### Build the C\# NuGet Package
16+
17+
Set the environment variable `BASIC_PLUGIN_EP_LIBRARY_PATH` to the path to the native plugin EP shared library. E.g., `basic_plugin_ep.dll`.
18+
19+
Run `setup.bat` from this directory. Pass the build configuration (e.g., Release or Debug) as an argument.
20+
21+
```
22+
.\setup.bat Release
23+
```
24+
25+
The generated NuGet package will be copied to the `./local_feed` directory.
26+
27+
## Build and run the sample application
28+
29+
Build and run the sample application.
30+
31+
```
32+
dotnet build .\SampleApp\SampleApp.csproj -c Release
33+
dotnet run --project .\SampleApp\SampleApp.csproj -c Release
34+
```

0 commit comments

Comments
 (0)