使用P/Invoke从C#调用C++ DLL时返回异常数组值的问题
问题原因与解决方案
核心问题分析
- C++参数传递逻辑错误:你的C函数中
unsigned char *ptr是值传递,函数内部ptr = new unsigned char[5];仅修改了函数局部的指针副本,不会将新分配的数组地址传递回C#调用者。C#端通过Marshal.AllocHGlobal分配的内存从未被C函数写入,后续拷贝的是未初始化的随机内存,所以结果不符合预期。 - C#调用参数不匹配:C#代码调用
getData(ref ptr, shouldDetect)时多传了一个shouldDetect参数,但C++函数仅定义了一个参数,这会导致调用栈混乱,引发未定义行为。 - 跨语言内存管理冲突:即使地址能传回,C++用
new分配的内存无法通过C#的Marshal.FreeHGlobal释放——两者依赖的内存分配器不同,强行释放会导致内存泄漏或程序崩溃。
修复方案(两种常见可行思路)
思路1:C#预先分配内存,C++直接写入
这种方式避免跨语言内存管理问题,内存由C#全权负责分配和释放:
修正后的C++函数:
int getData(unsigned char *ptr) { // 直接写入调用者提供的内存,不再内部分配 ptr[0] = 10; ptr[1] = 20; ptr[2] = 30; ptr[3] = 40; ptr[4] = 50; return 0; }
修正后的C#代码:
using System.Runtime.InteropServices; [DllImport(CppLibPath, CallingConvention = CallingConvention.Cdecl)] // 无需ref,传递预先分配的内存地址让C++写入 private static extern int getData(IntPtr ptr); public static void GetData() { var size = 5; var ptr = Marshal.AllocHGlobal(size); var arr = new byte[size]; // 移除多余的shouldDetect参数,仅传递内存地址 var result = getData(ptr); Marshal.Copy(ptr, arr, 0, size); Marshal.FreeHGlobal(ptr); }
思路2:C++分配内存,配套释放函数
如果必须由C++分配内存,需同时提供对应的释放函数,禁止用C#的内存释放API:
C++端新增释放函数:
// 修改为接收指针的指针,实现地址回传 int getData(unsigned char **ptr) { *ptr = new unsigned char[5]; (*ptr)[0] = 10; (*ptr)[1] = 20; (*ptr)[2] = 30; (*ptr)[3] = 40; (*ptr)[4] = 50; return 0; } // 配套释放函数,用delete[]释放new[]分配的内存 void freeData(unsigned char *ptr) { delete[] ptr; }
C#端调用代码:
using System.Runtime.InteropServices; [DllImport(CppLibPath, CallingConvention = CallingConvention.Cdecl)] private static extern int getData(ref IntPtr ptr); [DllImport(CppLibPath, CallingConvention = CallingConvention.Cdecl)] private static extern void freeData(IntPtr ptr); public static void GetData() { var size = 5; IntPtr ptr = IntPtr.Zero; var arr = new byte[size]; // 移除多余的shouldDetect参数 var result = getData(ref ptr); Marshal.Copy(ptr, arr, 0, size); // 调用C++的释放函数,不能用Marshal.FreeHGlobal freeData(ptr); }
内容的提问来源于stack exchange,提问作者El_Merendero
相关产品推荐
相关产品推荐

