Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 132 additions & 8 deletions csharp/src/Microsoft.ML.OnnxRuntime/NativeMethods.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using static Microsoft.ML.OnnxRuntime.NativeMethods;

Expand Down Expand Up @@ -474,6 +475,12 @@ internal static class NativeMethods

static NativeMethods()
{
#if !NETSTANDARD2_0 && !__ANDROID__ && !__IOS__
// Register a custom DllImportResolver to handle platform-specific library loading.
// Replaces default resolution specifically on Windows for case-sensitivity.
NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, DllImportResolver);
#endif

#if NETSTANDARD2_0
IntPtr ortApiBasePtr = OrtGetApiBase();
OrtApiBase ortApiBase = (OrtApiBase)Marshal.PtrToStructure(ortApiBasePtr, typeof(OrtApiBase));
Expand Down Expand Up @@ -847,7 +854,7 @@ static NativeMethods()
api_.CreateSyncStreamForEpDevice,
typeof(DOrtCreateSyncStreamForEpDevice));

OrtSyncStream_GetHandle =
OrtSyncStream_GetHandle =
(DOrtSyncStream_GetHandle)Marshal.GetDelegateForFunctionPointer(
api_.SyncStream_GetHandle,
typeof(DOrtSyncStream_GetHandle));
Expand All @@ -872,11 +879,127 @@ internal class NativeLib
// Define the library name required for iOS
internal const string DllName = "__Internal";
#else
// Note: the file name in ONNX Runtime nuget package must be onnxruntime.dll instead of onnxruntime.DLL(Windows filesystem can be case sensitive)
internal const string DllName = "onnxruntime.dll";
// For desktop platforms (including .NET Standard 2.0), we use the simple name
// to allow .NET's automatic platform-specific resolution (lib*.so, lib*.dylib, *.dll).
// For .NET Core 3.0+, case-sensitivity on Windows is handled by DllImportResolver.
internal const string DllName = "onnxruntime";
#endif
}

#if !NETSTANDARD2_0 && !__ANDROID__ && !__IOS__
/// <summary>
/// Custom DllImportResolver to handle platform-specific library loading.
/// On Windows, it explicitly loads the library with a lowercase .dll extension to handle
/// case-sensitive filesystems.
/// </summary>
private static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName == NativeLib.DllName || libraryName == OrtExtensionsNativeMethods.ExtensionsDllName)
{
string mappedName = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Explicitly load with .dll extension to avoid issues where the OS might try .DLL
mappedName = libraryName + ".dll";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// Explicitly load with .so extension and lib prefix
mappedName = "lib" + libraryName + ".so";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// Explicitly load with .dylib extension and lib prefix
mappedName = "lib" + libraryName + ".dylib";
}

if (mappedName != null)
{
// 1. Try default loading (name only)
if (NativeLibrary.TryLoad(mappedName, assembly, searchPath, out IntPtr handle))
{
return handle;
}

// 2. Try relative to assembly location (look into runtimes subfolders)
string assemblyLocation = null;
try { assemblyLocation = assembly.Location; } catch { }
if (!string.IsNullOrEmpty(assemblyLocation))
{
string assemblyDir = System.IO.Path.GetDirectoryName(assemblyLocation);
string rid = RuntimeInformation.RuntimeIdentifier;

// Probe the specific RID first, then common fallbacks for the current OS
string[] ridsToTry;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
ridsToTry = new[] { rid, "win-x64", "win-arm64" };
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
ridsToTry = new[] { rid, "linux-x64", "linux-arm64" };
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// We no longer provide osx-x64 in official package since 1.24.
// However, we keep it in the list for build-from-source users.
ridsToTry = new[] { rid, "osx-arm64", "osx-x64" };
}
else
{
ridsToTry = new[] { rid };
}

foreach (var tryRid in ridsToTry)
{
string probePath = System.IO.Path.Combine(assemblyDir, "runtimes", tryRid, "native", mappedName);
if (System.IO.File.Exists(probePath) && NativeLibrary.TryLoad(probePath, assembly, searchPath, out handle))
{
LogLibLoad($"[DllImportResolver] Loaded {mappedName} from: {probePath}");
return handle;
}
}
}

