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

C#通过结构体指针向C DLL传递结构体数组的P/Invoke调用问题

问题根因
  • 你当前的[DllImport]签名使用ref DATA pData声明参数时,封送层默认仅会封送单个DATA结构体大小的内存块到非托管侧,不会自动识别并拷贝托管堆上整个连续的DATA数组。
  • C端拿到指针后按传入的数组长度做指针偏移访问后续元素时,读取的是未被正确封送的非法内存,直接导致第二个及之后的结构体字段出现乱码、数值错误。
  • 你参考的字节数组ref arr[0]传参能生效,是因为byte是可直接内存拷贝的blittable基元类型,封送器对基元类型数组的ref首元素传参有特殊处理,但自定义结构体数组直接ref首元素不会触发该逻辑。
正确实现方案

你的DATA结构体本身的封送定义是正确的:32定长字节数组+32位int的内存布局和C端完全匹配,不需要调整。以下两种方案任选其一即可:

方案1:直接声明数组参数(最简洁,推荐日常使用)

直接修改P/Invoke签名,将第一个参数声明为数组类型,让封送器自动处理整个数组的连续内存拷贝:

[DllImport("a.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void test_data(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] DATA[] pData, 
    // 严谨匹配C端DWORD的话可以把length类型改成uint
    int length
);

特性说明:

  • [MarshalAs(UnmanagedType.LPArray)]明确告知封送器该参数对应非托管侧的连续数组
  • SizeParamIndex = 1指定数组长度由签名中索引为1的参数(即length参数)提供

调用时直接传入整个数组即可,不需要传数组首元素的ref:

int length = 10;
DATA[] arr = new DATA[length];
for(int i = 0; i < length; i++){
  arr[i].name = new byte[32];
  arr[i].age = 10;
}
// 直接传数组,无需ref arr[0]
test_data(arr, length);

方案2:手动分配非托管内存(高性能场景适用)

如果调用频率高、数组长度大,想要避免封送器的默认拷贝开销,可以手动在非托管堆分配连续内存,自行完成结构体拷贝,调用完成后释放内存:
首先修改P/Invoke签名,接收非托管指针类型:

[DllImport("a.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void test_data(IntPtr pData, uint length);

调用逻辑:

int length = 10;
DATA[] arr = new DATA[length];
for(int i = 0; i < length; i++){
  arr[i].name = new byte[32];
  arr[i].age = 10;
}

int structSize = Marshal.SizeOf<DATA>();
// 分配对应长度的连续非托管内存
IntPtr pUnmanagedArray = Marshal.AllocHGlobal(structSize * length);
try
{
    // 按偏移将每个托管结构体拷贝到非托管内存
    for (int i = 0; i < length; i++)
    {
        IntPtr pCurrentStruct = pUnmanagedArray + i * structSize;
        Marshal.StructureToPtr(arr[i], pCurrentStruct, false);
    }
    // 调用C端函数
    test_data(pUnmanagedArray, (uint)length);
}
finally
{
    // 必须释放非托管内存,避免内存泄漏
    Marshal.FreeHGlobal(pUnmanagedArray);
}
额外避坑提示
  • 你贴出的C端代码存在变量名笔误:函数入参命名为pStruct,但函数内部遍历使用的是pData,直接编译会报错,需要统一变量名。
  • C端DWORD是32位无符号整数,日常使用传int类型不会有问题,但如果数组长度可能超过int最大值,建议将C#侧对应参数改为uint做严格匹配。
  • 不要对自定义结构体数组使用ref arr[0]的传参方式,封送器不会自动遍历拷贝整个数组,本质上和传单个结构体的引用没有区别。

内容的提问来源于stack exchange,提问作者of32 inc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 07:39:22