使用Marshal.Copy的原生C ++浮点数组到C#浮点数组不适用于某些数据

在尝试将数组从C ++传递到C#时,我看到了一个非常奇怪的问题。 我正在使用Marshal.Copy(具体来说: https ://msdn.microsoft.com/en-us/library/a53bd6cz( v= vs.110).aspx)。

问题:从C ++到C#的float数组在结果数组中产生一些NaN (注意:我在Unity游戏引擎的上下文中工作)


示例C ++代码:

 extern "C" bool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API getSomeFloats(float** points, int* count) { std::vector results; std::vector key_points =  for (auto iter = key_points.begin(); iter < key_points.end(); iter++) { results.push_back(static_cast(iter->pt.x)); results.push_back(static_cast(iter->pt.y)); } *points = results.data(); *count = results.size(); // return true; } 

示例C#代码:

 [DllImport("NativePlugin")] private static extern bool getSomeFloats (ref IntPtr ptrResultItems, ref int resultItemsLength); private static float[] getFloatArrayFromNative() { IntPtr ptrResultItems = IntPtr.Zero; int resultItemsLength = 0; bool success = getSomeFloats (ref ptrResultItems, ref resultItemsLength); float[] resultItems = null; if (success) { // Load the results into a managed array. resultItems = new float[resultItemsLength]; Marshal.Copy (ptrResultItems , resultItems , 0 , resultItemsLength); //  return resultItems; } else { Debug.Log ("Something went wrong getting some floats"); return new float[] { -1, -2 }; } } 

示例输出:采用以下示例:C ++输出(print_out.csv):

123,456,789

C#输出(print_out_cs.csv):

123,NaN,789


我完全被这个困扰了。 我只是不明白为什么只有一些(大约7/100)花车返回NaN 。 有没有人有任何可能有帮助的建议/见解?

谢谢!

在您的代码中发现几个问题:

1std::vector results; 在堆栈上声明。 它会在函数返回时消失。 将其声明为指针

 std::vector *results = new std::vector(10); 

但一定要声明一个能在C ++端释放它的函数。

2.function参数不匹配。

你的C ++:

 getSomeFloats(float** points, int* count, CameraPose* pose) 

你的C#:

 getSomeFloats (ref IntPtr ptrResultItems, ref int resultItemsLength); 

您必须从C ++端删除CameraPose* pose或将IntPtr pose添加到C#端。

3 。 使用UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API

你不需要那个。 当您想使用Unity的内置函数(如GL.IssuePluginEvent时,可以使用GL.IssuePluginEvent 。 在这种情况下你没有使用它。

这应该这样做:

 #define DLLExport __declspec(dllexport) extern "C" { DLLExport bool getSomeFloats (ref IntPtr ptrResultItems, ref int resultItemsLength){} } 

4 .C#有一个垃圾收集器,可以在内存中移动变量。 如果要从C ++端修改它,必须固定C#数组。 你只需要固定C#数组。 另一种选择是在C ++端分配数组,将其返回C#将其复制到C#端的临时变量,然后在C ++端删除它。

5.将结果复制回数组而不是分配它。


推荐方法

很多方法可以做到这一点,其中一些非常慢。 如果你想使用Marshal.Copy ,你必须在C ++端分配数组,否则你将遇到一些未定义的行为。

最快和最有效的方法是将C#端的数组分配为全局变量。 将数组及其长度传递给本机端。 还传递第三个参数,C ++可以使用该参数告诉C#已更新或写入的索引量。

这比创建新数组要好得多,将其复制到C#变量,然后在每次调用函数时将其销毁。

这是你应该使用的:

C ++:

 #define DLLExport __declspec(dllexport) extern "C" { DLLExport void fillArrayNative(int* data, int count, int* outValue) { std::vector results; for (int i = 0; i < count; i++) { //Fill the array data[i] = results[i]; } *outValue = results.size(); } } 

您还可以使用: std::copy ( data, data+count, results.begin() ); 而不是循环来复制数据。

C#:

 [DllImport("NativePlugin", CallingConvention = CallingConvention.Cdecl)] private static extern void fillArrayNative(IntPtr data, int count, out int outValue); public unsafe void getFillArrayNative(float[] outArray, int count, out int outValue) { //Pin Memory fixed (float* p = outArray) { fillArrayNative((IntPtr)p, count, out outValue); } } 

用法

 const int arraySize = 44500; float[] arrayToFill = new float[arraySize]; void Start() { int length = arrayToFill.Length; int filledAmount = 0; getFillArrayNative(arrayToFill, length, out filledAmount); //You can then loop through it with with the returned filledAmount for (int i = 0; i < filledAmount; i++) { //Do something with arrayToFill[i] } } 

这只是一个例子,它比我之前使用过的所有其他方法都要快。 避免按照Marshal.Copy目前的方式进行操作。 如果您仍然希望按照自己的方式进行操作或使用Marshal.Copy那么这是执行此操作的合适方法,需要在每次调用中分配,复制数据和取消分配内存。

您在getSomeFloats返回的指针由results拥有。 在getSomeFloats返回之前, results的向量析构函数将释放该内存。 当C#代码尝试使用指针时,您正在访问未分配的内存,这会导致未定义的行为。 在您的情况下,大多数数据尚未更改,但其中一些已经更改。 可能已经更改了任何或所有数据(如果内存已被重用),甚至程序崩溃(如果已释放的内存已返回到操作系统)。