NativeAOT ILC编译x86时导出符号带@后缀致链接错误问题
x86平台下.NET导出函数与C++链接符号不匹配问题
问题场景
C#导出函数至DLL
使用[UnmanagedCallersOnly]特性导出函数:
public class Symbol { [UnmanagedCallersOnly(EntryPoint = "Console_Symbol_ReverseColors")] public static void ReverseColors(nint sym) { // 业务逻辑 } }
C++导入函数
在C++中声明导入该函数:
extern "C" __declspec(dllimport) void Console_Symbol_ReverseColors(void*);
编译与错误情况
- 编译x64版本时,使用命令:
链接C++代码正常通过。dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r win-x64 -c Release - 编译x86版本时,使用命令:
链接C++代码时触发LNK2019未定义引用错误:dotnet publish -p:NativeLib=Shared -p:SelfContained=true -r win-x86 -c ReleaseConsole.obj : error LNK2019: unresolved external symbol __imp__Console_Symbol_ReverseColors referenced in function "xxx"
通过dumpbin查看生成的lib文件符号:
- x64版本符号:
__imp_Console_Symbol_ReverseColors - x86版本符号:
__imp__Console_Symbol_ReverseColors@4
原因解析
这是Windows平台x86与x64调用约定的差异导致的:
- x64平台:Windows规定了统一的调用约定(本质是fastcall),所有函数导出时不会添加参数字节数的后缀修饰,因此符号格式统一。
- x86平台:.NET的
[UnmanagedCallersOnly]特性默认使用stdcall调用约定,这种约定会在导出符号后添加@N后缀(N是参数总字节数,这里参数是一个指针,占4字节,所以是@4)。而C++代码中未指定调用约定时,默认使用cdecl,编译器会尝试查找不带@N后缀的符号,两者不匹配就触发了链接错误。
修复方案
方案1:修改C++导入声明,匹配stdcall调用约定
在C++函数声明中显式指定__stdcall(或使用跨平台的WINAPI宏,它在x86下映射为__stdcall,x64下映射为统一调用约定):
// 方式1:直接指定__stdcall extern "C" __declspec(dllimport) void __stdcall Console_Symbol_ReverseColors(void*); // 方式2:使用WINAPI宏,兼容x86/x64 extern "C" __declspec(dllimport) void WINAPI Console_Symbol_ReverseColors(void*);
方案2:修改C#导出时指定Cdecl调用约定
在[UnmanagedCallersOnly]中通过CallConvs参数指定使用cdecl调用约定,让x86下导出的符号不带@N后缀,匹配C++默认的调用约定:
public class Symbol { [UnmanagedCallersOnly(EntryPoint = "Console_Symbol_ReverseColors", CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] public static void ReverseColors(nint sym) { // 业务逻辑 } }
内容的提问来源于stack exchange,提问作者Stas Badzi
相关产品推荐
相关产品推荐

