You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动