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

.NET 6.0中UnmanagedExports、DllExport及NativeAOT失效问题求助

问题描述

原基于.NET Framework的应用使用Robert Giesecke的UnmanagedExport工具,通过[DllExport]特性向原生语言导出函数,运行正常。升级至.NET 6.0后出现以下问题:

  • 函数可通过dumpbin查看到导出信息,原生程序能加载DLL并找到函数地址,但调用时抛出异常:Unhandled exception at 0x00007FFB928CCD29 (KernelBase.dll) in NativeExportTest.exe: 0xE0434352
  • 切换回.NET Framework或.NET Standard则恢复正常
  • 已尝试UnmanagedExports.Repack、UnmanagedExports.Repack.Upgrade、DllExport等包,均无效
  • NativeAOT虽能运行,但不支持.NET 6.0的全部特性
  • .NET 6.0的[UnmanagedCallersOnlyAttribute]因目标原生语言为运行时编译的C++,疑似不支持回调
  • 启用混合调试后,加载前出现新异常:Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified

相关代码

C#项目文件

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Platforms>AnyCPU;x64</Platforms>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="UnmanagedExports.Repack.Upgrade" Version="1.2.1" />
  </ItemGroup>
</Project>

C#导出类

public class Class1
{
    [DllExport]
    public static int _add(int a, int b)
    {
        return a + b;
    }
}

C++测试代码

const TCHAR* pemodule = _T("path to release dll");
HMODULE lib = LoadLibrary(pemodule);
typedef int( *_add)(int a, int b);
auto pAdd = (_add)GetProcAddress(lib, "_add");
int c = pAdd(1,2); // 此处抛出异常

解决方案

方案1:使用NetDllExport适配.NET 6+

这是专门为.NET Core/.NET 5+设计的DLL导出工具,替代旧版UnmanagedExport:

  1. 安装NuGet包NetDllExport(选择适配.NET 6的最新版本)
  2. 修改项目文件指定平台和运行时标识符,确保依赖正确复制
  3. 调整导出特性的调用约定,匹配原生程序需求

修改后的C#项目文件

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Platforms>x64</Platforms> <!-- 避免AnyCPU位数不匹配问题 -->
    <RuntimeIdentifier>win-x64</RuntimeIdentifier> <!-- 确保复制运行时依赖 -->
    <SelfContained>false</SelfContained> <!-- 按需设置,true则打包所有依赖 -->
    <DllExportCallingConvention>Cdecl</DllExportCallingConvention> <!-- 统一调用约定 -->
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="NetDllExport" Version="1.0.11" />
  </ItemGroup>
</Project>

修改后的C#导出代码

using NetDllExport;

public class Class1
{
    [DllExport("_add", CallingConvention = CallingConvention.Cdecl)]
    public static int Add(int a, int b)
    {
        return a + b;
    }
}

方案2:修复System.Runtime加载问题(适配旧方案)

若坚持使用旧版UnmanagedExport衍生包,需解决依赖加载问题:

  • 放弃AnyCPU编译,指定与原生程序一致的平台(x64/x86)
  • 在项目文件中添加<RuntimeIdentifier>win-x64</RuntimeIdentifier>,确保编译时复制所有.NET运行时依赖到输出目录
  • 非自包含部署时,确保目标机器已安装.NET 6 Desktop Runtime(Windows平台)
  • 检查输出目录,若缺失System.Runtime.dll等依赖,手动从.NET安装目录复制

方案3:COM互操作替代(无需回调场景)

如果原生程序支持COM调用,可将C#类注册为COM组件:

  1. 在C#类上标记[ComVisible(true)]并生成唯一GUID
  2. 调试时启用项目属性的"Register for COM Interop",或用regasm工具注册组件
  3. 原生C++通过COM接口调用方法

C# COM代码示例

using System.Runtime.InteropServices;

[ComVisible(true)]
[Guid("F89D2E1A-1234-5678-90AB-CDEF01234567")]
public interface ICalculator
{
    int Add(int a, int b);
}

[ComVisible(true)]
[Guid("F89D2E1B-1234-5678-90AB-CDEF01234567")]
public class Calculator : ICalculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

C++ COM调用示例

#include <windows.h>
#include <objbase.h>

// 需导入tlb文件或手动声明接口GUID
const IID IID_ICalculator = {0xF89D2E1A, 0x1234, 0x5678, {0x90, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67}};
const CLSID CLSID_Calculator = {0xF89D2E1B, 0x1234, 0x5678, {0x90, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67}};

int main()
{
    CoInitialize(NULL);
    ICalculator* pCalc = nullptr;
    HRESULT hr = CoCreateInstance(CLSID_Calculator, NULL, CLSCTX_INPROC_SERVER, IID_ICalculator, (void**)&pCalc);
    if (SUCCEEDED(hr))
    {
        int result = pCalc->Add(1, 2);
        pCalc->Release();
    }
    CoUninitialize();
    return 0;
}

关键注意事项
  • 必须保证.NET DLL与原生程序的位数一致(x64/x86),否则会出现加载或调用异常
  • .NET 6+非自包含部署依赖目标机器的.NET Runtime环境,自包含部署则需打包所有依赖
  • 导出函数的调用约定(Cdecl/StdCall)必须与原生程序匹配,否则会导致栈损坏或异常

内容的提问来源于stack exchange,提问作者clamchoda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 20:40:10