// 3. Try AppContext.BaseDirectory as a fallback
string baseDir = AppContext.BaseDirectory;
if (!string.IsNullOrEmpty(baseDir))
{
string probePath = System.IO.Path.Combine(baseDir, mappedName);
if (NativeLibrary.TryLoad(probePath, assembly, searchPath, out handle))
{
LogLibLoad($"[DllImportResolver] Loaded {mappedName} from: {probePath}");
return handle;
}

string rid = RuntimeInformation.RuntimeIdentifier;
probePath = System.IO.Path.Combine(baseDir, "runtimes", rid, "native", mappedName);
if (NativeLibrary.TryLoad(probePath, assembly, searchPath, out handle))
{
LogLibLoad($"[DllImportResolver] Loaded {mappedName} from: {probePath}");
return handle;
}
}

LogLibLoad($"[DllImportResolver] Failed loading {mappedName} (RID: {RuntimeInformation.RuntimeIdentifier}, Assembly: {assemblyLocation})");

}
}

// Fall back to default resolution
return IntPtr.Zero;
}

private static void LogLibLoad(string message)
{
System.Diagnostics.Trace.WriteLine(message);
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ORT_LOADER_VERBOSITY")))
{
Console.WriteLine(message);
}
}
#endif

[DllImport(NativeLib.DllName, CharSet = CharSet.Ansi)]
#if NETSTANDARD2_0
public static extern IntPtr OrtGetApiBase();
Expand Down Expand Up @@ -2644,7 +2767,7 @@ public delegate void DOrtAddKeyValuePair(IntPtr /* OrtKeyValuePairs* */ kvps,
byte[] /* const char* */ value);

/// <summary>
/// Get the value for the provided key.
/// Get the value for the provided key.
/// </summary>
/// <returns>Value. Returns IntPtr.Zero if key was not found.</returns>
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
Expand Down Expand Up @@ -2767,7 +2890,7 @@ out IntPtr /* OrtSyncStream** */ stream
// Auto Selection EP registration and selection customization

/// <summary>
/// Register an execution provider library.
/// Register an execution provider library.
/// The library must implement CreateEpFactories and ReleaseEpFactory.
/// </summary>
/// <param name="env">Environment to add the EP library to.</param>
Expand Down Expand Up @@ -2952,9 +3075,10 @@ internal static class OrtExtensionsNativeMethods
#elif __IOS__
internal const string ExtensionsDllName = "__Internal";
#else
// For desktop platforms, explicitly specify the DLL name with extension to avoid
// issues on case-sensitive filesystems. See NativeLib.DllName for detailed explanation.
internal const string ExtensionsDllName = "ortextensions.dll";
// For desktop platforms, use the simple name to allow .NET's
// automatic platform-specific resolution (lib*.so, lib*.dylib, *.dll).
// Case-sensitivity on Windows is handled by DllImportResolver.
internal const string ExtensionsDllName = "ortextensions";
#endif

[DllImport(ExtensionsDllName, CharSet = CharSet.Ansi,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,29 @@ private static Dictionary<string, string> GetSkippedModels(DirectoryInfo modelsD
skipModels["VGG 16-fp32"] = "bad allocation";
}

// The following models are from onnx repo and fail on MacOS nuget test pipeline.
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var macOSSkips = new[]
{
"test_castlike_FLOAT_to_STRING_expanded",
"test_castlike_FLOAT_to_BFLOAT16_expanded",
"test_castlike_BFLOAT16_to_FLOAT",
"test_cast_FLOAT_to_STRING",
"test_castlike_FLOAT_to_BFLOAT16",
"test_castlike_STRING_to_FLOAT_expanded",
"test_castlike_STRING_to_FLOAT",
"test_cast_STRING_to_FLOAT",
"test_castlike_BFLOAT16_to_FLOAT_expanded",
"test_cast_BFLOAT16_to_FLOAT",
"test_castlike_FLOAT_to_STRING"
};
foreach (var model in macOSSkips)
{
skipModels[model] = "Skipped on macOS due to flakes or lack of support";
}
}

return skipModels;
}

