C# 程序无法从 C++ COM 程序取回字节数组

C# Program Can't Get Byte Array Back From A C++ COM Program

我似乎无法在 C# 程序中从 COM C++ 程序中获取字节数组。 C# 程序包含对 C++ DLL 的引用,并通过以下方式实例化:

_wiCore = new WebInspectorCoreLib.WICore();

实际调用

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

这 returns 图像大小正确,但数组中没有任何内容。我还尝试了签名(pImageBytes 的额外间接级别):

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

并且在 C# 中传递了一个 IntPtr,但是这个 returns 包含图像字节地址的内存地址,而不是图像字节。

对我做错了什么有什么想法吗?

有多种方法可以从 C++ 传回数组。

例如,您可以像您尝试的那样使用原始字节数组。它可以工作,但在 .NET 中不是很实用,因为它不是 .NET 喜欢的 COM automation type

所以,假设我们有这个 .idl:

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

这是一个示例本机实现:

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

还有一个示例 C# 调用代码(请注意,.NET 类型库导入程序在它不是 COM 自动化类型时除了指针之外什么都不知道,所以它只是盲目地将参数定义为 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);
}

注意:这在进程外场景中不起作用,因为 .idl 不完整,无法支持它等。

或者您可以使用 COM 自动化类型,例如 SAFEARRAY(或包装 VARIANT)。这也将允许您将它与其他语言一起使用(例如 VB/VBA、脚本引擎等)

所以,我们可以拥有这个 .idl:

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

本机实现示例(有点复杂,因为 COM 自动化不是针对 C/C++,而是针对 VB/VBA/脚本对象...):

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

正如预期的那样,示例 C# 代码要简单得多:

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