manski's blog

P/Invoke Tutorial: Pinning (Part 4)

Sometimes a C/C++ function needs to store data you pass to it for later reference. If such data is a managed object (like a string or class) you need to make sure that the garbage collector doesn’t delete it while it’s still used/stored in the native code.

That’s what pinning is for. It prevents the garbage collector from deleting and moving the object.

Pinning an Object

To pin a managed object, use GCHandle.Alloc():

// Pin "objectToBePinned"
GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);

The objectToBePinned remains pinned until you call Free() on the handle:

// Unpin "objectToBePinned"
handle.Free();

After unpinning the object it can again be:

  • moved by the garbage collector (to optimize memory layout)
  • deleted, if no more references exist to it

Notes:

  • Free() will never be called automatically. If you don’t call it manually, the memory of the pinned object will never be freed (i.e. you create a memory leak).
  • You only need to pin objects (including strings). You can’t pin primitive types (like int) and structs, as they reside on the stack and are passed by copy. If you try pin a struct, a copy of the struct will be pinned.
  • Classes and structs must have the attribute [StructLayout(LayoutKind.Sequential)] to control the layout of their fields. Otherwise GCHandle.Alloc() will throw an ArgumentException reading: “Object contains non-primitive or non-blittable data.”
  • If the method you’re calling doesn’t store a reference to the passed object for later reference, you don’t need to pin this object. P/Invoke automatically pins objects before the C/C++ function is called and unpins them after the function has returned. So, manually pinning an object is actually about the (time of) unpinning.

Note also that you don’t need (and actually can’t) pin delegates. You need, however, to extend the lifetime of the delegate for as long as it can be called from unmanaged code. To cite an MSDN blog entry on this:

Along the same lines, managed Delegates can be marshaled to unmanaged code, where they are exposed as unmanaged function pointers. Calls on those pointers will perform an unmanaged to managed transition; a change in calling convention; entry into the correct AppDomain; and any necessary argument marshaling. Clearly the unmanaged function pointer must refer to a fixed address. It would be a disaster if the GC were relocating that! This leads many applications to create a pinning handle for the delegate. This is completely unnecessary. The unmanaged function pointer actually refers to a native code stub that we dynamically generate to perform the transition & marshaling. This stub exists in fixed memory outside of the GC heap.

However, the application is responsible for somehow extending the lifetime of the delegate until no more calls will occur from unmanaged code. The lifetime of the native code stub is directly related to the lifetime of the delegate. Once the delegate is collected, subsequent calls via the unmanaged function pointer will crash or otherwise corrupt the process.

Passing a Pinned Object

Now that you’ve pinned your object you surely want to pass it to a C/C++ function.

The easiest way to do this is to specify managed type directly on the P/Invoke method:

// Directly using "MyType" as parameter type
[DllImport("NativeLib")]
private static extern void do_something(MyType myType);

Then call this method:

GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);

do_something(objectToBePinned);

// NOTE: Usually you wouldn't free the handle here if "do_something()" 
//   stored the pointer to "objectToBePinned".
handle.Free();

The alternative is to pass it as IntPtr (although it’s no different from the direct approach):

[DllImport("NativeLib")]
private static extern void do_something(IntPtr myType);

...

GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);

do_something(handle.AddrOfPinnedObject());

// NOTE: Usually you wouldn't free the handle here if "do_something()" 
//   stored the pointer to "objectToBePinned".
handle.Free();

Pinning and Passing Strings

Pinning strings is the same as pinning objects with one exception:

You must specify the CharSet.Unicode. when passing pinned strings!

Otherwise P/Invoke will convert the string into an ASCII string (thereby copying it).

Assume this C function:

void do_something(void* str1, void* str2) {
  // Check whether the pointers point to the same address
  printf("Equals: %s\n", (str1 == str2 ? "true" : "false"));
}

Then:

// WRONG! Will print "false"
[DllImport("NativeLib", EntryPoint="do_something")]
private static extern void do_something1(string str1, IntPtr str2);

// CORRECT! Will print "true"
[DllImport("NativeLib", CharSet = CharSet.Unicode, EntryPoint="do_something")]
private static extern void do_something2(string str1, IntPtr str2);

...

string text = "my text";
GCHandle handle = GCHandle.Alloc(text, GCHandleType.Pinned);

// Will print "false"
do_something1(text, handle.AddrOfPinnedObject());
// Will print "true"
do_something2(text, handle.AddrOfPinnedObject());

Verifying the Pinned Object is passed

As mentioned in the previous section P/Invoke may create a copy of an object instead of passing it by reference directly.

You can easily verify this by comparing the pointer adresses. In C# use handle.AddrOfPinnedObject().ToString() to obtain the address of the pinned object.

9 comments

  1. Anthony said:

    Good, clear description. Thanks. I was worried about pinning delegates.

  2. B Quinn said:

    Finally after two days search I found the crux of the matter here thanks for being explicit

  3. Tez said:

    might be an old article, but nicely done, covers the topic nicely.

  4. nitta said:

    Thanks for your clear explanation, but I think your code has a typo in the following.

    GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);
    do_something(objectToBePinned.AddrOfPinnedObject());

    I think the right code is

    GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);
    do_something(handle.AddrOfPinnedObject());
    ^^^^^^^

    • Sebastian Krysmanski (post author) replied:

      You’re absolutely right. Thanks for pointing that out. I’ve corrected the code accordingly.

  5. Alan said:

    Note that you don’t necessarily have to pin the object to pass it around. GCHandleType.Normal will work just as fine if you just need to get the reference back to use in the managed side. Please note that pinning objects undermines the GC performance overall, including code that has nothing to do with that will get slower if you pin a lot of stuff, thus pinning should be avoided as much as possible unless you really need to access the object/memory in the unmanaged code, if you just need to store the reference you don’t have to pin at all.

  6. ertan said:

    Thank you so much. Great explanation.

  7. Pingback: Pin data for P/Invoke access – Windows Questions

  8. Sujay Ghosh said:

    Hello Sebestian ,

    Great article .

    Wanted to add we can use pin_ptr

    GCHandle handle = GCHandle.Alloc(objectToBePinned, GCHandleType.Pinned);
    handle.Free()

    can be replaced with pin_ptr

    pin_ptr pPinArrayUValues = &objectToBePinned;

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.