Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does .NET interop copy array data back and forth, or does it pin the array?

I have this COM method signature, declared in C#:

void Next(ref int pcch,
          [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
          char[] pchText);

I call it like this:

int cch = 100;
var buff = new char[cch];
com.Next(ref cch, buff);

Does the .NET interop layer first copy the whole array to a temporary unmanaged memory buffer, then copy it back? Or does the array get automatically pinned and passed by reference?

For the sake of trying, I did this in the COM object (C++):

*pcch = 1;
pchText[0] = L'A';
pchText[1] = L'\x38F'; // 'Ώ'

I do get 'Ώ' back when I check buff[1] in C# upon return. But I don't think this is a strong proof that the array gets pinned, rather than copied back and forth.

like image 800
avo Avatar asked Jul 30 '14 05:07

avo


People also ask

How does array copy work in C#?

CopyTo copies all the elements of the current array to the specified destination array. This method should be called from the source array and it takes two parameters. The first being the array you want to copy to, and the second parameter tells it what index of the destination array it should start copying into.

What is Interop C++?

C++ Interop Unlike other . NET languages, Visual C++ has interoperability support that enables managed and unmanaged code to be located in the same application and even in the same file. You then build the C++ code by using the /clr compiler switch to produce a managed assembly.


2 Answers

It isn't always easy to tell, particularly if you use an invalid declaration of course. A char[] can't be marshaled as LPWStr, it has to be LPArray. Now the CharSet attribute plays a role, since you did not specify it, the char[] will be marshaled as an 8-bit char[], not a 16-bit wchar_t[]. The marshaled array element is not the same size (it is not "blittable") so the marshaller must copy the array.

Pretty undesirable, particularly given that your C++ code expects wchar_t. A very easy way to tell in this specific case is not getting anything back in the array. If the array is marshaled by copying then you have to tell the marshaller explicitly that the array needs to be copied back after the call. You'd have to apply the [In, Out] attribute on the argument. You'll get Chinese.

The normal way to tell if the array gets marshaled by copying is by using the debugger. Enable unmanaged debugging in your C# program. Set a breakpoint on the call as well as a breakpoint in the first statement in the native function. When the 1st breakpoint hits, use Debug + Windows + Memory + Memory 1. Put buff in the Address box and switch the display to "4-byte Integer". You'll see the address of the array object, the 4-byte type handle, the 4-byte array length and the array content itself. So you know that if the array isn't copied that the passed addressed is the displayed address plus 8.

Press F5 to continue, the breakpoint in the native function hits. Look at the pchText argument, the debugger tells you its address. If it matches then the marshaller simply passed a pointer. If not then you got a copy of the array.

like image 192
Hans Passant Avatar answered Sep 17 '22 04:09

Hans Passant


Let's do a small experiment. First, let's change your COM method to look like this (in 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;
}

Then, change the C# method signature:

void Next(ref uint pcch, out IntPtr addr, 
    [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
    char[] pbuff); 

Finally, test it like this:

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);

As expected, addr1 == addr2 is true. Thus, apparently the array does get pinned rather than copied when passed to COM.

That said, I couldn't find any documentation which would feature this as a hard requirement for a CLR implementation. E.g., this may or may not be true for Mono.

like image 29
noseratio Avatar answered Sep 20 '22 04:09

noseratio