PInvoke封送C++ DLL至C#时的结构体传递错误咨询
嘿,第一次搞C++和C#的跨语言互操作,踩PInvoke封送的坑太正常了!咱们一步步来把这个问题解决掉:
先把C++侧的基础工作做扎实
首先你的结构体用了#pragma pack(push,2)来指定2字节对齐,这块没问题,但要注意几个细节:
- 别忘了用
#pragma pack(pop)恢复默认对齐规则,不然会影响你DLL里其他代码的内存布局 - 导出函数的声明要完整且符合C风格(避免C++名字修饰),比如你的
MYCVAPI应该是定义了__declspec(dllexport),同时最好显式指定调用约定(推荐__stdcall,和C# PInvoke默认匹配),完整代码大概是这样:#pragma pack(push, 2) typedef struct { unsigned int width; unsigned int height; // 注释掉的字段以后启用时,也要同步考虑C#侧的封送 } Sframe; #pragma pack(pop) extern "C" __declspec(dllexport) Sframe __stdcall findPossiblePlates(/* 这里补全你的函数参数,比如输入图像的相关参数 */);
C#侧的结构体必须严格匹配内存布局
C#的结构体必须和C++的内存布局完全一致,否则肯定会出封送错误:
- 用
[StructLayout(LayoutKind.Sequential, Pack = 2)]来对应C++的2字节对齐 - 字段类型要一一对应:C++的
unsigned int对应C#的uint - 示例代码:
[StructLayout(LayoutKind.Sequential, Pack = 2)] public struct Sframe { public uint width; public uint height; // 后续添加字段时,要同步匹配C++侧的类型和对齐 }
正确声明PInvoke函数
这一步最容易踩调用约定和函数名的坑:
- 确保DLL的路径正确,要么把DLL放到C#项目的输出目录(比如bin/Debug),要么在
DllImport里写绝对路径 - 函数名要和C导出的完全一致(因为用了
extern "C",不会被C编译器改名) - 调用约定要和C侧匹配:如果C用的是
__stdcall,C#可以不用显式写(默认就是StdCall);如果是__cdecl,就要在DllImport里加CallingConvention = CallingConvention.Cdecl - 示例声明:
[DllImport("YourDllFileName.dll")] public static extern Sframe findPossiblePlates(/* 这里补全和C++侧对应的参数 */);
快速排查封送错误的小技巧
- 对比结构体大小:在C++里打印
sizeof(Sframe),在C#里打印Marshal.SizeOf(typeof(Sframe)),两个值必须完全相等,不相等的话肯定是布局或类型不对 - 架构要一致:C#项目的目标平台(x86/x64)必须和C++ DLL的编译架构完全一致,不然会出现加载失败或者奇怪的封送错误
- 以后启用指针字段(比如你注释的
char*或unsigned char*)时,C#里要用IntPtr来对应,同时要注意内存的分配和释放(比如用Marshal.StringToHGlobalAnsi处理字符串,用Marshal.AllocHGlobal分配内存,用完一定要释放避免泄漏)
内容的提问来源于stack exchange,提问作者Athinodoros Sgouromallis




