当 运行 在 Android 上时,为什么 C# 数组在被 ref 从 C# 传递到 C++ 库后更改为长度 1,但在 Windows 上正常工作?
Why does C# array change to length 1 after being passed by ref from C# to a C++ library when running on Android but works properly on Windows?
当 Android 上的 运行 时,我作为引用从 C# 传递到 C++ 库函数 returns 的数组的长度,而不是其实际长度。
代码在为 windows 编写时工作正常,但为 Android.
编写时却不行
仅供参考,这是一个 Unity 项目,我正在使用 OpenCV。
我在库中有以下函数。
extern "C" void ApplyCannyEffect(Color32 **rawImage, int width, int height)
{
Mat image(height, width, CV_8UC4, *rawImage);
flip(image, image, -1);
Mat edges;
Canny(image, edges, 50, 200);
dilate(edges, edges, (5, 5));
cvtColor(edges, edges, COLOR_GRAY2RGBA);
normalize(edges, edges, 0, 1, NORM_MINMAX);
multiply(image, edges, image);
flip(image, image, 0);
}
Color32 定义为
struct Color32
{
uchar red;
uchar green;
uchar blue;
uchar alpha;
};
在我的 C# 代码中,我通过以下方式声明此函数:
[DllImport("test")]
internal static extern void ApplyCannyEffect(ref Color32[] rawImage, int width, int height);
在 C# 中,我从纹理中获取图像数据。 Unity 的 GetPixels32 函数 returns 每个像素的结构数组,从左下角的像素开始(Unity 的约定,而不是 OpenCV 的)。这些结构有四个字节,每个字节对应每个像素的红色、绿色、蓝色和 alpha 值。然后我检查数组的长度,这是预期的大小(以像素为单位的宽度 * 以像素为单位的高度)。我调用 C++ 函数。然后我检查数组的长度,它是 1。不用说,我没有使用大小为 1 像素的图像。
Color32[] rawImage = texture.GetPixels32();
Debug.Log(rawImage.Length.ToString()); //output is as expected on both Windows and Android
ApplyCannyEffect(ref rawImage, texture.width, texture.height);
Debug.Log(rawImage.Length.ToString()); //output is same as above on Windows but "1" on Android
它按预期在 Windows 上运行,但在 Android 上不起作用。而且我的Windows代码和我的Android代码之间的相关代码没有变化。
这是什么原因造成的?
我对C++不太熟悉。但我没想到需要在 C++ 中将 Color32 参数作为指向指针的指针传递(我从教程中获得了这个示例代码)。这是因为我正在传递一组结构吗?通过在结构数组上使用两个引用运算符,它最终会成为指向数组第一个结构元素的第一个 uchar 的指针吗?这是函数调用后长度变为1的原因吗?
为了测试这一点,我尝试将 Color32 结构数组转换为字节数组,该字节数组的长度是 C# 中 Color32 数组的 4 倍(r、g、b 和数据依次填充字节数组)
private byte[] Color32toByteArray(Color32[] rawImage)
{
int outputIndex = 0;
byte[] output = new byte[rawImage.Length * 4];
for (int c = 0; c < rawImage.Length; c++)
{
output[outputIndex] = rawImage[c].r;
outputIndex++;
output[outputIndex] = rawImage[c].g;
outputIndex++;
output[outputIndex] = rawImage[c].b;
outputIndex++;
output[outputIndex] = rawImage[c].a;
outputIndex++;
}
Debug.Log("rawImage byte array length " + output.Length.ToString());
return output;
}
然后将输出传递给我修改为 Windows C++ dll 的函数:
extern "C" __declspec(dllexport) void ApplyCannyEffect_Byte(unsigned char* rawImage, int width, int height)
{
using namespace cv;
// create an opencv object sharing the same data space
Mat image(height, width, CV_8UC4, rawImage);
// start with flip (in both directions) if your image looks inverted
flip(image, image, -1);
Mat edges;
Canny(image, edges, 50, 200);
dilate(edges, edges, (5, 5));
cvtColor(edges, edges, COLOR_GRAY2RGBA);
normalize(edges, edges, 0, 1, NORM_MINMAX);
multiply(image, edges, image);
// flip again (just vertically) to get the right orientation
flip(image, image, 0);
}
通过以下代码
Color32[] rawImage = texture.GetPixels32();
int length = rawImage.Length;
byte[] rawByteImage = Color32toByteArray(rawImage);
ApplyCannyEffect_Byte(ref rawByteImage, texture.width, texture.height);
rawImage = BytetoColor32Array(rawByteImage, texture.width, texture.height);
texture.SetPixels32(rawImage);
texture.Apply();
Debug.Log((rawByteImage.Length / 4).ToString() + " " + rawImage.Length.ToString());
但这也(在 Windows 上)只是在 ApplyCannyEffect_Byte 调用时使程序崩溃(评论说线路按预期工作,数组长度的输出是正确的)。
为什么第一组代码适用于 Windows,但不适用于 Android?
这可能是包装问题。考虑使用 Unity 的 Color32 结构,它完全适合在本机代码中使用。
你也不能将托管数组作为 ref 传递(因为 ref 也可能添加内部信息,例如 array length before actual数据,被 DLL 代码覆盖),对于这个调用你应该使用
Color32[] rawImage = texture.GetPixels32();
var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(rawImage, 0);
ApplyCannyEffect_Byte(ptr, texture.width, texture.height);
texture.SetPixels32(rawImage);
texture.Apply();
...
// Modified function import:
[DllImport("test")]
internal static extern void ApplyCannyEffect(IntPtr rawImage, int width, int height);
注意:没有转换,因为它实际上并不需要并且会大大降低性能,还会产生无用的 GC 分配。使用本机函数,您可以直接写入 Color32 数组。
注意 2:对于完全无 GC 的实现,考虑使用 NativeArray Texture2D.GetPixelData() 方法,您可以从 NativeArray 获取 IntPtr,并在 SetPixels32 之后处理它或预分配 Color32[] 数组并用 GCHandle 固定它(不是非常方便的解决方案)。
无需更改 DLL 函数签名。
当 Android 上的 运行 时,我作为引用从 C# 传递到 C++ 库函数 returns 的数组的长度,而不是其实际长度。
代码在为 windows 编写时工作正常,但为 Android.
编写时却不行仅供参考,这是一个 Unity 项目,我正在使用 OpenCV。
我在库中有以下函数。
extern "C" void ApplyCannyEffect(Color32 **rawImage, int width, int height)
{
Mat image(height, width, CV_8UC4, *rawImage);
flip(image, image, -1);
Mat edges;
Canny(image, edges, 50, 200);
dilate(edges, edges, (5, 5));
cvtColor(edges, edges, COLOR_GRAY2RGBA);
normalize(edges, edges, 0, 1, NORM_MINMAX);
multiply(image, edges, image);
flip(image, image, 0);
}
Color32 定义为
struct Color32
{
uchar red;
uchar green;
uchar blue;
uchar alpha;
};
在我的 C# 代码中,我通过以下方式声明此函数:
[DllImport("test")]
internal static extern void ApplyCannyEffect(ref Color32[] rawImage, int width, int height);
在 C# 中,我从纹理中获取图像数据。 Unity 的 GetPixels32 函数 returns 每个像素的结构数组,从左下角的像素开始(Unity 的约定,而不是 OpenCV 的)。这些结构有四个字节,每个字节对应每个像素的红色、绿色、蓝色和 alpha 值。然后我检查数组的长度,这是预期的大小(以像素为单位的宽度 * 以像素为单位的高度)。我调用 C++ 函数。然后我检查数组的长度,它是 1。不用说,我没有使用大小为 1 像素的图像。
Color32[] rawImage = texture.GetPixels32();
Debug.Log(rawImage.Length.ToString()); //output is as expected on both Windows and Android
ApplyCannyEffect(ref rawImage, texture.width, texture.height);
Debug.Log(rawImage.Length.ToString()); //output is same as above on Windows but "1" on Android
它按预期在 Windows 上运行,但在 Android 上不起作用。而且我的Windows代码和我的Android代码之间的相关代码没有变化。
这是什么原因造成的?
我对C++不太熟悉。但我没想到需要在 C++ 中将 Color32 参数作为指向指针的指针传递(我从教程中获得了这个示例代码)。这是因为我正在传递一组结构吗?通过在结构数组上使用两个引用运算符,它最终会成为指向数组第一个结构元素的第一个 uchar 的指针吗?这是函数调用后长度变为1的原因吗?
为了测试这一点,我尝试将 Color32 结构数组转换为字节数组,该字节数组的长度是 C# 中 Color32 数组的 4 倍(r、g、b 和数据依次填充字节数组)
private byte[] Color32toByteArray(Color32[] rawImage)
{
int outputIndex = 0;
byte[] output = new byte[rawImage.Length * 4];
for (int c = 0; c < rawImage.Length; c++)
{
output[outputIndex] = rawImage[c].r;
outputIndex++;
output[outputIndex] = rawImage[c].g;
outputIndex++;
output[outputIndex] = rawImage[c].b;
outputIndex++;
output[outputIndex] = rawImage[c].a;
outputIndex++;
}
Debug.Log("rawImage byte array length " + output.Length.ToString());
return output;
}
然后将输出传递给我修改为 Windows C++ dll 的函数:
extern "C" __declspec(dllexport) void ApplyCannyEffect_Byte(unsigned char* rawImage, int width, int height)
{
using namespace cv;
// create an opencv object sharing the same data space
Mat image(height, width, CV_8UC4, rawImage);
// start with flip (in both directions) if your image looks inverted
flip(image, image, -1);
Mat edges;
Canny(image, edges, 50, 200);
dilate(edges, edges, (5, 5));
cvtColor(edges, edges, COLOR_GRAY2RGBA);
normalize(edges, edges, 0, 1, NORM_MINMAX);
multiply(image, edges, image);
// flip again (just vertically) to get the right orientation
flip(image, image, 0);
}
通过以下代码
Color32[] rawImage = texture.GetPixels32();
int length = rawImage.Length;
byte[] rawByteImage = Color32toByteArray(rawImage);
ApplyCannyEffect_Byte(ref rawByteImage, texture.width, texture.height);
rawImage = BytetoColor32Array(rawByteImage, texture.width, texture.height);
texture.SetPixels32(rawImage);
texture.Apply();
Debug.Log((rawByteImage.Length / 4).ToString() + " " + rawImage.Length.ToString());
但这也(在 Windows 上)只是在 ApplyCannyEffect_Byte 调用时使程序崩溃(评论说线路按预期工作,数组长度的输出是正确的)。
为什么第一组代码适用于 Windows,但不适用于 Android?
这可能是包装问题。考虑使用 Unity 的 Color32 结构,它完全适合在本机代码中使用。
你也不能将托管数组作为 ref 传递(因为 ref 也可能添加内部信息,例如 array length before actual数据,被 DLL 代码覆盖),对于这个调用你应该使用
Color32[] rawImage = texture.GetPixels32();
var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(rawImage, 0);
ApplyCannyEffect_Byte(ptr, texture.width, texture.height);
texture.SetPixels32(rawImage);
texture.Apply();
...
// Modified function import:
[DllImport("test")]
internal static extern void ApplyCannyEffect(IntPtr rawImage, int width, int height);
注意:没有转换,因为它实际上并不需要并且会大大降低性能,还会产生无用的 GC 分配。使用本机函数,您可以直接写入 Color32 数组。
注意 2:对于完全无 GC 的实现,考虑使用 NativeArray Texture2D.GetPixelData() 方法,您可以从 NativeArray 获取 IntPtr,并在 SetPixels32 之后处理它或预分配 Color32[] 数组并用 GCHandle 固定它(不是非常方便的解决方案)。
无需更改 DLL 函数签名。