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

C#互操作:不使用unsafe能否封送含二维数组的C结构体?

无需unsafe的C#互操作方案处理C DLL的二维结构数组

当然有办法在不使用unsafe关键字的情况下处理这个二维结构数组!我们可以利用C#的封送特性和一些辅助技巧来实现,下面分两种情况给出方案——完全不修改C API和仅做最小改动C API的情况,你可以根据需求选择。

方案一:完全不修改现有C API

这个方案不需要改动任何C代码,仅通过C#的封送机制和Marshal类来完成数据转换。

首先重新定义C#端的结构和导入函数,完全避免unsafe:

public struct MyPodStruct
{
    public double a;
    public double b;
}

[StructLayout(LayoutKind.Sequential)]
private struct My2dArray
{
    // 用IntPtr替代指针的指针,绕过unsafe语法
    public IntPtr arr;
    public int n;
    public int m;
}

[DllImport("Foo.DLL", EntryPoint = "Init2d", CallingConvention = CallingConvention.Cdecl)]
private static extern void Init2d(ref My2dArray s, int n, int m);

[DllImport("Foo.DLL", EntryPoint = "Free2d", CallingConvention = CallingConvention.Cdecl)]
private static extern void Free2d(ref My2dArray s);

[DllImport("Foo.DLL", EntryPoint = "Fill2dResult", CallingConvention = CallingConvention.Cdecl)]
private static extern void Fill2dResult(ref My2dArray result);

接下来是核心的数据转换逻辑,通过Marshal.Copy来读取原生内存中的指针数组和结构数据:

public static MyPodStruct[,] GetFill2dResult(int n, int m)
{
    My2dArray nativeArray = new My2dArray();
    MyPodStruct[,] managedArray = new MyPodStruct[n, m];
    
    // 初始化并填充原生二维数组
    Init2d(ref nativeArray, n, m);
    Fill2dResult(ref nativeArray);
    
    // 第一步:读取所有行的指针(原生的MyPodStruct**转成C#的IntPtr数组)
    IntPtr[] rowPointers = new IntPtr[n];
    Marshal.Copy(nativeArray.arr, rowPointers, 0, n);
    
    // 第二步:遍历每一行,把原生结构数据复制到托管数组
    for (int i = 0; i < n; i++)
    {
        // 一次性复制整行的MyPodStruct到一维托管数组
        MyPodStruct[] rowData = new MyPodStruct[m];
        Marshal.Copy(rowPointers[i], rowData, 0, m);
        
        // 将一维行数据映射到二维数组的对应位置
        for (int j = 0; j < m; j++)
        {
            managedArray[i, j] = rowData[j];
        }
    }
    
    // 务必释放原生内存,避免泄漏
    Free2d(ref nativeArray);
    
    return managedArray;
}

方案原理

我们把原生的MyPodStruct**当作一个指向IntPtr数组的指针,先通过Marshal.Copy把这些行指针读取到C#的IntPtr[]中。之后再针对每个行指针,用Marshal.Copy直接把整行的结构数据复制到托管数组里,全程不需要直接操作指针,完全规避了unsafe语法。


方案二:仅做最小改动C API(更高效)

如果你可以对C API做极其微小的改动(新增一个辅助函数,不修改原有逻辑),这个方案的效率会更高,尤其是处理大数组时。

新增C辅助函数

在原有的C代码中新增一个负责复制数组的函数,逻辑非常简单:

#include <string.h>

void Copy2dArray(const My2dArray* src, MyPodStruct* dst, int n, int m)
{
    for (int i = 0; i < n; i++)
    {
        // 把每一行的结构数据直接复制到目标缓冲区
        memcpy(dst + i*m, src->arr[i], sizeof(MyPodStruct)*m);
    }
}

C#端实现

导入新增的函数,然后简化转换逻辑:

// 新增的函数导入
[DllImport("Foo.DLL", EntryPoint = "Copy2dArray", CallingConvention = CallingConvention.Cdecl)]
private static extern void Copy2dArray(ref My2dArray src, [Out] MyPodStruct[] dst, int n, int m);

public static MyPodStruct[,] GetFill2dResultOptimized(int n, int m)
{
    My2dArray nativeArray = new My2dArray();
    // 先创建一维托管数组作为复制缓冲区
    MyPodStruct[] tempArray = new MyPodStruct[n * m];
    MyPodStruct[,] managedArray = new MyPodStruct[n, m];
    
    Init2d(ref nativeArray, n, m);
    Fill2dResult(ref nativeArray);
    
    // 一次性把整个二维数组复制到托管缓冲区
    Copy2dArray(ref nativeArray, tempArray, n, m);
    
    // 将一维缓冲区的数据映射到二维数组
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            managedArray[i, j] = tempArray[i * m + j];
        }
    }
    
    Free2d(ref nativeArray);
    return managedArray;
}

方案优势

这个方案只新增了一个无侵入性的辅助函数,原有API的逻辑完全不变。通过原生代码完成内存复制,减少了C#端的中间步骤,效率比方案一更高,同时仍然不需要使用unsafe。


总结

  • 如果完全不能修改C API,方案一完全满足需求,全程无unsafe,逻辑清晰易维护。
  • 如果允许做最小改动,方案二是更优选择,性能更好,代码也更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:25:37