C#传递coordpt结构体锯齿数组到C++ DLL异常排查与实现优化
问题解答
1 现有代码的错误分析
你给出的代码存在多处直接导致内存访问异常的错误,具体如下:
- 结构体定义严重错误
你把C中值类型的coordpt定义成了C#的引用类型class,封送时会多一层指针,完全不符合C的内存布局。其次LayoutKind.Explicit的偏移值设置错误:double类型占8字节,x偏移为0,y的偏移应该是8,你写FieldOffset(1)会导致两个字段内存完全重叠,传递的数据全部错乱。
正确的结构体定义如下:[StructLayout(LayoutKind.Sequential)] public struct coordpt { public double x; public double y; public coordpt(double x, double y) { this.x = x; this.y = y; } } - 调用约定可能不匹配
头文件中定义如果是__WATCOMC__编译环境,函数调用约定是__stdcall,你当前DllImport写的是CallingConvention.Cdecl,如果DLL是Watcom编译器生成的,调用约定不匹配会直接导致栈不平衡,触发内存访问错误,可先改为CallingConvention.StdCall测试。 StructureToPtr参数错误Marshal.StructureToPtr的第三个参数fDeleteOld你传了true,但你通过AllocCoTaskMem申请的是全新内存,里面没有需要释放的旧结构数据,传true会让CLR尝试释放未初始化内存的内容,直接触发访问违规,该参数应该传false。- 其他隐患
直接对IntPtr做整数加法在32位运行环境下可能溢出,建议使用IntPtr.Add计算偏移;你当前只申请了非托管内存,没有对应的释放逻辑,会导致内存泄漏。
上述错误均会直接触发你遇到的AccessViolationException异常。
2 更简便的锯齿数组传递实现
如果允许使用unsafe代码,可以省略逐个拷贝结构体的步骤,直接固定数组内存传递,实现更简单、性能更高:
// 调用逻辑示例 unsafe { GCHandle[] rowHandles = new GCHandle[inputArrayOfArrays.Length]; IntPtr[] ptrArr = new IntPtr[inputArrayOfArrays.Length]; for (int i = 0; i < inputArrayOfArrays.Length; i++) { // 固定每一行的数组内存 rowHandles[i] = GCHandle.Alloc(inputArrayOfArrays[i], GCHandleType.Pinned); ptrArr[i] = rowHandles[i].AddrOfPinnedObject(); } // 调用DLL函数 int result = CPlusPlusFunction(ptrArr, NumberOfArrays, arrayContainesTheSizeOfEachArray); // 释放固定句柄 foreach (var handle in rowHandles) { handle.Free(); } }
这种方式不需要手动申请、拷贝内存,依托GC的固定机制实现,逻辑更简洁,也不会有内存泄漏风险,前提是C++函数不会在返回后仍持有传入的指针地址。
如果不能使用unsafe代码,修复现有封送方法的错误后也可正常使用,注意调用完DLL函数后要逐个释放你申请的CoTaskMem内存即可。
内容的提问来源于stack exchange,提问作者Eran.G
相关产品推荐
相关产品推荐

