如何在托管 (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++) 代码之间来回传递数组内容的主要内容,如果未能解决你的问题,请参考以下文章