Pinning Pointers

Hot on the heels of my article on interior pointers, comes a much more insightful one by Stan Lippman on the same issue. That happens sometimes. I enjoyed the chat we had on the VC++ 2005 Beta, and I wanted to point that there are two other online chats coming up. One is on upgrading COM apps to .NET, and the other is on the library and runtime enhancements in VC++ 2005. They're lumped together with other chats that might be of interest on this page.

On to pinning pointers. Sometimes, the interior pointer simply won't suffice. Say I have a function I really need to access in a native code module. For example, I have a native function that loads an array for me, and I want to use it to load a managed array. Sounds like a complicated task, and it can be. Conventional wisdom tells us that the native function can't get access to the managed array, because it's on the GC heap, and could move around all over the place. I can't use interior pointers because this is native code. So it seems the onlyh choice left is to make another native array, call into the native function, and then loop through the native array, copying the members over from it to the managed array. Clunky, to say the least.

There's a better way? You bet. It's called the pinning pointer. In Managed Extensions, we exposed it using the keyword __pin. In the new syntax, we expose it through another smart-pointer-ish type, pin_ptr<>, located in the cli namespace (the namespace formerly known as stdcli::language). What the pinning pointer does is "pin" our managed object down on the GC heap, preventing the garbage collector from moving it around. In addition to this pinning, it gives us what we need; a conversion to a native pointer.

Though pinning pointers are cool, they are sometimes not well understood. The best way to think of them is that an object is pinned so long as a pinning pointer points to it. That's important enough a concept that you ought to read that sentence again. I'll wait. What this means is that your pin_ptr object is only pinning something on the GC help while its in scope. When it goes out of scope, it stops pointing to that object, and that object can be moved at any time. That means you can't go saving a native pointer, and expect your pin_ptr to hold it forever. 

Dangers of pinning pointers. In fact, because of the dangers of misinterpretation, we severely restrict where and how you can use pinning pointers. They can't be involved anywhere temporaries are created, can't be the argument to or the return type from a function, can't be members of a type, and can't be involved in casts, to name a few. But this is C++, and what would C++ be without a way to shoot yourself in the foot. Long ago, I wrote an article that included some warnings about the pinning pointer. The examples are in Managed Extensions, but the concepts are pretty solid. Rather than repeat myself, I'll just link to it, and request that you read it. Especially that example of how to make a quick and easy GC hole.

Enough talk, let's see an example. Right.

using namespace System;
using namespace cli;

void myUnmanagedFunction(wchar_t* p, int size){
for(int i=0; i<size; i++)
p[i] = 'A'+i;
}

int main(){
array<Char>^ arr = gcnew array<Char>(10);
pin_ptr<Char> ppC = &arr[0]; //implicit conversion from interior to pin pointer
myUnmanagedFunction(ppC, 10);

for(int i=0; i<10; i++)
Console::Write(arr[i]);
Console::Write("\n");
}

Putting it all together can be complicated. Brandon helped me sort it out one day by drawing a helpful diagram, which I'll replicate here.

Don't quit your day job. I know, I'm not much of an artist. Think of the arrows as "can convert to." Note that for orthogonality, native pointers can convert to pin and interior pointers. Hey, it's sometimes useful, you'll be glad it's there. That's it for pin pointers. In a future article, I might look at our upcoming for each syntax.