如何通过PInvoke从C#向C库传递数组指针并正确获取数据
解决C#调用C DLL中getData函数的内存写入问题
首先注意到你提供的C++代码中调用的是getFrame,但C库函数声明是getData,这大概率是笔误,请先确认C#代码里的函数名与DLL导出的函数名完全一致(可通过dumpbin /exports工具查看DLL的导出表)。
下面是几种正确的调用方式,以及内存验证方法:
方法一:修正数组封送特性与函数签名
默认情况下,C#将值类型数组作为[In]参数封送,仅传递数据到非托管代码,不会回传修改后的数据。即使添加[In, Out],若函数签名或调用约定不匹配也会失效,请重点检查以下几点:
- 确保DLL的调用约定为
cdecl(与你指定的一致) - 函数返回值类型匹配:C++的
uint对应C#的uint而非int,错误的返回值类型会导致调用栈混乱,进而引发内存异常 - 显式指定数组的封送特性与固定长度
修正后的代码:
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "getData")] static private extern uint getData([In, Out, MarshalAs(UnmanagedType.LPArray, SizeConst = 4096)] ushort[] buffer, ushort dataIndex); ushort[] buffer = new ushort[4096]; // 调用前填充固定值,方便验证是否被修改 Array.Fill(buffer, (ushort)0xFFFF); uint response = getData(buffer, 1); // 检查前10个元素的变化 for(int i = 0; i < 10; i++) { Console.WriteLine(buffer[i]); }
方法二:使用IntPtr手动管理非托管内存
这种方式可完全控制内存分配,规避自动封送的潜在问题:
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)] static private extern uint getData(IntPtr buffer, ushort dataIndex); // 分配非托管内存:4096个ushort,每个占2字节 IntPtr unmanagedBuffer = Marshal.AllocHGlobal(4096 * sizeof(ushort)); try { // 可选:初始化非托管内存为固定标记值 ushort[] initBuffer = new ushort[4096]; Array.Fill(initBuffer, (ushort)0xAAAA); Marshal.Copy(initBuffer, 0, unmanagedBuffer, 4096); uint response = getData(unmanagedBuffer, 1); // 将非托管内存数据复制回托管数组 ushort[] resultBuffer = new ushort[4096]; Marshal.Copy(unmanagedBuffer, resultBuffer, 0, 4096); // 验证数据 Console.WriteLine("First 5 elements:"); for(int i = 0; i < 5; i++) { Console.WriteLine(resultBuffer[i]); } } finally { // 必须释放非托管内存,避免泄漏 Marshal.FreeHGlobal(unmanagedBuffer); }
方法三:使用unsafe代码直接传递指针
若项目允许启用unsafe代码,可直接获取托管数组的指针:
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)] static private extern unsafe uint getData(ushort* buffer, ushort dataIndex); unsafe { ushort[] buffer = new ushort[4096]; Array.Fill(buffer, (ushort)0xBBBB); fixed(ushort* ptr = buffer) { uint response = getData(ptr, 1); } // 直接检查buffer数组即可 foreach(var val in buffer.Take(5)) { Console.WriteLine(val); } }
验证内存是否被正确写入的方法
- 初始化标记法:调用前将数组填充固定非随机值(如0xFFFF、0xAAAA),调用后检查是否有值被修改,若仍为初始值说明内存未被写入;若变为其他值,再对比是否符合预期数据
- 调试器内存查看:C#调试时,在调用后设置断点,通过
Memory窗口查看IntPtr或unsafe指针指向的内存内容,与C++调试时的内存数据对比 - 文件对比法:写简单的C++测试程序,调用
getData后将buffer数据写入文件;C#调用后将数据写入同格式文件,对比两个文件是否一致
内容的提问来源于stack exchange,提问作者Iridium
相关产品推荐
相关产品推荐

