从 C++ 本机插件更新浮点数组
Update float array from C++ native plugin
我在尝试将数组从 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, NaN, 789
我完全被这个难住了。我只是不明白为什么只有一些(大约 7/100)花车返回 NaN
。有人有什么 advice/insight 可以帮助的吗?
谢谢!
您 return 在 getSomeFloats
中的指针归 results
所有。在 getSomeFloats
return 秒之前,results
的矢量析构函数将释放该内存。当 C# 代码尝试使用指针时,您正在访问未分配的内存,这会导致未定义的行为。在您的情况下,大部分数据尚未更改,但其中一些已更改。可能任何或所有数据都可能已被更改(如果内存已被重新使用),甚至程序崩溃(如果释放的内存已 returned 到 OS)。
在您的代码中发现了一些问题:
1。 std::vector<float> results;
在堆栈上声明。函数 returned 时它将消失。将其声明为指针
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++ 端分配数组,return 它到 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
,那么 是合适的方法,它需要在每次调用中分配、复制数据和取消分配内存。
我在尝试将数组从 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, NaN, 789
我完全被这个难住了。我只是不明白为什么只有一些(大约 7/100)花车返回 NaN
。有人有什么 advice/insight 可以帮助的吗?
谢谢!
您 return 在 getSomeFloats
中的指针归 results
所有。在 getSomeFloats
return 秒之前,results
的矢量析构函数将释放该内存。当 C# 代码尝试使用指针时,您正在访问未分配的内存,这会导致未定义的行为。在您的情况下,大部分数据尚未更改,但其中一些已更改。可能任何或所有数据都可能已被更改(如果内存已被重新使用),甚至程序崩溃(如果释放的内存已 returned 到 OS)。
在您的代码中发现了一些问题:
1。 std::vector<float> results;
在堆栈上声明。函数 returned 时它将消失。将其声明为指针
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++ 端分配数组,return 它到 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
,那么