C#调用C++ DLL需用DllImport声明,C++须用extern "C"和__declspec(dllexport)导出C风格函数,平台、调用约定、路径、字符串/结构体内存布局必须严格一致,否则引发DllNotFound、EntryPointNotFound或访问冲突等错误。
DllImport 声明函数,不是“引用”或“添加引用”你不能像引用 .NET 程序集那样直接“添加引用”C++ DLL。C# 调用 C++ DLL 的核心机制是平台调用(P/Invoke),靠 DllImport 特性显式声明外部函数签名。如果直接把 C++ DLL 拖进项目或右键“添加引用”,会失败——它不是托管程序集。
关键前提:C++ DLL 必须导出 C 风格函数(即用 extern "C" 和 __declspec(dllexport)),否则 C# 找不到符号(常见错误:System.EntryPointNotFoundException)。
extern "C" __declspec(dllexport) int add(int a, int b);
AnyCPU 默认可能失败,建议显式设为 x64 或 x86)PATH 环境变量路径,或用 SetDllDirectory 指定DllImport 的路径和调用约定不能写错路径写相对路径(如 "MyNative.dll")比绝对路径更安全;调用约定(CallingConvention)必须和 C++ 导出函数一致,默认是 __cdecl,但 Windows API 风格常用 __stdcall。写错会导致栈失衡、崩溃或返回值异常(比如始终返回 0)。
常见组合:
__declspec(dllexport) int func(...); → C# 用 CallingConvention = CallingConvention.Cdecl(默认可省略)__declspec(dllexport) int __stdcall func(...); → C# 必须显式写 CallingConvention = CallingConvention.StdCall
.dll 后缀(
DllImport("MyNative") 即可,运行时自动补)C# 和 C++ 对字符串、指针、结构体的默认处理完全不同。直接传 string 或 int[] 很容易引发访问冲突或乱码。
[MarshalAs(UnmanagedType.LPStr)] 或 [MarshalAs(UnmanagedType.LPWStr)] 明确编码,C++ 接收 const char* 或 const wchar_t*
[StructLayout(LayoutKind.Sequential)],字段顺序、对齐(Pack=1)要和 C++ 一致;避免使用 string 字段,改用固定长度 char[256] + [MarshalAs]
StringBuilder 并预先 .Capacity,而非 string
[DllImport("MyNative.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetString([MarshalAs(UnmanagedType.LPStr)] StringBuilder buffer, int bufferSize);不是所有报错都提示“找不到 DLL”——很多崩溃发生在调用瞬间,没堆栈或只有 AccessViolationException。
DllNotFoundException:DLL 文件根本没找到。用 Process Monitor 过滤进程名,看它尝试加载了哪些路径EntryPointNotFoundException:函数名不匹配。用 dumpbin /exports MyNative.dll 查真实导出名(注意是否有前导下划线或 @ 后缀)真正麻烦的从来不是“怎么写第一行 DllImport”,而是让两边的数据解释方式严丝合缝。一个字节对齐差异、一个字符串编码误判,就足以让调用看起来“成功”却返回垃圾值。