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

时间:2017-10-13 14:16:22

标签: c# c++ arrays pinvoke

我有一个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;
}

它运行良好,但我不知道如何将指针转换为Byte数组。有人知道这样做的功能吗?

1 个答案:

答案 0 :(得分:3)

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

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

当我需要在本机代码和C#代码之间传递一些数组数据时,我成功使用了安全数组。在C ++方面,您可以使用ATL的CComSafeArraysimplify 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 enum字段告诉.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);