从 C++ 原生插件更新浮点数组

Posted

技术标签:

【中文标题】从 C++ 原生插件更新浮点数组【英文标题】:Update float array from C++ native plugin 【发布时间】:2019-04-11 05:45:19 【问题描述】:

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

问题:从 C++ 到 C# 的浮点数组在结果数组中产生了一些 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;

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

输出示例: 举个例子: C++ 输出(print_out.csv):

123、456、789

C# 输出 (print_out_cs.csv):

123, 南, 789


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

谢谢!

【问题讨论】:

这些值是真实值吗? (示例中的那些) @Gusman 是的,这些是真正的价值观。值的范围可以从 0 到 5000。 【参考方案1】:

在您的代码中发现了一些问题:

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

std::vector<float> *results = new std::vector<float>(10);

但请确保同时声明一个将在 C++ 端释放它的函数。

2.函数参数不匹配。

你的 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)时使用此选项。在这种情况下你没有使用它。

应该这样做:

#define DLLExport __declspec(dllexport)

extern "C"

    DLLExport void fillArrayNative(float* data, int count, int* outValue) 
    
    

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();
    

您也可以使用: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,那么here 是合适的方法,它需要在每次调用中分配、复制数据和取消分配内存。

【讨论】:

感谢您的详细回复。如果我得到一些运作良好的东西,我会研究你的建议并报告。哦,我已经更新了 C++ 方法签名,因为它不正确。 应该没有问题。让我知道是否有。记住要慢一点开始,测试然后添加更多代码和测试,这样你就会知道如果它停止工作会发生什么。 我希望在报告之前尝试您的“推荐方法”,但没有时间。尽管如此,即使遵循您第一点的一般建议,现在也足以让我通过。当我开始使用它时,我会报告推荐方法的结果。感谢您的帮助!【参考方案2】:

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

【讨论】:

以上是关于从 C++ 原生插件更新浮点数组的主要内容,如果未能解决你的问题,请参考以下文章

C++ 将浮点数保存并加载到二进制文件中,由指针寻址

c++浮点数的格式化输出

在 C++ 中正确转换浮点数

C++浮点数误差是啥

为啥C++里面浮点与整数相乘的结果跟在计算器里的不一样呢?浮点我选的float

字节数组byte[]和整型,浮点型数据的转换——Java代码