pinvoke调用c++函数需确保c abi兼容:c++侧用extern "c"导出,c#侧匹配callingconvention;字符串需显式编组;结构体须sequential+pack=1对齐;推荐nativelibrary动态加载。

用 PInvoke 调用 C++ 导出函数最直接,但必须确保 C++ 侧导出的是 C 风格符号
直接在 C# 里 DllImport 只能调用 C ABI 兼容的函数,不是所有 C++ 编译出来的 DLL 都能直接用。常见错误是 C++ 项目默认用 C++ 名字修饰(name mangling),导致 C# 找不到 EntryPoint,报错 System.EntryPointNotFoundException。
解决方法只有两个:
• C++ 源文件中用 extern "C" 包裹导出函数声明
• 编译时加 /EXPORT 或用 .def 文件显式导出符号(推荐前者)
示例 C++ 导出:
extern "C" __declspec(dllexport) int add(int a, int b) {
return a + b;
}
C# 对应调用:
[DllImport("native.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int add(int a, int b);
注意:CallingConvention.Cdecl 必须和 C++ 侧一致;若用 __stdcall,C++ 得写 extern "C" __declspec(dllexport) int __stdcall add(...),且 C# 改用 CallingConvention.StdCall。
传字符串要小心编码和内存管理,string 默认按 UnmanagedType.LPStr 处理
C# 的 string 是 Unicode(UTF-16),而多数 C++ 函数期望 ANSI 或 UTF-8 字符串。不显式指定 marshaling 容易出现乱码或崩溃。
- 如果 C++ 接收
const char*(ANSI):在 C# 中用[MarshalAs(UnmanagedType.LPStr)],并确保系统代码页兼容 - 如果 C++ 接收
const char*(UTF-8):需手动用Encoding.UTF8.GetBytes()转字节数组,再传byte*,C++ 侧不能直接用std::string构造(会析构托管内存) - 如果 C++ 返回
char*(如const char* get_msg()):C# 侧必须用IntPtr接收,再用Marshal.PtrToStringAnsi()或Marshal.PtrToStringUTF8()(.NET 5+)转换,否则可能读到已释放内存
结构体跨语言传递必须用 [StructLayout(LayoutKind.Sequential)] 显式控制布局
C++ 和 C# 默认结构体对齐、填充规则不同,尤其含 bool、short、指针字段时,二进制布局一错,整个结构读出来就是垃圾值。
关键点:
- 必须加
[StructLayout(LayoutKind.Sequential, Pack = 1)](Pack = 1最保险,避免隐式填充) - C++ 侧对应结构要用
#pragma pack(1)或__attribute__((packed))(GCC/Clang)对齐 - 字段类型严格对应:C# 的
bool是 1 字节,但 C++ 的bool大小不保证,建议统一用byte/uint8_t - 含数组字段时,C# 用
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)],不能用int[]
.NET Core/.NET 5+ 推荐用 NativeLibrary 替代静态 DllImport,便于运行时加载和调试
硬编码 DllImport("xxx.dll") 在 Linux/macOS 上路径失败,在容器里容易找不到文件,且无法捕获加载失败细节。
改用动态加载更可控:
if (!NativeLibrary.TryLoad("native", out IntPtr lib))
throw new DllNotFoundException("native not found");
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), (assembly, library, assemblyDirectory) => {
if (library == "native") return lib;
return null;
});
这样既能统一管理 native 库路径,又能配合 ldd(Linux)或 otool -L(macOS)查依赖缺失,比靠异常堆栈定位快得多。
真正麻烦的从来不是“能不能调”,而是“为什么传进去的值变了”“为什么返回的字符串是空的”“为什么结构体第 3 个字段永远为 0”——这些全取决于 C/C++ 侧是否真正按 ABI 合规导出,以及 C# 侧有没有逐字节对齐、编码、生命周期都盯死。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










