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);
我似乎无法在 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);