.NET 8手动映射AOT编译DLL调用UnmanagedCallersOnly方法报错
非AOT编译的.NET 8调用AOT DLL的UnmanagedCallersOnly方法报错的解决办法
问题现象
手动映射调用AOT编译的.NET DLL时,无论是创建委托还是使用delegate* unmanaged指向函数指针并调用,都会抛出致命异常:
Fatal error. Invalid Program: attempted to call a UnmanagedCallersOnly method from managed code.
该调用在.NET Framework 4.8、AOT编译的.NET 8程序中可正常执行;使用DllImport或LoadLibrary+GetProcAddress方式在.NET 8中调用也能正常运行,但默认编译的.NET 8代码(非AOT)无论Debug/Release模式都会报错。
调用代码示例1(委托方式)
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate long AddNumbersDelegate(int a, int b); public static void Main() { Memory Dll = new Memory(File.ReadAllBytes(@"C:\VsProjects\ClassLibrary1\bin\Release\net8.0\publish\win-x64\ClassLibrary1.dll")); AddNumbersDelegate delegat = (AddNumbersDelegate)Dll.GetDelegateFromFuncName("Sum", typeof(AddNumbersDelegate)); long val = delegat(40, 2); // 此处抛出致命错误 Console.WriteLine("sum result:" + val); Console.ReadKey(); }
调用代码示例2(函数指针方式)
public static void Main() { Memory Dll = new Memory(File.ReadAllBytes(@"C:\VsProjects\ClassLibrary1\bin\Release\net8.0\publish\win-x64\ClassLibrary1.dll")); IntPtr ptr = (IntPtr)Dll.GetPtrFromFuncName("Sum"); delegate* unmanaged[Cdecl]<int, int, long> delegat = (delegate* unmanaged[Cdecl]<int, int, long>)ptr; long val = delegat(40, 2); // 此处抛出致命错误 Console.WriteLine("sum result:" + val); Console.ReadKey(); }
AOT编译的DLL代码
public class Class1 { [UnmanagedCallersOnly(EntryPoint = "Sum", CallConvs = new[] { typeof(CallConvCdecl)})] public static long Sum(int a, int b) { return a + b; } }
原因分析
[UnmanagedCallersOnly]特性的设计目标是让托管方法仅被非托管代码调用,CoreCLR(非AOT的.NET运行时)会对标记该特性的方法添加校验逻辑:一旦检测到调用来自托管代码,直接触发无效程序错误,强制遵循设计意图。- AOT编译的程序生成原生代码,跳过了CoreCLR的这层校验;.NET Framework本身没有该特性的校验逻辑,因此都能正常调用。
DllImport或LoadLibrary+GetProcAddress的方式会让运行时判定调用来自非托管上下文,从而绕过校验。
解决方案
方案1:改用官方推荐的互调用方式(优先选择)
使用DllImport或者LoadLibrary+GetProcAddress+Marshal.GetDelegateForFunctionPointer的组合,这是官方认可的调用非托管导出函数的方式,能绕过CoreCLR的校验:
方式1:DllImport
[DllImport("ClassLibrary1.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "Sum")] private static extern long Sum(int a, int b); public static void Main() { long val = Sum(40, 2); Console.WriteLine("sum result:" + val); Console.ReadKey(); }
方式2:LoadLibrary + GetProcAddress
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate long AddNumbersDelegate(int a, int b); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32.dll", CharSet = CharSet.Ansi)] private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); [DllImport("kernel32.dll")] private static extern bool FreeLibrary(IntPtr hModule); public static void Main() { IntPtr dllHandle = LoadLibrary(@"C:\VsProjects\ClassLibrary1\bin\Release\net8.0\publish\win-x64\ClassLibrary1.dll"); if (dllHandle == IntPtr.Zero) { Console.WriteLine("加载DLL失败"); Console.ReadKey(); return; } IntPtr funcPtr = GetProcAddress(dllHandle, "Sum"); if (funcPtr == IntPtr.Zero) { Console.WriteLine("获取函数指针失败"); FreeLibrary(dllHandle); Console.ReadKey(); return; } var sumDelegate = Marshal.GetDelegateForFunctionPointer<AddNumbersDelegate>(funcPtr); long val = sumDelegate(40, 2); Console.WriteLine("sum result:" + val); FreeLibrary(dllHandle); Console.ReadKey(); }
方案2:修改AOT DLL的导出逻辑(需有权限修改DLL源码)
如果可以调整AOT DLL的代码,移除[UnmanagedCallersOnly]特性,改用传统的导出方式(比如借助[DllExport] NuGet包,注意适配.NET 8 AOT环境),或者将方法改为普通静态方法后再导出。不过这个方案需要调整DLL的编译流程,不如方案1便捷。
方案3:将调用程序改为AOT编译
如果业务场景允许,把调用方的.NET 8程序也用Native AOT编译,这样运行时会跳过CoreCLR的校验逻辑,就能正常使用手动映射的方式调用目标函数。
内容的提问来源于stack exchange,提问作者Илья
相关产品推荐
相关产品推荐

