You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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,提问作者Илья

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:32:03