Home > Net >  C# Program Can't Get Byte Array Back From A C COM Program
C# Program Can't Get Byte Array Back From A C COM Program

Time:10-22

I can't seem to get a byte array in a C# program filled from a COM C program. The C# program includes a reference to the C DLL and is instantiated by:

_wiCore = new WebInspectorCoreLib.WICore();

Actual call

uint imageSize = *image byte count*;  // set to size of image being retrieved
var arr = new byte[imageSize];
_wiCore.GetFlawImage(1, 0, ref imageSize, out arr);

C IDL:

[id(5)] HRESULT GetFlawImage([in] ULONG flawID, [in] USHORT station, [in, out] ULONG *pImageSize, [out] BYTE *pImageBytes);

This returns the image size correctly, but nothing in the array. I've also tried the signature (extra level of indirection for pImageBytes):

[id(5)] HRESULT GetFlawImage([in] ULONG flawID, [in] USHORT station, [in, out] ULONG *pImageSize, [out] BYTE **pImageBytes);

and in C# passing in an IntPtr but this returns the address of memory that contains the address of the image bytes, not the image bytes.

Any thoughts on what I'm doing wrong?

CodePudding user response:

There are multiple ways to pass an array back from C .

For example, you can use a raw byte array like you were trying to do. It works but it's not very practical from .NET because it's not a COM automation type which .NET loves.

So, let's say we have this .idl:

interface IBlah : IUnknown
{
    HRESULT GetBytes([out] int *count, [out] unsigned char **bytes);
}

Here is a sample native implementation:

STDMETHODIMP CBlah::GetBytes(int* count, unsigned char** bytes)
{
    if (!count || !bytes)
        return E_INVALIDARG;

    *count = numBytes;
    *bytes = (unsigned char*)CoTaskMemAlloc(*count);
    if (!*bytes)
        return E_OUTOFMEMORY;

    for (unsigned char i = 0; i < *count; i  )
    {
        (*bytes)[i] = i;
    }
    return S_OK;
}

And a sample C# calling code (note the .NET type lib importer doesn't know anything beyond pointers when it's not a COM automation type, so it just blindly defines the argument as an IntPtr):

var obj = (IBlah)Activator.CreateInstance(myType);

// we must allocate a pointer (to a byte array pointer)
var p = Marshal.AllocCoTaskMem(IntPtr.Size);
try
{
    obj.GetBytes(out var count, p);

    var bytesPtr = Marshal.ReadIntPtr(p);
    try
    {
        var bytes = new byte[count];
        Marshal.Copy(bytesPtr, bytes, 0, bytes.Length);
        // here bytes is filled
    }
    finally
    {
        // here, we *must* use the same allocator than used in native code
        Marshal.FreeCoTaskMem(bytesPtr);
    }
}
finally
{
    Marshal.FreeCoTaskMem(p);
}

Note: this won't work in out-of-process scenario as the .idl is not complete to support this, etc.

Or you can use a COM Automation type such as SAFEARRAY (or a wrapping VARIANT). which also would allow you to use it with other languages (such as VB/VBA, Scripting engines, etc.)

So, we could have this .idl:

HRESULT GetBytesAsArray([out] SAFEARRAY(BYTE)* array);

This sample native implementation (a bit more complex, as COM automation was not meant for C/C , but for VB/VBA/Scripting object...):

STDMETHODIMP CBlah::GetBytesAsArray(SAFEARRAY** array)
{
    if (!array)
        return E_INVALIDARG;

    // create a 1-dim array of UI1 (byte)
    *array = SafeArrayCreateVector(VT_UI1, 0, numBytes);
    if (!*array)
        return E_OUTOFMEMORY;

    unsigned char* bytes;
    HRESULT hr = SafeArrayAccessData(*array, (void**)&bytes); // check errors
    if (FAILED(hr))
    {
        SafeArrayDestroy(*array);
        return hr;
    }

    for (unsigned char i = 0; i < numBytes; i  )
    {
        bytes[i] = i;
    }

    SafeArrayUnaccessData(*array);
    return S_OK;
}

And the sample C# code is much simpler, as expected:

var obj = (IBlah)Activator.CreateInstance(myType);
obj.GetBytesAsArray(out var bytesArray);
  • Related