Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C++/CLI

Guide to using Pinning Pointers

Rate me:
Please Sign up or sign in to vote.
4.74/5 (29 votes)
29 Nov 2004CPOL5 min read 100.8K   32   13
Article on the usage and dangers of pinning pointers

Introduction

Having written an article on interior pointers, I thought the next logical topic for an article from me should be pinning pointers. This article will explain what pinning pointers are, when they come in handy and how they can be harmful to the overall performance of your .NET application. If you haven't already read my article on interior pointers and if you don't know what interior pointers are, then it'd be a good idea to quickly go through that article :-

Now that I've enjoyed the rather conceited satisfaction of having plugged my own article, lets get going.

What are pinning pointers?

During Garbage Collection cycles, the GC frequently compacts the CLI heap, relocates objects and adjusts managed references so that they continue referencing the same object that they were before the GC cycle. Since this is done transparently, the programmer needn't really be bothered with this at all unless there is an unmanaged pointer pointing to a CLI heap object. Since the GC cannot adjust unmanaged pointers, this would obviously result in havoc and so unmanaged pointers are not allowed to point to CLI objects. But there might be situations where you want to pass a CLI object to an unmanaged function (possibly during native interop) and to facilitate this, the CLR provides us with pinning pointers. A pinning pointer (or a pinned pointer) prevents the GC from relocating the pinned object for as long as the pinning pointer remains in scope. It's not difficult to write some arguably contorted code to verify this and we'll do just that :-

See below our test ref class, the DoLotsOfAllocs function that we use to fill up the CLI heap and to force a GC cycle, and a native function that takes an unmanaged int* as argument.

MC++
ref class Test
{
public:
    int m_i;
};

void DoLotsOfAllocs(int x=10000)
{
    for(int i=0; i<x; i++)
    {
        gcnew Test();
    }    
}

void NativeFunc(int* pInt)
{
    *pInt += 100;
}

Here's the test code :-

MC++
void _tmain()
{
    //Fill up Generation-0
    DoLotsOfAllocs();

    Test^ t1 = gcnew Test();
    //Need a gap between the two Test objects to avoid
    //the interior pointer being pinned too (the GC seems
    //to pin an area and not just an object)
    DoLotsOfAllocs(1000); 
    Test^ t2 = gcnew Test();

    pin_ptr<int> p1 = &t1->m_i;
    interior_ptr<int> p2 = &t2->m_i;

    NativeFunc(p1);
    Console::WriteLine(t1->m_i);

    printf("pinned p1 = %p : interior ptr p2 = %p\r\n",p1,p2);
    DoLotsOfAllocs(); //Force a Gen-0 GC
    printf("pinned p1 = %p : interior ptr p2 = %p\r\n",p1,p2);

Here's the output I got (obviously, addresses won't match on other machines) :-

100
pinned p1 = 00ACA244 : interior ptr p2 = 00ACD130
pinned p1 = 00ACA244 : interior ptr p2 = 00AA8D54

p1 (the pinned pointer) retains its value even after a GC cycle, while p2 (an interior pointer) has changed confirming that while the unpinned object has been relocated, the pinned object has remained fixed in the CLI heap.

Passing to native code

If all we needed to do was directly access the underlying object via a pointer and do pointer operations (like pointer arithmetic and comparison), then it'd wholly suffice to purely use interior pointers. But interior pointers do not convert to native pointers and thus cannot be passed to native functions expecting native pointers. That's where pinned pointers come in handy with their implicit conversion to native pointers.

MC++
//Native function
#pragma unmanaged
void Test2(wchar_t* p)
{
    wchar_t vowarr[] = L"aeiou";
    while(*p++ = wcschr(vowarr,*p) ? towupper(*p) : *p);
}

//Mixed mode function
#pragma managed
void Test1()
{
    String^ s = "hello world";
    pin_ptr<Char> p = const_cast< interior_ptr<Char> >(PtrToStringChars(s));
    Test2(p);
    Console::WriteLine(s);
}

PtrToStringChars returns a const interior pointer which we first const_cast to a volatile interior pointer which gets implicitly converted to a pinned pointer. Now we pass this pinned pointer to Test2(...) which accepts an unmanaged wchar_t* and the pinned pointer is implicitly converted to the unmanaged pointer.

Now that I've shown you how pinned pointers have advantages and uses over interior pointers, I've got to tell you this - only use pinned pointers when you have to and even when you have to, make sure that your pinned pointer does not remain in scope for too long. This is explained in the next section.

Pinning pointers and heap fragmentation

Lets assume that we have 3 objects O1, O2 and O3 in the CLI heap [assume Gen-0], which will now look like this :-

O1O2O3Free Space

Now, assume that O2 is now an unreachable object and a GC occurs. After GC the CLI heap will look like this :-

O1O3Free Space

Now assume that O3 is a pinned object (means there are 1 or more pinning pointers pointing to it). If so, since the GC cannot relocate O3, the CLI heap will look like this :-

O1 O3Free Space

Now imagine what happens if we had numerous such pinned objects (indicated by a gray background); the CLI heap would look something like :-

O1 O3 O5 O6O7 O8Free Space

Now, the heap is thoroughly fragmented, which makes GC cycles and memory allocation for new objects rather slower and less efficient than otherwise, and there is also the risk that there won't be a large enough contiguous block of free space to allocate a large object (say an array) which will result in some kind of memory exception.

Recommendations for using pinning pointers

[ Please note that these are strictly my ideas and could be remarkably erroneous for all I know, but with feedback from some of you out there who might know better, I hope to narrow down the level of inaccuracy. ]

  1. Use pinning pointers only when absolutely necessary, else try and use interior pointers or tracking references.

  2. When you do use pinning pointers, try and reduce the duration of their scope (the longer they remain in scope, the greater the risk of heap fragmentation).

  3. Try to avoid pinning generation-0 objects (since GC cycles occur most frequently in generation-0).

  4. If you need to pin multiple objects, try and allocate those objects together, so that the pinned objects will all be in one contiguous area in the CLI heap and the extent of fragmentation will be reduced.

  5. During interop calls, first check and see if the marshalling layer does any pinning for you, and do your own pinning only if required.

  6. Avoid situations where you pass a pinned pointer to a native pointer and risk using the native pointer after the pinned pointer has gone out of scope. Once the pinned pointer is out of scope, the object is no longer pinned and might get relocated or collected by the GC, in which case your native pointer is now pointing to a random area in the CLI heap.

Conclusion

I thoroughly enjoyed writing this article and am feeling rather proud of the fancy html-table graphics that I used for my CLI heap representations. If any of you feel that the graphics sucked, please don't let me know, so I can continue basking in an idiotic glory that makes sense only to me. On a more serious note, please feel free to submit you unmitigated feedback so that I can improve the article to the maximum extent possible. Thank you.

Related links

License

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


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
GeneralRe: I gave you a five... Pin
Jim Crafton4-Dec-04 16:40
Jim Crafton4-Dec-04 16:40 
GeneralRe: I gave you a five... Pin
Nemanja Trifunovic6-Dec-04 2:33
Nemanja Trifunovic6-Dec-04 2:33 
GeneralRe: I gave you a five... Pin
Nish Nishant25-Apr-05 17:56
sitebuilderNish Nishant25-Apr-05 17:56 

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

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