C# 程序无法从 C++ COM 程序中获取字节数组
Posted
技术标签:
【中文标题】C# 程序无法从 C++ COM 程序中获取字节数组【英文标题】:C# Program Can't Get Byte Array Back From A C++ COM Program 【发布时间】:2021-12-08 13:01:45 【问题描述】:我似乎无法在从 COM C++ 程序填充的 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);
这会正确返回图像大小,但数组中没有任何内容。我还尝试了签名(pImageBytes 的额外间接级别):
[id(5)] HRESULT GetFlawImage([in] ULONG flawID, [in] USHORT station, [in, out] ULONG *pImageSize, [out] BYTE **pImageBytes);
在 C# 中传入一个 IntPtr,但这会返回包含图像字节地址的内存地址,而不是图像字节。
对我做错了什么有什么想法吗?
【问题讨论】:
也许您需要将 C# 内存提供给 C++ 来填充? 当 COM 调用需要返回动态分配的内存时,您不能使用 c++ 堆,因此不要使用 new 来分配图像内存,请使用 ::CoTaskMemAlloc docs.microsoft.com/en-us/windows/win32/api/combaseapi/…。另一种选择是返回一个安全的字节数组:docs.microsoft.com/en-us/archive/msdn-magazine/2017/march/… 你为什么不使用SAFEARRAY
?然后将其声明为retval
而不是out
我同意@Charlieface 的观点——SAFEARRAY
是唯一在 COM 中表示数组的正确方法。您最终可能想要使用 DCOM,然后尝试将指针传递给多个数据,这将严重破坏,而 SAFEARRAY 会正常工作。
@BenVoigt - 这不是真的。您基本上可以在 COM 中使用任何类型(如果使用是进程外的,您必须注意输入/输出/等注释和/或提供代理存根)。事实是,高级语言(阅读:不同于 C/C++)通常更容易使用自动化类型(VARIANT 类型等)。
【参考方案1】:
有多种方法可以从 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# 调用代码(请注意,当它不是 COM 自动化类型时,.NET 类型库导入器除了指针之外什么都不知道,所以它只是盲目地将参数定义为 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/Scripting 对象...):
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);
【讨论】:
这是我需要的。谢谢你。 (虽然我不确定你是否想调用 SafeArrayDestroy(*array);。似乎分配它的代码应该释放它)。 @user1524258 - 发生错误时必须调用 SafeArrayDestroy,是的[out, retval]
对SAFEARRAY
更有意义
@Charlieface - 我认为这不会“更有意义”。这只是我没有做出的更高级别的语义选择(仅对理解它的语言有用),因为这不是这里的主题。以上是关于C# 程序无法从 C++ COM 程序中获取字节数组的主要内容,如果未能解决你的问题,请参考以下文章