互操作:具有 C# COM 服务器字节数组参数的 C++ COM 客户端 - 数组是复制入/出还是直接访问?
Posted
技术标签:
【中文标题】互操作:具有 C# COM 服务器字节数组参数的 C++ COM 客户端 - 数组是复制入/出还是直接访问?【英文标题】:Interop: C++ COM client with C# COM server byte array parameter - is array copied in/out or accessed directly? 【发布时间】:2012-07-10 14:09:06 【问题描述】:我们有一个具有以下接口和类声明的 C#/.Net COM 服务器(COM 可见程序集):
[ComVisible(true)]
[Guid( "..." )]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITest
void TestMethod( [In] int nNumElements,
[In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] byte [] bArray );
[ComVisible(true)]
[Guid( "..." )]
[ClassInterface( ClassInterfaceType.None )]
[ProgId( "CSCOMServer.CSTest" )]
public class CSTest : ITest
public void TestMethod( int nNumElements, byte [] bArray )
for (int i = 0; i < bArray.Length; i++)
bArray [i] = (byte)i;
生成的 IDL/typelib 是:
...
interface ITest : IUnknown
HRESULT _stdcall TestMethod(
[in] long nNumElements,
[in, out] unsigned char* bArray);
;
...
coclass CSTest
interface _Object;
[default] interface ITest;
;
我们的目标是能够从 C++ COM 客户端使用它,使用以下代码可以正常工作:
int iSize = 10;
ITest *test;
byte *buf = (byte*)CoTaskMemAlloc( iSize * sizeof(byte) );
CoCreateInstance( CLSID_CSTest, NULL, CLSCTX_INPROC_SERVER, IID_ITest, (void **) &test );
test->TestMethod( iSize, buf );
缓冲区由 C++ 分配并传递给 C# 用于填充。 一切正常,在 TestMethod() 完成后,C++ 数组 (buf) 包含由 C# 方法设置的正确值。
问题是关于效率: Interop 包装器是否在编组(“In”阶段)然后返回(“Out”阶段)期间执行数组复制,或者 C# 方法是否直接在传入的内存(可能被固定)上操作?
【问题讨论】:
【参考方案1】:不,这里肯定需要一份副本,因为您要求进行结构更改。 CLR 必须创建一个托管数组对象以满足 byte[] 参数类型要求。并且源数组不能按原样使用,因为它只是一块原始内存。其他方式 (byte[] to unsigned char*) 可以工作,但这里不是这样。
数组的普通 COM 自动化类型是 SAFEARRAY 顺便说一句,没有速度优势,但有更多的 COM 客户端可以使用您的服务器。
【讨论】:
我同意 SAFEARRAY 是要走的路。在我们的例子中,我们被困在处理一个用 C++ 编写的遗留 COM IDL/DLL 中,而托管版本 (C#) 需要模仿它。因此,符合数组方法的参数...当然,可能对相当大的数组进行双重复制并不是很吸引人。不幸的是,在这种特殊情况下,在编组过程中,文档对于复制或缺少文档不是很清楚......如果您有任何指向该主题的文档(MSDN 或其他)的链接,它肯定也会有所帮助。非常感谢您的意见! 我必须同意这个答案 - 鉴于我指定的方法签名,必须制作一份副本。谢谢!【参考方案2】:在这种情况下,将在非托管代码和托管代码之间传递引用。 即使未指定 in/out 参数,“固定优化”也会在某些情况下为您执行此操作(但用作 tlib 时,始终必须指定 in/out 修饰符)。
您可以假设原始类型的一维数组总是固定在(通过引用传递)在同一单元上运行的非托管代码和托管代码之间。
您可以在此处阅读更多信息: http://msdn.microsoft.com/en-us/library/z6cfh6e6(v=vs.80).aspx
有关“固定优化”的更多信息: http://msdn.microsoft.com/en-us/library/23acw07k.aspx
【讨论】:
我仍然不清楚的是,我们正在处理托管 COM 服务器和非托管客户端,而提到的文章处理的是相反的情况。此外,[将数组参数传递给 .NET 代码/C 样式数组] 部分的最后一句提到 互操作封送拆收器使用 CoTaskMemAlloc 和 CoTaskMemFree 方法来分配和检索内存...( msdn.microsoft.com/en-us/library/…) 这是否意味着封送器可能分配内存用于复制? 我想是的,亚历克斯。有时 marshaler 可以根据公寓模型和数组类型复制数组。但是,在原始类型数组上,我认为情况并非如此。存储在内存中的原始类型由托管和非托管代码“按原样”读取。例如,Int32 在内存中以相同的方式在托管和非托管代码上表示。尽管 MS 文档对此并不清楚,但将 Int32 编组为与它已经相同的表示没有任何目的;) 但是在其他数据结构中,封送拆收器必须对那个数组做一些工作,所以它必须模拟两边的输入/输出,但仍然制作数组的副本。 明白。非常感谢!动态确定是否发生复制肯定会很好......以上是关于互操作:具有 C# COM 服务器字节数组参数的 C++ COM 客户端 - 数组是复制入/出还是直接访问?的主要内容,如果未能解决你的问题,请参考以下文章
字符串数组 C# 与 C++ dll 的互操作性;从 C# 到 C++ dll 的字符串数组,它设置数据并将其发送回 c#