Search code examples
c#c++clrpinvoke

Pin data for P/Invoke access


I am passing data from C#-Code to C++-Code and vice versa using P/Invoke. This works fine so far.

Lately I read a few articles (e.g. this one) about the need of pinning these data because the GC might rearrange or delete them while C++ is performing its tasks.

I consulted some Microsoft articles but for me they are not entirely clear concerning when pinning needs to be done manually. I understood this article in the way that the CLR assures that no problems can arise when the GC collects. It does that by either pinning data or copying them into unmanaged memory where the GC does not collect. So for me this means that the programmer does not need to take care of pinning. The example in this article doesn't show any pinning either. I am still not sure though whether my conclusion is correct.

Digging further I found some more information for specific data types I use in my code:

int, long: Passed by value - must therefore not be pinned. But what about ref int?

IntPtr: I use AllocHGlobal() which allocates space in the unmanaged memory. The GC does not touch that. So no pinning is necessary.

byte[]: Passed by reference but pinned automatically. See here: As an optimization, arrays of blittable types and classes that contain only blittable members are pinned instead of copied during marshaling.

string: Passed by reference but pinned automatically. See here: Pinning is automatically performed during marshaling for objects such as String, however you can also manually pin memory using the GCHandle class.

string[], 'custom struct with strings': Here I am really not sure. Does the sentence "Pinning is automatically performed during marshaling for objects such as String [...]" include string arrays and custom structs?

At the moment I don't pin anything and the code works fine. Even when I force the GC to collect while C++ is performing its tasks. But of course this doesn't mean that it will always work fine. I use .Net Framework 4.8.

Do I need to do pinning for the mentioned data types?


Solution

  • The rules here are conditional to what you're invoking, so it is only possible to give vague advice without a specific example, but:

    If the method that you're invoking via P/Invoke only uses the pointer for the duration of that P/Invoke call, then: in almost all cases you're fine and passing an implicit pointer, or using fixed around the call (i.e. if the P/Invoke signature just declares SomeStruct* or IntPtr), will work fine.

    If the method that you're invoking via P/Invoke stores the pointer before it returns, and then expects that pointer to be meaningful (perhaps for a callback-based async/completion API), that's the problem scenario where you need to make sure that the data isn't going to be moved:

    • for unmanaged memory such as from AllocHGlobal: nothing is necessary
    • for memory lower down on the stack (i.e. where the stack-frame isn't going to get reused during this time): nothing is necessary
      • (if you're using stack memory with P/Invoke and you can't satisfy that condition: you've made a serious design error)
    • for managed memory (objects on the heap, etc); that's where the fun starts; note that .NET 5 introduces a "pinned object heap" (usually used for arrays intended for P/Invoke), but except for that: you'd need to manually pin