从C++本机插件更新float数组

Bre*_*rin 1 c# c++ interop unity-game-engine

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

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


示例C++代码:

extern "C" bool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API getSomeFloats(float** points, int* count) {
        std::vector<float> results;
        std::vector<SOME_TYPE> key_points = <SOME_POINTS>

        for (auto iter = key_points.begin(); iter < key_points.end(); iter++) {
            results.push_back(static_cast<float>(iter->pt.x));
            results.push_back(static_cast<float>(iter->pt.y));
        }

        *points = results.data();
        *count = results.size();

        //<Print results to csv here>

        return true;
}
Run Code Online (Sandbox Code Playgroud)

示例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);

            // <PRINT out resultItems to csv here>

            return resultItems;
        } else {
            Debug.Log ("Something went wrong getting some floats");
            return new float[] { -1, -2 };
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

123,456,789

C#输出(print_out_cs.csv):

123,NaN,789


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

谢谢!

Pro*_*mer 6

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

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

std::vector<float> *results = new std::vector<float>(10);
Run Code Online (Sandbox Code Playgroud)

但请确保还声明一个将在C++端释放它的函数.

2.功能参数不匹配.

你的C++:

getSomeFloats(float** points, int* count, CameraPose* pose)
Run Code Online (Sandbox Code Playgroud)

你的C#:

getSomeFloats (ref IntPtr ptrResultItems, ref int resultItemsLength);
Run Code Online (Sandbox Code Playgroud)

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

3.使用UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API.

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

这应该这样做:

#define DLLExport __declspec(dllexport)

extern "C"
{
    DLLExport void fillArrayNative(float* data, int count, int* outValue) 
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

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(float* data, int count, int* outValue) 
    {
        std::vector<float> results;
        for (int i = 0; i < count; i++)
        {
            //Fill the array
            data[i] = results[i];
        }
        *outValue = results.size();
    }
}
Run Code Online (Sandbox Code Playgroud)

您也可以使用: 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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]
    }
}
Run Code Online (Sandbox Code Playgroud)

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