DllImport 声明需显式指定调用约定、字符串编码、结构体布局及平台架构匹配,否则易致崩溃;回调委托须持久化防止 GC 回收,且所有类型必须严格对应 C++ 原型。

DllImport 声明写不对,程序直接崩溃
不是所有 C++ 函数都能直接 DllImport,尤其涉及字符串、结构体或回调时。C# 默认用 Unicode 字符集,而 C++ DLL 很可能按 ANSI 编译;函数调用约定(__cdecl 还是 __stdcall)不匹配会导致栈被破坏,现象是调用后立即 AccessViolationException 或静默崩溃。
- 务必在
DllImport属性里显式指定CallingConvention = CallingConvention.Cdecl(多数 C++ DLL 默认)或StdCall,别依赖默认值 - 字符串参数加
[MarshalAs(UnmanagedType.LPStr)]或[MarshalAs(UnmanagedType.LPWStr)],别裸写string - 导出函数必须用
extern "C" __declspec(dllexport)包裹,否则 C++ 编译器会名字修饰(name mangling),C# 找不到符号
结构体传参时内存布局错位
C# 和 C++ 对结构体默认内存对齐规则不同,尤其含 char、short、指针混排时,字段偏移一差,整个结构读写就全乱了,表现为数据截断、乱码、甚至访问非法地址。
- 在 C# 结构体上加
[StructLayout(LayoutKind.Sequential, Pack = 1)](Pack = 1最保险,和多数 C++ 默认一致) - 所有字段类型严格对应 C++ 原型:C++ 的
unsigned int对应 C# 的uint,不是int;void*对应IntPtr,不是object或byte* - 避免在结构体里放托管类型(如
string、class),要用IntPtr+ 手动Marshal操作
回调函数从 C++ 调回 C# 时崩得莫名其妙
把 C# 方法地址传给 C++ DLL 当回调,常见错误是委托实例被 GC 回收,C++ 再调用时指向已释放内存,抛 ExecutionEngineException 或直接进程终止。
- 回调委托变量必须在类级别长期持有(不能是局部变量),防止被 GC 清理
- 声明委托时加
[UnmanagedFunctionPointer(CallingConvention.Cdecl)],和 DLL 期望的调用约定一致 - 传给 C++ 的委托指针,需用
GCHandle.Alloc锁定或至少确保生命周期覆盖 DLL 使用期;简单场景下可将委托字段设为static并显式赋值
64 位程序加载 32 位 DLL 报错 BadImageFormatException
这是最常被忽略的兼容性问题:C# 项目平台目标(x86/x64/AnyCPU)必须和 C++ DLL 架构完全一致。选 AnyCPU 且勾了 “首选 32 位”,运行在 64 位系统上仍会以 32 位加载,此时若 DLL 是 64 位,就报这个错。
立即学习“C++免费学习笔记(深入)”;
- 检查 DLL 架构:用
dumpbin /headers xxx.dll看machine字段(8664是 x64,14C是 x86) - C# 项目属性 → “生成” → “平台目标” 必须严格匹配,不要信
AnyCPU的模糊性 - 调试时打开“仅我的代码”关闭,否则异常堆栈可能不显示真实 P/Invoke 层崩溃点
真正麻烦的从来不是声明那一行 DllImport,而是字符编码、内存所有权、线程上下文、架构对齐这四点里漏掉任意一个细节——它不会报错,只会等你上线跑两天后,在某个特定用户机器上突然哑火。


















