3

I have an application that marshals data between C# and C++ using PInvoke. The application is a native C++ app that starts the CLR with the C# part internally.

At some point I have to marshal data from C++ to C# and I'm using Marshal.PtrToStructure for this. The C++ part basically looks like this:

struct X {};

auto xPtr = new X; // somewhere in the application

callCSharp(xPtr); // somewhere else

and the C# part like this:

public void callCSharp(IntPtr xPtr)
{
    var x = Marshal.PtrToStructure<X>(xPtr);
}

This code works on my Windows 10 machine but I'm not sure if I could run into trouble with this code. Lifetime management is all done in C++ so I don't have to allocate or free anything on C# side. However I'm not sure if the CLR can always access the native heap. xPtr is allocated using new and is therefore located on the native heap of the C++ application. Marshal.PtrToStructure<X>(xPtr) then tries to read memory at that location but I don't know if this can cause problems.

I've read this which indicates that both the C++ application and the CLR use the same heap (GetProcessHeap that is) so this seems to support my findings for Windows 10, but it might be different for other OS versions.

Is my sample code above sane? Are there any pitfalls here? Does this code work in Windows 7, 8 and 10?

Timo
  • 9,269
  • 2
  • 28
  • 58

1 Answers1

3

Yes that's perfectly reasonable and safe as long as the C/C++ code doesn't free the memory. Note that it isn't always necessary (or desirable) to use Marshal here; depending on what <X> is, you can do this in other ways too, including:

  • unsafe (cast a void* to a X*)
  • Unsafe.AsRef<X>(...) (which casts a void* to a ref X)
  • new Span<X>(...) (which creates a span of some number of X from a void*; a span is like a vector, but talking to arbitrary memory)

All of these are zero-copy approaches, meaning your C# code is then talking directly to exactly the same memory space, not a local snapshot; but if you dereference the pointer (managed or unmanged) into a non-reference local, then it will make a copy.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    Very interesting. So the first approach does a copy when casting? What if my signature were `public unsafe void callCSharp(X* xPtr)`? I definitely need a copy into the managed heap, I don't want to operate on the same object. – Timo Aug 24 '20 at 13:26
  • 1
    @Timo when dealing with this kind of data, you often use a `struct`, not a `class`, so the difference is moot; as for what it does: `X*` is an *unmanaged* reference and `ref X` is a *managed* reference; both are simply pointers to the data. In this context, the differences between a managed reference and an unmanaged reference are that: managed references don't need `unsafe` code, but you can't store a managed reference in a field (it is only valid on the stack). In the general case, there are some other nuances between them, but they don't apply here. The main difference, then, is `.` vs `->` – Marc Gravell Aug 24 '20 at 15:34
  • 1
    @Timo as for a "copy when casting" - you copy the *unmanaged reference* when casting - I can't talk about anything else without seeing very specific code examples, as in terms of the value: it is possible to do it both with and without copying – Marc Gravell Aug 24 '20 at 15:37
  • Alright thanks for the clarification. I was confused previously because you said the last two were zero-copy approaches so I thought the first one would somehow cause a copy. Everything's clear now. – Timo Aug 24 '20 at 15:46
  • @Timo yeah, I clarified that - my bad: they're all equally zero-copy as long as you don't dereference the value into a local – Marc Gravell Aug 24 '20 at 15:48