能调通不等于调得对;dllimport声明错一个参数会导致栈溢出、垃圾值或静默失败,首要陷阱是callingconvention不匹配——c++默认__cdecl而c#默认stdcall,必须显式指定callingconvention.cdecl并用dumpbin验证导出名。

能调通不等于调得对;DllImport 声明错一个参数,运行时就可能栈溢出、返回垃圾值,甚至静默失败。
CallingConvention 不匹配是第一大坑
绝大多数 C/C++ DLL 默认用 __cdecl(参数从右往左压栈,调用方清栈),但 C# 的 [DllImport] 默认走 Winapi(即 StdCall)。不显式指定,轻则 System.AccessViolationException,重则程序直接崩溃。
- 确认 C++ 端导出方式:查源码是否含
__cdecl或__stdcall;若没写,默认是__cdecl - C# 端必须显式写:
[DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)] - Windows 系统 DLL(如
user32.dll)多用StdCall,但自研或跨平台 C DLL 几乎全是Cdecl - 用
dumpbin /exports NativeDll.dll查看导出符号,若函数名末尾带@n(如Add@8),说明是StdCall;无后缀则是Cdecl
字符串传参不封送,中文直接变乱码或崩
C++ 函数若声明为 void Log(const char* msg),C# 传 "你好" 会按 UTF-16 解释成两字节 ASCII,结果只取前两个字节——你看到的是 或空指针异常。
-
const char*对应[MarshalAs(UnmanagedType.LPStr)] string,且需加CharSet = CharSet.Ansi -
const wchar_t*对应[MarshalAs(UnmanagedType.LPWStr)] string,配合CharSet = CharSet.Unicode - 避免直接传
string且不加MarshalAs——这是最常被忽略的隐性错误 - 若函数内部修改字符串(如
char* outBuf),改用StringBuilder并预设Capacity
DLL 路径和加载时机不可靠
写 [DllImport("MyLib.dll")] 看似简单,但 Windows 加载器按固定顺序搜索路径:exe 目录 → 当前工作目录 → PATH 环境变量 → 系统目录。当前工作目录(Environment.CurrentDirectory)在 GUI 或服务场景下极易被第三方库篡改。
- 绝对路径最稳:
[DllImport(@"C:\MyApp\libs\MyLib.dll")](注意 @ 符号) - 相对路径风险高:除非你完全控制启动方式(如用
Process.Start指定WorkingDirectory) - 动态加载更可控:
LoadLibrary+GetProcAddress可捕获ERROR_FILE_NOT_FOUND,比静态绑定的DllNotFoundException更早暴露问题 - .NET 6+ 支持
NativeLibrary.Load,但仅限于已知路径,仍不解决“找不见”的根本问题
函数签名大小写与导出名必须一字不差
C++ 编译器导出函数名受编译选项影响极大:extern "C" 控制是否修饰,__declspec(dllexport) 控制是否导出,而 /EXPORT 链接器选项还能强行改名。C# 里写错一个字母,就是 System.EntryPointNotFoundException。
- 用
dumpbin /exports MyLib.dll看真实导出名,不是头文件里的名字 - 若 C++ 端用了
extern "C",导出名就是纯函数名(如Add);否则可能是?Add@@YAHJJ@Z(C++ name mangling) - 用
EntryPoint = "RealExportName"显式指定,绕过自动匹配:[DllImport("MyLib.dll", EntryPoint = "AddInts")] - 32/64 位平台必须严格一致:x64 程序加载 x86 DLL 必报
BadImageFormatException,且异常堆栈不提示位数问题
真正难的不是写出能跑的代码,而是让同一份 DLL 在不同机器、不同启动路径、不同 .NET 版本下都稳定加载并正确交换数据——这要求你亲手验证导出名、亲手测字符串编码、亲手检查平台目标,而不是依赖“它以前能跑”。










