C# NET interop是来回复制阵列数据,还是固定阵列?

C# NET interop是来回复制阵列数据,还是固定阵列?,c#,c++,.net,com,com-interop,C#,C++,.net,Com,Com Interop,我有这个COM方法签名,用C#声明: 我这样称呼它: int cch = 100; var buff = new char[cch]; com.Next(ref cch, buff); uint cch = 10; var buff = new char[cch]; IntPtr addr1; unsafe { fixed (char* p = &buff[0]) { addr1 = (IntPtr)p; } } IntPtr addr2;

我有这个COM方法签名,用C#声明:

我这样称呼它:

int cch = 100;
var buff = new char[cch];
com.Next(ref cch, buff);
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);
NET互操作层是否先将整个阵列复制到临时非托管内存缓冲区,然后再将其复制回来?或者数组是否自动固定并通过引用传递

为了尝试,我在COM对象(C++)中做了以下操作:


当我在返回时检查C#中的
buff[1]
时,我确实得到了
“Ώ”
。但我不认为这是数组被固定而不是来回复制的有力证据。

这并不总是很容易判断,尤其是当使用无效声明时。char[]不能作为LPWStr封送,它必须是LPArray。现在CharSet属性起作用了,因为您没有指定它,char[]将被封送为8位char[],而不是16位wchar\u t[]。封送处理的数组元素大小不同(它不是“blittable”),因此封送处理程序必须复制数组

非常不受欢迎,特别是考虑到C++代码期望W查尔夫T。在这种情况下,一种非常简单的方法是不从数组中获取任何内容。如果数组是通过复制封送的,那么您必须明确地告诉封送器需要在调用后将数组复制回。您必须对参数应用

[In,Out]
属性。你会得到中文的

判断数组是否通过复制封送的正常方法是使用调试器。在C#程序中启用非托管调试。在调用上设置断点,并在本机函数的第一条语句中设置断点。当第一个断点到达时,使用Debug+Windows+Memory+Memory 1。将buff放入地址框,并将显示切换为“4字节整数”。您将看到数组对象的地址、4字节类型句柄、4字节数组长度和数组内容本身。因此,您知道如果未复制数组,则传递的地址是显示的地址加上8


按F5键继续,本机函数中的断点将命中。查看pchText参数,调试器会告诉您它的地址。如果匹配,则封送拆收器只传递一个指针。如果没有,那么您就得到了阵列的副本。

让我们做一个小实验。首先,让我们将COM方法更改为如下(在C++中):

STDMETHODIMP ccombject::Next(ULONG*pcch,int*addr,ollechar*pbuff)
{
pbuff[0]=L'A';
pbuff[1]=L'\x38F';
*地址=(int)pbuff;
*pcch=1;
返回S_OK;
}
然后,更改C#方法签名:

void Next(ref uint pcch, out IntPtr addr, 
    [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
    char[] pbuff); 
最后,按如下方式进行测试:

int cch = 100;
var buff = new char[cch];
com.Next(ref cch, buff);
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来说,这可能是真的,也可能不是真的。

这并不能回答这个问题,因为这一切到底是如何实现的,但在这种情况下,pchText参数隐含地是In参数。它只是碰巧工作,因为您正在进行(并且它可能只是作为一个指针实现的)。尝试使用进程外的COM服务器,它将无法工作(并且您的C++代码将只会在进程中处理存根内存)。如果它确实是out或ref参数,则需要向其添加一些ref或out。@SimonMourier,很抱歉,我没有澄清这一点,但它只是一个进程内DLL COM对象,没有代理/存根。每个COM接口都应该在其内部或外部工作process@SimonMourier,我同意,我的意思是,在我们的项目中,它只在STA线程的进程中使用。不管怎样,我按照您的建议添加了
[In,Out]
:.Hmmm,字符集的问题在于它应用于整个函数,而不是单个参数。而且它不会影响
char[]
,但会影响指定为
LPTStr
的字符串参数。实现字符串缓冲区的正确方法是使用
StringBuilder
。。。您可以将其预分配到所需的大小,还可以指定
LPStr
LPWStr
。Hans,再次感谢,它实际上是一个
LPArray
,我只是盲目地从中复制了旧代码。实际上,
char
非常有效。我认为
CharSet
p/invoke
很重要,但对COM不重要(我们之前也讨论过)。例如,我尝试了
Ώ
pchText[0]=L'\x38F'
),它又回到了C。这里有些记录:我无法复制,地址不同。编辑:我可以在同一个公寓里复制。但是,无论是否在同一单元中,数据似乎都没有写入
char[]
。我找到了原因,我使用了(在IDL方面)LPWSTR*pszData,我正在
CoTaskMemAlloc
中。正在写入的是内存地址,而不是实际的字符。@PauloMadeira,我的回答假设相同的COM单元。线程间和进程间COM封送处理要复杂得多,但至少.NET到COM互操作部分没有创建中间缓冲区(在上面的代码中)。然后,COM marshaller可能正在创建一个,但它也不知道它处理的是.NET对象。