我有这个COM方法签名,在C#中声明:
void Next(ref int pcch,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
char[] pchText);
我称之为:
int cch = 100;
var buff = new char[cch];
com.Next(ref cch, buff);
.NET互操作层是否首先将整个数组复制到临时的非托管内存缓冲区,然后将其复制回来?或者数组是否自动固定并通过引用传递?
为了尝试,我在COM对象(C ++)中完成了这个:
*pcch = 1;
pchText[0] = L'A';
pchText[1] = L'\x38F'; // 'Ώ'
我在返回时在C#中检查'Ώ'
时会得到buff[1]
。但我不认为这是一个强有力的证据,证明数组被固定,而不是来回复制。
答案 0 :(得分:9)
总是很容易辨别,特别是当你使用无效声明时。 char []无法作为LPWStr封送,它必须是LPArray。现在CharSet属性起作用,因为你没有指定它,char []将被编组为8位char [],而不是16位wchar_t []。封送的数组元素大小不同(它不是" blittable")因此编组器必须复制数组。
非常不受欢迎,特别是考虑到您的C ++代码需要wchar_t。在这个特定情况下,一个非常简单的方法是在数组中没有得到任何回报。如果通过复制对数组进行封送处理,则必须明确告诉编组程序在调用后需要将数组复制回来。您必须在参数上应用[In, Out]
属性。你会得到中文。
判断数组是否通过复制进行编组的正常方法是使用调试器。在C#程序中启用非托管调试。在调用中设置断点以及在本机函数的第一个语句中设置断点。当第一个断点点击时,使用Debug + Windows + Memory + Memory 1.将 buff 放入地址框,并将显示切换到" 4字节整数"。您将看到数组对象的地址,4字节类型句柄,4字节数组长度和数组内容本身。所以你知道如果数组没有被复制,那么传递的地址就是显示的地址加上8。
按F5继续,本机功能中的断点点击。查看 pchText 参数,调试器会告诉您它的地址。如果匹配则编组器只是传递一个指针。如果没有,那么你得到了数组的副本。
答案 1 :(得分:6)
我们做一个小实验。首先,让我们将您的COM方法更改为这样(在C ++中):
STDMETHODIMP CComObject::Next(ULONG* pcch, int* addr, OLECHAR* pbuff)
{
pbuff[0] = L'A';
pbuff[1] = L'\x38F';
*addr = (int)pbuff;
*pcch = 1;
return S_OK;
}
然后,更改C#方法签名:
void Next(ref uint pcch, out IntPtr addr,
[In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
char[] pbuff);
最后,像这样测试:
uint cch = 10;
var buff = new char[cch];
IntPtr addr1;
unsafe
{
fixed (char* p = &buff[0])
{
addr1 = (IntPtr)p;
}
}
IntPtr addr2;
com.Next(ref cch, out addr2, buff);
Console.WriteLine(addr1 == addr2);
正如所料,addr1 == addr2
是true
。因此,显然数组在传递给COM时会被固定而不是被复制。
尽管如此,我找不到任何将此作为CLR实施的硬性要求的文档。例如,Mono可能会或可能不会这样。