C#调用C++ DLL传入cv::Mat参数出现编组错误该如何解决?
问题背景
你遇到的问题是C++ DLL导出函数使用了C++专属类型作为跨语言调用的参数和返回值,导致C#互操作封送失败,具体疑问点如下:
- 该问题如何修复?
- 如何实现C#侧Mat类型与C++侧cv::Mat类型的匹配适配,避免报错?
- 需要修改C侧函数,还是只需在C#侧做特殊处理即可读取Mat内部数据传入C函数?
报错根本原因
当前报错Cannot marshal 'return value': Generic types cannot be marshaled的直接原因是:
- C的
std::vector<std::string>属于C STL模板类型,C#互操作层无法识别和封送 - 入参的
cv::Mat和C# OpenCVSharp的Mat是不同语言实现的类,内存布局完全不一致,无法直接跨语言传递
解决方案
三个问题明确答复
- 修复方案:修改C++ DLL的导出函数签名为C风格的可互操作类型,C#侧对应调整P/Invoke声明和传参逻辑即可解决
- Mat适配方案:不直接传递Mat对象,而是传递Mat的核心元数据(宽、高、行步长、像素数据指针、像素格式枚举值),在C++侧手动组装为
cv::Mat,无需依赖两侧Mat的二进制兼容性 - 改动范围:两侧都需要做少量修改,原有业务逻辑完全不需要改动,仅调整对外交互的入口代码即可
具体代码修改
C++侧修改
第一步:调整导出函数签名,新增内存释放函数
避免使用C++专属类型作为入参和返回值,改为C基础类型:
#include <cstring> // 新增:用于释放返回字符串的内存,避免内存泄漏 extern "C" __declspec(dllexport) void __cdecl FreeString(char* str) { if (str != nullptr) delete[] str; } // 修改导出函数签名,入参改为Mat的核心属性,返回值改为char* extern "C" __declspec(dllexport) char* __cdecl ProcessFrame(int width, int height, int step, unsigned char* data, int cvType) { // 手动组装cv::Mat,原有业务逻辑完全不变 cv::Mat image(height, width, cvType, data, step); /**************************** 此处保留你原来的ProcessFrame所有业务代码,直到得到listName结果 ****************************/ // 将返回的字符串列表拼接为|分隔的字符串 std::string joinStr; for (size_t i = 0; i < listName.size(); i++) { if (i != 0) joinStr += "|"; joinStr += listName[i]; } // 分配返回内存,C#侧调用FreeString释放 char* res = new char[joinStr.size() + 1]; strcpy(res, joinStr.c_str()); return res; }
C#侧修改
第一步:调整P/Invoke声明,适配C风格函数签名
using System.Runtime.InteropServices; using OpenCvSharp; [DllImport("detect-camera.dll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr ProcessFrame(int width, int height, int step, IntPtr data, int cvType); [DllImport("detect-camera.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void FreeString(IntPtr str);
第二步:调整调用逻辑
private void button1_Click(object sender, EventArgs e) { Mat image = Cv2.ImRead("D:/c++/ImportCallFunction/ImportCallFunction/123.jpg"); // 传递Mat的核心属性到C++ IntPtr resPtr = ProcessFrame(image.Cols, image.Rows, (int)image.Step(), image.Data, image.Type()); // 转换返回结果为C#字符串 string resStr = Marshal.PtrToStringAnsi(resPtr); // 释放C++侧分配的内存,避免泄漏 FreeString(resPtr); // 拆分得到人脸列表 List<string> facelist = resStr.Split('|').ToList(); foreach (var item in facelist) { listBox1.Items.Add(item); } }
注意事项
- 确保C++使用的OpenCV版本与C# OpenCVSharp绑定的OpenCV版本的
cv::Mat::type()枚举值定义一致,避免像素格式解析错误 - 若需要支持中文返回值,可将字符串编码改为UTF8,C#侧用
Marshal.PtrToStringUTF8转换即可 - 处理过程中不要提前释放C#侧的Mat对象,避免C++侧访问已释放的像素内存
内容的提问来源于stack exchange,提问作者reza
相关产品推荐
相关产品推荐

