Introduction
One very confusing facet of the now obsolete Managed Extensions to C++ was
its pointer usage syntax, where <code lang=mc++>T* could be a native pointer, a
managed reference or an interior pointer. In the new C++/CLI syntax, managed
references use the ^
punctuator (called hat by Redmondians and mistakenly
called cap by me the first time I saw it), thereby avoiding any confusion
with a native pointer. With probably the same intentions, Herb Sutter and team
have provided us with a far more intuitive template-style syntax for
representing interior pointers, similar to the new syntax for representing CLI
arrays. In this article, I'll talk about what interior pointers are, where they
can be used and how they can be used.
What are interior pointers?
Interior pointers are essentially pointers into the CLI heap (needn't
always point to the CLI heap though) that point to managed objects or
members of managed objects. The basic problem with using a native pointer to
point to a managed object or a member-object of a managed object is that the
Garbage Collector might move the object around in the CLI heap during a
GC/Memory-Compaction cycle. Thus, a managed object at <code lang=mc++>0x00BB0010
might end up at <code lang=mc++>0x00BBC010 after a GC cycle, and you can imagine
what'd happen if we had a native pointer <code lang=mc++>p pointing to the managed
object before the GC cycle occurs. The pointer <code lang=mc++>p would continue
pointing to <code lang=mc++>0x00BB0010 (the location of the managed object before
the GC cycle), but now the managed object is at <code lang=mc++>0x00BBC010, and
<code lang=mc++>0x00BB0010 is just a random location in the CLI heap that might
later be occupied by some other object. If the native pointer <code lang=mc++>p is
now used to modify the contents of <code lang=mc++>0x00BB0010, we are basically
corrupting data - most undesirable as you can see!
That's where interior pointers come into play. The CLR knows about interior
pointers and every time the Garbage Collector relocates an object pointed to by
an interior pointer variable, the CLR automatically updates the value of the
interior pointer to reflect the new location. So we can continue using the
interior pointer and yet be totally oblivious as to the GC moving our pointed-to
object around the CLI heap. Here's some code to see this in action :-
Here's our test class with an <code lang=mc++>int member.
<pre lang=mc++>ref class Test
{
public:
int m_i;
};
Here's a function to try and fill up the CLI heap's Generation-0 memory. [You
might have to modify the loop-count (10,000 for me) to other values if you are
not getting the desired effect]
<pre lang=mc++>void DoLotsOfAllocs()
{
for(int i=0; i<10000; i++)
{
gcnew Test();
}
}
Here's the test code.
<pre lang=mc++>void _tmain()
{
DoLotsOfAllocs();
Test^ t = gcnew Test();
t->m_i = 99;
interior_ptr<int> p = &t->m_i;
printf("%p %d\r\n",p,*p);
DoLotsOfAllocs();
printf("%p %d\r\n",p,*p);
Output on my machine [addresses will be different for you]
******* Output ******
00AC9B3C 99
00AA8D18 99
There you go; the same interior pointer variable <code lang=mc++>p which was
pointing to <code lang=mc++>0x00AC9B3C later points to <code lang=mc++>0x00AA8D18. Had
we been using a native pointer, the runtime wouldn't have been able to update
its value (the pointed-to address); and just in case you were wondering about
trying that out, don't bother, since the compiler won't let you use a native
pointer to point to a managed object's member.
Passing by reference using interior pointers
One handy use for interior pointers is when you have a requirement for
functions with pass-by-ref arguments. Let's take a simple function that simply
squares the passed integer :-
<pre lang=mc++>ref class Test
{
public:
int m_i;
};
void Square(interior_ptr<int> pNum)
{
*pNum *= *pNum;
}
Now see this code.
<pre lang=mc++>void _tmain()
{
Test^ t = gcnew Test();
t->m_i = 99;
interior_ptr<int> p = &t->m_i;
printf("%d\r\n",*p);
Square(p);
printf("%d\r\n",*p);
int a = 10;
Square(&a);
printf("%d\r\n",a);
Output :-
******* Output ******
99
9801
100
You can see how we can pass both interior pointers as well as native pointers
to the same function; this is because native pointers automatically convert to
interior pointers. [Note that interior pointers cannot convert to native
pointers]
Now that we've seen how interior pointers can be used for passing values by
reference, it should be stated here that the same could be just as easily
accomplished using tracking references. See below another function that does the
same thing using a tracking reference as parameter.
<pre lang=mc++>void Square2(int% pNum)
{
pNum *= pNum;
}
And the corresponding caller code.
<pre lang=mc++>void _tmain()
{
Test^ t = gcnew Test();
t->m_i = 99;
printf("%d\r\n",t->m_i);
Square2(t->m_i);
printf("%d\r\n",t->m_i);
int a = 10;
Square2(a);
printf("%d\r\n",a);
Tracking references and interior pointers are quite similar in nature, though
you can do a lot more with interior pointers like pointer arithmetic and pointer
comparison.
Pointer arithmetic with interior pointers
Here's some sample code that iterates through and sums an array of <code lang=mc++>ints.
<pre lang=mc++>void ArrayStuff()
{
array<int>^ arr = gcnew array<int> {2,4,6,8,3,5,7};
interior_ptr<int> p = &arr[0];
int s = 0;
while(p != &arr[0] + arr->Length)
{
s += *p;
p++;
}
printf("Sum = %d\r\n",s);
}
And here's some code that directly manipulates a <code lang=mc++>System::String
<pre lang=mc++>String^ str = "hello";
interior_ptr<Char> ptxt = const_cast< interior_ptr<Char> >(
PtrToStringChars(str));
for(int i=0; i<str->Length; i++)
*(ptxt+i) = *(ptxt+i) + 1;
Console::WriteLine(str);
If you are of a masochistic disposition, you could change the <code lang=mc++>for
loop to
<pre lang=mc++>for(; (*ptxt++)++; *ptxt);
with the same results. It doesn't produce very legible looking code but it
does demonstrate that you can do pointer comparison (implicit check for <code lang=mc++>
nullptr in the above example) with interior pointers.
Points of interest
You cannot have an interior pointer as a member of a class and I guess this
is to maintain language interoperability. I mean you can't have VBers using your
class if it's full of interior pointer member objects, can you? (If I guessed
wrong, please feel free to correct me)
You cannot use an interior pointer to point to a <code lang=mc++>ref object,
though you can point to the handle to a <code lang=mc++>ref object. Thus <code lang=mc++>
interior_pointer<System::String> is not allowed but <code lang=mc++>interior_ptr<System::String^>
is legal.
All interior pointers are implicitly initialized to <code lang=mc++>nullptr
unless you explicitly give it some other default value.
The compiler will emit a modopt
to distinguish
interior pointers from tracking references. Currently this
modopt
is <code lang=mc++>Microsoft::VisualC::IsCXXPointerModifier (found in
Microsoft.VisualC.dll) but in the final release this will be <code lang=mc++>
System::Runtime::CompilerServices::IsExplicitlyDereferenced (found in
mscorlib.dll starting with Whidbey).
For <code lang=mc++>value classes, the <code lang=mc++>this pointer is an interior
pointer. My guess is that, this is so because <code lang=mc++>value classes can be members of
managed classes, and if so, assuming the <code lang=mc++>value class uses the <code lang=mc++>
this pointer, it could be fatally dangerous if the pointer was not an
interior pointer. (If I guessed wrong, please feel free to correct me)
<pre lang=mc++>value class V
{
void A()
{
interior_ptr<V> pV1 = this;
V* pV2 = this; //won't compile
}
};
Conclusion
I hope I've given a pretty decent coverage of interior pointers and their
usage in C++/CLI. Kindly submit your feedback so that I get a chance to improve
on this article. Thank you.