如何在托管 (C#) 和非托管 (C++) 代码之间来回传递数组内容

Posted

技术标签:

【中文标题】如何在托管 (C#) 和非托管 (C++) 代码之间来回传递数组内容【英文标题】:How to pass array contents back and forth between managed (C#) and unmanaged (C++) code 【发布时间】:2020-12-06 13:33:57 【问题描述】:

我正在尝试将一个浮点数组从 C# 传递给一个 C++ dll,该 dll 将对那个数组做一些工作,然后以浮点数组的形式传回一个答案(不一定具有相同的长度)。

我有代码可以传递数组,修改它然后返回它,但是数组中的值与预期的不一样,尽管数组看起来格式正确... 下面的示例采用浮点数组,将其传递给 C++。在 C++ 中,将值 2 添加到数组的每个元素以模拟完成的工作,并使用回调将结果返回给 C#。 每次都会返回一组不同的值。 一组典型的返回值是:- 81.58881、2、75.75787、4

任何想法都将不胜感激.. 干杯 格雷厄姆

C#

public delegate void CallBack1(IntPtr param, int len);
[DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void testCPlusSide(CallBack1 cb, IntPtr arr, int len);

private void button_Click(object sender, EventArgs e)

    float[] dataIn = new float[]  0, 0.5f, 7.79f, 46 ;
    GCHandle gch = GCHandle.Alloc(dataIn);

    CallBack1 testCB = new CallBack1(TestCallBack);

    testCPlusSide(testCB, GCHandle.ToIntPtr(gch), dataIn.Length);

    gch.Free();


void TestCallBack(IntPtr param, int len)

    float[] vals = new float[len];
    GCHandle gch = GCHandle.FromIntPtr(param);
    Marshal.Copy(param, vals, 0, len);
    StringBuilder sb = new StringBuilder();
    foreach (float item in vals)
    
        sb.Append(item.ToString() + "\n");
    
    MessageBox.Show(sb.ToString());

C++

typedef void(__stdcall * CallBack1)(float * arr, int len);
__declspec(dllexport) void testCPlusSide(CallBack1 cb, float* arr, int len)

    for (int i = 0; i < len; i++)
    
        arr[i] += 2;
    
    cb(arr, len);

更新 我添加了一个临时的float 来读取 C++ 端数组的内容,我发现数组 C++ 端的内容与传递的值没有关系 - 我不知道为什么....

【问题讨论】:

可能值得在 c++ 中添加一些诊断打印。只是为了看看数组在边界那一侧之前和之后的内容。 如果您在 Windows 上开发,您是否尝试过使用 Visual Studio 进行调试并从 C# 步入 C++? 是的 - 对于调试和诊断输出 - 这就是促使更新的原因。我还将 C# 端 dllimport 定义更改为:- public static extern void testCPlusSide(CallBack1 cb, [MarshalAs(UnmanagedType.LPArray)] float[] x, int len);它正确传递了数据,但在 C++ 中调用回调时给出了 System.ExecutionEngineException 错误 显然这是一条过时的错误消息,表明运行时出现未指定的致命错误... 【参考方案1】:

我已经设法让事情顺利进行。 我更改了 GCHandle.Alloc 方法,因此它创建了一个固定对象指针并且一切正常。 我还在 TestCallBackFunction 中删除了一个不必要的 GCHandle 调用 完整更改的代码是:-

C#

public delegate void CallBack1(IntPtr param, int len);
[DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)]  

public static extern void testCPlusSide(CallBack1 cb, IntPtr arr, int len);
    
private void button_Click(object sender, EventArgs e)

    float[] dataIn = new float[]  0, 0.5f, 7.79f, 46 ;
    GCHandle gch = GCHandle.Alloc(dataIn,GCHandleType.Pinned);

    CallBack1 testCB = new CallBack1(TestCallBack);

    testCPlusSide(testCB, gch.AddrOfPinnedObject(), dataIn.Length);
    
    gch.Free();

    
void TestCallBack(IntPtr param, int len)

    float[] vals = new float[len];
    Marshal.Copy(param, vals, 0, len);
    StringBuilder sb = new StringBuilder();
    foreach (float item in vals)
    
        sb.Append(item.ToString() + "\n");
    
    MessageBox.Show(sb.ToString());

C++

typedef void(__stdcall * CallBack1)(float * arr, int len);

__declspec(dllexport) void testCPlusSide(CallBack1 cb, float* arr, int len)

    for (int i = 0; i < len; i++)
    
        arr[i] += 2;
    
    cb(arr, len);

感谢 Carlos 和 rhughes 的帮助 干杯 格雷厄姆

【讨论】:

这不是必需的,pinvoke marshaller 已经确保在本机代码运行时固定数组。只需将参数声明为 float[] 即可。如果本机代码存储指针并且稍后在 testCPlusSide() 完成后发生回调,那么您将遇到需要保持数组对象处于活动状态并固定的问题。你总是想避免的事情,固定对 GC 来说是痛苦的。固定的时间越长,情况就越糟糕。 嗨汉斯 - 抱歉声明哪个参数为浮点数?在上面的评论中,我探讨了声明:- public static extern void testCPlusSide(CallBack1 cb, [MarshalAs(UnmanagedType.LPArray)] float[] x, int len);当使用回调时,这会导致非托管代码中的错误——公平地说,这是一个调试器错误。请你能扩展你的意思吗?

以上是关于如何在托管 (C#) 和非托管 (C++) 代码之间来回传递数组内容的主要内容,如果未能解决你的问题,请参考以下文章

托管和非托管的c++是啥意思,有啥区别?

托管和非托管代码错误 C3699

在托管代码和非托管代码之间传递非托管结构的安全数组

在 C# 中传递 IntPtr 指针后,在非托管 C++ 代码中分配数组,编组类型

C#调用C++编写的DLL(非托管)

C# 托管和非托管混合编程