You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:51:18