Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

P/Invoke Tutorial: Pinning (Part 4)

, 12 Jun 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Pinning prevents the garbage collector from deleting and moving an object.

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 in use/still 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 the memory)
  • 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.

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(objectToBePinned.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 addresses. In C# use handle.AddrOfPinnedObject().ToString() to obtain the address of the pinned object.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sebastian Krysmanski
Software Developer University of Stuttgart
Germany Germany
I have studied Software Engineering and am currently working at the University of Stuttgart, Germany.
 
I have been programming for many years and have a background in C++, C#, Java, Python and web languages (HTML, CSS, JavaScript).
Follow on   Twitter

Comments and Discussions

 
SuggestionI give you a 5 PinprofessionalShawn-USA1-May-13 19:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141220.1 | Last Updated 12 Jun 2012
Article Copyright 2012 by Sebastian Krysmanski
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid