Search code examples
c#c++memory-managementinteropclr

Is Marshal.PtrToStructure on pointers that point to the native C++ heap sane?


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?


Solution

  • 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.