使用SWIG封装C结构体指针供C#调用的配置实现问题
SWIG封装LabView导出C数组接口给C#调用的实现方案
现有接口的核心问题
- 类型映射错误:
int32是32位整型,当前用%apply long {int32}映射到C#的64位long,会导致参数传参错位、内存读取异常。 - 指针层级不匹配:
AllocateDoubleArray返回值是DoubleArray也就是DoubleArrayBase**二级指针,SWIG默认将其包装为不透明的IntPtr类型,无法直接解引用拿到内部的DoubleArrayBase实例访问成员。 - 柔性数组处理缺失:结构体中
double Numeric[1]是LabView导出数组的典型柔性数组写法,实际分配的内存长度等于dimSize个double元素,而非声明的长度1,SWIG默认只会将其生成长度为1的固定数组,无法访问全部元素。 - 未适配C#枚举遍历需求:默认生成的包装类没有实现
IEnumerable<double>接口,无法直接foreach遍历。
修正后的SWIG接口文件
直接替换当前的.i文件内容即可:
%module MyModule %{ #include "MyHeader.h" %} %include <stdint.i> %include <windows.i> %include <arrays_csharp.i> %include <typemaps.i> // 修正int32类型映射,对应C# int(32位整型) %apply int { int32_t, int32 }; %apply int *INOUT { int32_t *, int32 *}; // 处理DoubleArray二级指针返回值:解引用拿到DoubleArrayBase* %typemap(csout, excode=SWIGEXCODE) DoubleArray AllocateDoubleArray { IntPtr rawPtr = $imcall;$excode // DoubleArray是DoubleArrayBase**,解一次引用拿到DoubleArrayBase* IntPtr basePtr = System.Runtime.InteropServices.Marshal.ReadIntPtr(rawPtr); var ret = new DoubleArrayBase(basePtr, false); // 缓存原始句柄用于后续释放/重分配 ret._handle = rawPtr; return ret; } // 给DoubleArrayBase添加私有字段存原始句柄,以及C#端扩展成员 %typemap(cscode) DoubleArrayBase %{ internal IntPtr _handle; // 公开dimSize为C#属性,直接读取结构体成员 public int DimSize => dimSize; // 索引器直接访问Numeric数组元素 public double this[int index] { get { if (index < 0 || index >= DimSize) throw new IndexOutOfRangeException(); // Numeric是结构体中double数组首地址,偏移读取对应位置元素 IntPtr numOffset = System.Runtime.InteropServices.Marshal.OffsetOf(typeof(DoubleArrayBase), nameof(Numeric)); return System.Runtime.InteropServices.Marshal.ReadDouble(swigCPtr.Handle + numOffset.ToInt64() + index * sizeof(double)); } } // 实现IEnumerable支持foreach遍历 public System.Collections.Generic.IEnumerator<double> GetEnumerator() { for (int i = 0; i < DimSize; i++) { yield return this[i]; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } %} // 处理DeAllocateDoubleArray的参数,自动传入缓存的句柄地址 %typemap(csin) DoubleArray *hdlPtr { IntPtr handlePtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(IntPtr.Size); System.Runtime.InteropServices.Marshal.WriteIntPtr(handlePtr, $input._handle); $csinput = handlePtr; } %typemap(csargout) DoubleArray *hdlPtr { System.Runtime.InteropServices.Marshal.FreeCoTaskMem($csinput); } // 原头文件声明 typedef struct { int32_t dimSize; double Numeric[1]; } DoubleArrayBase; typedef DoubleArrayBase **DoubleArray; DoubleArray __cdecl AllocateDoubleArray (int32 elmtCount); MgErr __cdecl ResizeDoubleArray (DoubleArray *hdlPtr, int32 elmtCount); MgErr __cdecl DeAllocateDoubleArray (DoubleArray *hdlPtr);
C#端调用示例
封装完成后可以直接按预期方式调用:
// 分配长度为10的DoubleArray DoubleArrayBase myArray = MyModule.AllocateDoubleArray(10); // 直接读取数组长度 int size = myArray.DimSize; // 索引访问元素 double firstVal = myArray[0]; // 直接foreach遍历所有元素 foreach (double val in myArray) { Console.WriteLine(val); } // 用完释放内存 MyModule.DeAllocateDoubleArray(ref myArray);
注意事项
- 上述typemap中构造
DoubleArrayBase时传入false,是告知SWIG不要持有该指针的所有权,内存由LabView导出的DeAllocateDoubleArray函数统一释放,避免重复释放。 - 调用
ResizeDoubleArray后需要重新读取DimSize值,不要缓存旧的长度,重分配后内部指针地址可能变化。 - 所有数组访问的索引边界校验在C#端完成,避免越界访问非托管内存导致程序崩溃。
内容的提问来源于stack exchange,提问作者Alex Gillet
相关产品推荐
相关产品推荐

