.NET互操作来回复制数组数据,还是固定数组?

时间:2014-07-30 05:46:05

标签: c# c++ .net com com-interop

我有这个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]。但我不认为这是一个强有力的证据,证明数组被固定,而不是来回复制。

2 个答案:

答案 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 == addr2true。因此,显然数组在传递给COM时会被固定而不是被复制。

尽管如此,我找不到任何将此作为CLR实施的硬性要求的文档。例如,Mono可能会或可能不会这样。