1

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?

6
  • Maybe you need to provide the C# memory to C++ to fill it in? Commented Oct 21, 2021 at 19:06
  • When a COM call needs to return dynamically allocated memory you can't use the c++ heap so don't use new to allocate the image memory, use ::CoTaskMemAlloc learn.microsoft.com/en-us/windows/win32/api/combaseapi/…. Another option is to return a safearray of bytes: learn.microsoft.com/en-us/archive/msdn-magazine/2017/march/… Commented Oct 21, 2021 at 19:07
  • 2
    Why don't you use a SAFEARRAY? And then declare it as retval instead of out Commented Oct 21, 2021 at 19:08
  • I agree with @Charlieface -- SAFEARRAY is the only correct way to represent an array in COM. You might eventually want to use DCOM, and then trying to pass pointers to more than one datum around will break horribly, while SAFEARRAY will work just fine. Commented Oct 21, 2021 at 19:30
  • 1
    @BenVoigt - This is not true. You can use basically any type in COM (if usage is out-of-process, you must take care of in/out/etc. annotations and/or provide proxy stub). What is true is it's in general easier for higher level languages (read: different from C/C++) to use automation types (VARIANT types et al.), though. Commented Oct 21, 2021 at 20:36

1 Answer 1

1

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);
Sign up to request clarification or add additional context in comments.

4 Comments

This is what I needed. Thank you. (Although I'm not sure you want to call SafeArrayDestroy(*array);. Seems like the code that allocated it should release it).
@user1524258 - SafeArrayDestroy must be called in case of error, yes
[out, retval] would make more sense for the SAFEARRAY
@Charlieface - I don't think it would make "more sense". It's only a higher level semantic choice (only useful for languages that understand it) that I didn't make as this is not the subject here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.