A.Pissicat
A.Pissicat

Reputation: 3275

How to convert a Byte* from C++ to Byte[] in C#

I have a library in C++ containing a library returning a Byte* :

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

I'm using this library in a program in 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;
}

It works good, but I don't know how to convert the pointer to a Byte array. Someone know a function to do that ?

Upvotes: 0

Views: 1951

Answers (1)

Mr.C64
Mr.C64

Reputation: 42934

A problem of your function interface is that the memory that the function dynamically allocates to return the rotated image byte array, must be allocated with the same memory allocator that the C# side (or whatever client code) will use to release the memory.

In other words, the module that allocates the memory and the module that frees it must use the same allocator.

When I needed to pass some array data between native code and C# code I successfully used Safe Arrays. On the C++ side, you can use ATL's CComSafeArray to simplify the safe array programming; on the other hand, C# and the CLR understand safe arrays well, so it's easy to get the array data in C# and consume it in managed code.

You can use a function like this to produce a safe array of bytes in C++ (in your case, the safe array will store the rotated image data):

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;
}

On the C# side, you can use this PInvoke signature:

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

The VT_UI1 enum field tells the .NET Marshaller that the safe array contains bytes.

You can get the array data in C# with simple code like this:

byte[] data;
ProduceSafeArrayOfBytes(out data);

As you can see, in your C# code you deal with a simple byte[] array; all the proper data marshalling (including freeing memory) happens automatically under the hood.

You can modify the aforementioned skeleton code, adding the other function parameters, like your image width and height.


As an alternative, another option would be developing a tiny bridging layer using C++/CLI, to convert from raw C-style arrays to .NET managed arrays.

Anyway, the note on your DLL function interface to use a common memory allocator for allocating and releasing the array memory is still valid.


As a third option, if you can modify your DLL function interface, you can require the caller to allocate an array and pass it to the DLL function. The function will write the result data in this caller-allocated array.

This would simplify the memory management, as you give the DLL function a block of memory that is already allocated by the caller. The caller will have the responsibility for both allocating and releasing that memory.

So, your DLL function would look like this:

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

The result array is allocated by the caller, who is also responsible to free it. The function just writes its output in this caller-allocated array.

PInvoke would look like this:

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

Upvotes: 3

Related Questions