如何在 C# 中将 Byte* 从 C++ 转换为 Byte[]

Posted

技术标签:

【中文标题】如何在 C# 中将 Byte* 从 C++ 转换为 Byte[]【英文标题】:How to convert a Byte* from C++ to Byte[] in C# 【发布时间】:2018-03-25 17:04:45 【问题描述】:

我有一个 C++ 库,其中包含一个返回 Byte* 的库:

typedef unsigned char Byte;
Byte* RotateImage90(Byte* data, int w, int h);

我在 C# (Xamarin) 的程序中使用这个库:

[DllImport("libCpp", EntryPoint = "RotateImage90")]
public static extern IntPtr rotate90(byte[] data, int w, int h);

Byte[] test(Byte[] data, int w, int h)

    IntPtr ptr = rotate90(data, w, h);
    Byte[] img = ????;// <= function missing
    return img;

效果很好,但我不知道如何将指针转换为字节数组。有人知道这样做的功能吗?

【问题讨论】:

这个函数在C++程序中是不可能正确使用的,当你pinvoke它时也不会变得更好。 它似乎在 C++ 中工作。我在函数RotateImage90(C++ 中)中添加了一个断点,它接收到一个指针,我检查了值,它对应于 C# 发送的data 中的值。 谁释放了 C++ 代码返回的字节数组?你怎么知道在指针指向的地址要读取多少字节?返回的指针和进去的字节数组一样吗? @xxbbcc 返回相同大小的字节数组,高度和宽度被反转(这是一个图像旋转)。所以我知道要读取的字节数。 C++ 函数返回新图像的指针。 C# 函数必须获取数组中的所有字节并在最后释放它。 不返回指针,而是强制调用者传入一个已经分配的字节数组(连同大小)。返回写入字节数组的字节数。这样,调用者可以以任何他们认为合适的方式分配/释放数组。 【参考方案1】:

您的函数接口的一个问题是函数动态分配以返回旋转图像字节数组的内存必须使用 C# 端(或任何客户端代码)的 same 内存分配器分配) 将用于释放内存。

换句话说,分配内存的模块和释放它的模块必须使用 same 分配器。

当我需要在本机代码和 C# 代码之间传递一些数组数据时,我成功地使用了 安全数组。在 C++ 端,您可以使用 ATL 的 CComSafeArray 到 simplify the safe array programming;另一方面,C# 和 CLR 对安全数组的理解很好,因此很容易在 C# 中获取数组数据并在托管代码中使用。

您可以使用这样的函数在 C++ 中生成一个安全的字节数组(在您的情况下,安全数组将存储旋转后的图像数据):

extern "C" HRESULT __stdcall ProduceSafeArrayOfBytes(/* [out] */ SAFEARRAY** ppsa)

    HRESULT hr = S_OK;

    try
     
        // Create the safe array to be returned to the caller
        CComSafeArray<BYTE> sa( /* Element count */);

        // Fill the safe array data.
        // You can use a simple sa[i] syntax,
        // where 'i' is a 0-based index
        ...

        // Return the safe array to the caller (transfer ownership)
        *ppsa = sa.Detach();
    
    // Convert exceptions to HRESULT return codes
    catch (const CAtlException& e)
    
        hr = e;
    
    catch (const std::exception& )
     
        hr = E_FAIL;
    

    return hr;

在 C# 端,您可以使用此 PInvoke 签名:

[DllImport("NativeDll.dll", PreserveSig = false)]
public static extern void ProduceSafeArrayOfBytes(
    [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
    out byte[] result
);

VT_UI1 枚举字段告诉 .NET Marshaller 安全数组包含字节。

你可以用这样的简单代码在 C# 中获取数组数据:

byte[] data;
ProduceSafeArrayOfBytes(out data);

如您所见,在您的 C# 代码中,您处理的是一个简单的 byte[] 数组;所有正确的数据编组(包括释放内存)都会在后台自动进行。

您可以修改上述骨架代码,添加其他功能参数,例如您的图像宽度和高度。


作为替代方案,另一种选择是使用 C++/CLI 开发一个小型桥接层,将原始 C 样式数组转换为 .NET 托管数组。

无论如何,你的 DLL 函数接口上关于使用公共内存分配器来分配和释放数组内存的说明仍然有效。


作为第三种选择,如果您可以修改您的 DLL 函数接口,您可以要求调用者分配一个数组并将其传递给 DLL 函数。该函数会将结果数据写入这个调用者分配的数组

这将简化内存管理,因为您为 DLL 函数提供了一个由调用者已经分配的内存块。调用者将负责分配和释放该内存。

因此,您的 DLL 函数将如下所示:

extern "C" void __cdecl RotateImage90(Byte* result, Byte* data, int width, int height);

结果数组由调用者分配,调用者也负责释放它。 该函数只是将其输出写入此调用方分配的数组中。

PInvoke 看起来像这样:

[DllImport("NativeDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void RotateImage90(byte[] result, byte[] data, int width, int height);

【讨论】:

.net 也可以理解原始字节数组(如果使用得当),如果没有任何东西(阅读:COM、自动化客户端、VB/VBA/脚本等)强制您使用它们,SAFEARRAY 就有点矫枉过正了.

以上是关于如何在 C# 中将 Byte* 从 C++ 转换为 Byte[]的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中将 int[] 转换为 byte[]

如何在 C# 中将字节 [] 转换为日期时间?

如何在 C# 中将 List<byte> 转换为 byte[]?

如何在C#中将字节转换为字符串[重复]

无法在 C# 中将类型 'byte[]' 隐式转换为 'byte?[]'

如何在 C# 中将字符串转换为字节 []