使用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
。 有没有人有任何可能有帮助的建议/见解?
谢谢!
在您的代码中发现几个问题:
1 。 std::vector
在堆栈上声明。 它会在函数返回时消失。 将其声明为指针
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#代码尝试使用指针时,您正在访问未分配的内存,这会导致未定义的行为。 在您的情况下,大多数数据尚未更改,但其中一些已经更改。 可能已经更改了任何或所有数据(如果内存已被重用),甚至程序崩溃(如果已释放的内存已返回到操作系统)。