Expand Down Expand Up @@ -934,6 +957,7 @@ public void TestPretrainedModelsWithOrtValue(string opsetDir, string modelName)
[MemberData(nameof(GetSkippedModelForTest), Skip = "Skipped due to Error, please fix the error and enable the test")]
private void TestPreTrainedModels(string opsetDir, string modelName, bool useOrtValueAPIs = false)
{

var opsetDirInfo = new DirectoryInfo(opsetDir);
var opset = opsetDirInfo.Name;
string onnxModelFileName = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,18 @@ stages:

- template: nuget/templates/test_macos.yml
parameters:
AgentPool: macOS-14
AgentPool: 'AcesShared'
UseHostedVmImage: 'false'
PoolDemands: 'ImageOverride -equals ACES_VM_SharedPool_Sequoia'
ArtifactSuffix: 'CPU'

- template: nodejs/templates/test_macos.yml
parameters:
AgentPool: 'AcesShared'
UseHostedVmImage: 'false'
PoolDemands: 'ImageOverride -equals ACES_VM_SharedPool_Sequoia'
StageSuffix: 'MacOS_ARM64'

- template: nodejs/templates/test_win.yml
parameters:
AgentPool: 'onnxruntime-Win-CPU-VS2022-Latest'
Expand All @@ -117,10 +126,6 @@ stages:
AgentPool: 'onnxruntime-Ubuntu2204-AMD-CPU'
StageSuffix: 'Linux_CPU_x64'

- template: nodejs/templates/test_macos.yml
parameters:
StageSuffix: 'macOS_CPU_x64'

- template: nuget/templates/test_win.yml
parameters:
AgentPool: 'onnxruntime-Win2022-GPU-A10'
Expand Down Expand Up @@ -225,7 +230,7 @@ stages:
- checkout: self
clean: true
submodules: none

- download: build
artifact: 'Windows_Packaging_tensorrt_build_artifacts'
displayName: 'Download Windows GPU Packages Build'
Expand All @@ -246,7 +251,7 @@ stages:
versionSpec: "17"
jdkArchitectureOption: x64
jdkSourceOption: 'PreInstalled'

- task: PythonScript@0
displayName: 'Update CTest Path References'
inputs:
Expand Down
12 changes: 10 additions & 2 deletions tools/ci_build/github/azure-pipelines/nodejs/templates/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ steps:


- task: PowerShell@2
displayName: 'Move Artifact Directory'
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
displayName: 'Move Artifact Directory (Windows)'
inputs:
targetType: 'inline'
script: |
Move-Item -Path "$(Pipeline.Workspace)/build/NPM_packages" -Destination "$(Build.BinariesDirectory)/nodejs-artifact"

- task: CmdLine@2
condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT'))
displayName: 'Move Artifact Directory (POSIX)'
inputs:
script: |
mv "$(Pipeline.Workspace)/build/NPM_packages" "$(Build.BinariesDirectory)/nodejs-artifact"

- script: mkdir e2e_test
workingDirectory: '$(Build.BinariesDirectory)'

Expand All @@ -38,4 +46,4 @@ steps:
npm init -y
npm install $(NpmPackageFilesForTest) --onnxruntime-node-install-cuda=skip
node -p "require('onnxruntime-node')"
workingDirectory: '$(Build.BinariesDirectory)/e2e_test'
workingDirectory: '$(Build.BinariesDirectory)/e2e_test'
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
parameters:
StageSuffix: ''
AgentPool : 'macOS-15'
UseHostedVmImage: 'true'
PoolDemands: ''

stages:
- stage: Nodejs_Test_MacOS_${{ parameters.StageSuffix }}
dependsOn:
Expand All @@ -11,7 +15,12 @@ stages:
clean: all
timeoutInMinutes: 120
pool:
vmImage: 'macOS-15'
${{ if eq(parameters.UseHostedVmImage, 'true') }}:
vmImage: ${{ parameters.AgentPool }}
${{ else }}:
name: ${{ parameters.AgentPool }}
${{ if ne(parameters.PoolDemands, '') }}:
demands: ${{ parameters.PoolDemands }}

variables:
- name: OnnxRuntimeBuildDirectory
Expand Down
Loading
Loading