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

C#调用C++ DLL输入参数未修改,Marshal.Copy抛空引用异常

问题解决:C#调用C++ DLL结构体双向传递异常及数据未更新问题

核心问题分析

你遇到的System.ArgumentNullException是因为结构体按值传递导致DLL修改的是副本,原结构体的grid指针未被赋值,加上C#与C++结构体内存布局不匹配、内存管理不当共同引发的。

分步解决方案

1. 统一C++与C#的结构体内存布局

C++端结构体定义(确保用extern "C"导出,避免名称 mangling):

extern "C" {
    struct InputParams {
        int gridSize;
        float* grid;
        // 其他参数保持字段顺序一致
    };

    __declspec(dllexport) void __stdcall ObjectToTopo(InputParams* inOutParams);
}

C#端对应结构体必须指定内存布局与C++完全一致:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct InputParams
{
    public int gridSize;
    public IntPtr grid; // 用IntPtr对应C++的float*指针
    // 其他字段顺序、类型必须与C++严格匹配
}

2. 按引用传递结构体(关键)

C#调用DLL方法时必须用ref关键字,确保DLL拿到的是原结构体的指针而非副本:

[DllImport("YourDllName.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void ObjectToTopo(ref InputParams param);

3. 正确管理非托管内存

C#端需提前分配非托管内存给grid数组,避免DLL端分配内存引发的托管/非托管内存泄漏问题:

// 准备托管输入数组
float[] inputGrid = new float[100]; // 示例大小,对应gridSize
InputParams param = new InputParams();
param.gridSize = inputGrid.Length;

// 分配非托管内存(大小为数组元素数×单元素字节数)
param.grid = Marshal.AllocHGlobal(inputGrid.Length * sizeof(float));
// 将托管数组复制到非托管内存
Marshal.Copy(inputGrid, 0, param.grid, inputGrid.Length);

try
{
    // 调用DLL方法,传递结构体引用
    ObjectToTopo(ref param);

    // 读取DLL修改后的结果
    float[] outputGrid = new float[param.gridSize];
    Marshal.Copy(param.grid, outputGrid, 0, param.gridSize);
    // 此处使用outputGrid处理业务逻辑
}
finally
{
    // 必须释放非托管内存,避免泄漏
    if (param.grid != IntPtr.Zero)
    {
        Marshal.FreeHGlobal(param.grid);
    }
}

4. DLL端完善指针有效性检查

在C++的ObjectToTopo方法中先校验输入指针,避免空指针访问崩溃:

void __stdcall ObjectToTopo(InputParams* inOutParams)
{
    // 先检查输入有效性
    if (!inOutParams || !inOutParams->grid || inOutParams->gridSize <= 0)
    {
        // 可添加错误日志或返回错误码
        return;
    }

    // 修改grid数据示例(根据业务逻辑替换)
    for (int i = 0; i < inOutParams->gridSize; i++)
    {
        inOutParams->grid[i] *= 2.0f; // 示例:将每个元素翻倍
    }

    // 如需修改gridSize,直接修改即可(C#端会同步拿到更新后的值)
    // inOutParams->gridSize = newSize;
}

关键注意事项

  • 调用约定必须统一:C++用__stdcall,C#的DllImport指定CallingConvention.StdCall(默认是StdCall,但显式指定更安全)。
  • 内存释放必须配对:Marshal.AllocHGlobal分配的内存必须用Marshal.FreeHGlobal释放,且要放在finally块中确保即使异常也能释放。
  • 字段顺序严格匹配:结构体的字段顺序、类型在C++和C#中必须完全一致,否则会出现内存偏移导致的未知错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 20:12:44