
Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]
.pdf818 C H A P T E R 2 0 ■ U N S A F E C + + . N E T P R O G R A M M I N G
A pinned pointer can point to a reference handle, a value type, and an element of a managed array. It cannot pin a reference type, but it can pin the members of a reference type. A pinned pointer has all the abilities of a native pointer, the most notable being pointer comparison and arithmetic. Listing 20-6 shows the pin_ptr<> keyword in action.
Listing 20-6. pin_ptr in Action
#include <stdio.h> using namespace System;
ref class RTest
{
public: int i;
RTest()
{
i = 0;
}
}; |
|
value class VTest |
|
{ |
|
public: |
|
int i; |
|
}; |
|
#pragma unmanaged |
|
void incr (int *i) |
|
{ |
|
(*i) += 10; |
|
} |
|
#pragma managed |
|
void incr (VTest *t) |
|
{ |
|
t->i += 20; |
|
} |
|
void main () |
|
{ |
|
RTest ^rtest = gcnew RTest(); |
// rtest is a reference type |
pin_ptr<int> i = &(rtest->i); |
// i is a pinned int pointer |
incr( i ); |
// Pointer to managed data passed as |
|
// parameter of unmanaged function call |

C H A P T E R 2 0 ■ U N S A F E C + + . N E T P R O G R A M M I N G |
819 |
Console::WriteLine ( rtest->i );
VTest ^vtest = gcnew VTest; |
// vtest is a boxed value type |
|
vtest->i = 0; |
|
|
pin_ptr<VTest> ptest = &*vtest; // ptest is a pinned value type. |
|
|
|
// The &* says give the address of |
the |
|
// indirection of vtest |
|
incr( ptest ); |
// Pointer to value type passed as |
|
|
// parameter of unmanaged function |
call |
Console::WriteLine ( vtest->i );
array<Byte>^ arr = gcnew array<Byte> {'M', 'C', '+', '+'};
pin_ptr<Byte> |
p = |
&arr[1]; |
// ENTIRE |
array is pinned |
|
unsigned char |
*cp |
= p; |
|
|
|
printf("%s\n", --cp); |
// cp bytes will not move during call |
||||
|
|
|
// |
notice |
the negative pointer arithmetic |
|
|
|
// |
into the array. |
}
Figure 20-6 shows the results of this little program.
Figure 20-6. Results of Pinned.exe
One thing that you might want to be aware of is that, as you can see in the preceding code example, there is no problem including standard include files like stdio.h, but if you do you are going to need to use the /clr switch, as these headers usually cause unmanaged code to be generated.
Pinning Interior Pointers
A major difference between pinned pointers and interior pointers is that pinned pointers cast to native pointers, while interior pointers cannot, due to their ability to change as memory is compacted. Because of this, even though the interior pointer has all the functionality of a native pointer, it still cannot be passed to an unmanaged/native function that is expecting a native pointer.
Fortunately, there is nothing stopping you from pinning an interior pointer as you can see in Listing 20-7.
820C H A P T E R 2 0 ■ U N S A F E C + + . N E T P R O G R A M M I N G
Listing 20-7. Pinning an Interior Pointer using namespace System;
value class Test
{
public: int i;
};
#pragma unmanaged
void incr (int *i)
{
(*i) += 10;
}
#pragma managed
void main ()
{
Test ^test = gcnew Test(); interior_ptr<int> ip = &test->i; (*ip) = 5;
// incr( |
ip ); |
// Invalid |
|
pin_ptr<int> i = ip; |
// i is a pinned interior pointer |
||
incr( |
i ); |
// |
Pinned pointer to interior pointer passed to a |
|
|
// |
native function call expecting a native pointer |
Console::WriteLine ( test->i );
}
Including the vcclr.h File
Okay, now that we have all the pieces, let’s look at one last thing before we move on to the advanced features in the next chapter.
You have seen that there is no problem placing unmanaged class pointers within a managed class, but you are not able to do the opposite (place an managed class handle into an unmanaged class) due to the garbage collector’s inability to maintain member handles in unmanaged classes. (Actually, unmanaged classes don’t even understand the handle syntax in the first place, so the garbage collector’s inabilities are sort of a mute point.)
class |
ClassMember {}; |
|
|
ref class |
RefClassMember {}; |
|
|
class Class |
|
|
|
{ |
|
|
|
public: |
|
|
|
RefClassMember |
^hrc; |
// Big fat ERROR |
|
}; |
|
|
|
C H A P T E R 2 0 ■ U N S A F E C + + . N E T P R O G R A M M I N G |
821 |
ref class RefClass |
|
|
{ |
|
|
public: |
|
|
ClassMember |
*pc; |
// No problemo |
}; |
|
|
Well, let’s not give up prematurely here... it is not entirely accurate that you can’t place a managed class in an unmanaged class. What you can’t do is place a handle to a managed class into the unmanaged class. What you use instead of the handle are interior pointers and on occasion pinned pointers. Oh, and you also need to use the .NET Framework class
System::Runtime::InteropServices::GCHandle or the much easier template gcroot<T>. I use gcroot<T>, but feel free to explore GCHandle (if you are a glutton for punishment).
You can find the gcroot<T> template in gcroot.h, but the preferred method of accessing it is via the vcclr.h, as this header file will contain an assortment of utilities. (It only currently contains one utility, but it is still a good practice to follow what Microsoft suggests.)
Essentially, gcroot<T> provides you with the ability to place an interior pointer into your managed class instead of a handle. Listing 20-8 shows a simple example of using gcroot<T>.
Listing 20-8. Pinning an Interior Pointer
#include "stdio.h" #include "vcclr.h"
using namespace System;
ref class MClass
{
public: int x;
~MClass() { Console::WriteLine("MClass disposed"); } protected:
!MClass() { Console::WriteLine("MClass finalized"); }
};
#pragma unmanaged // Works with or without this line
class UMClass
{
public:
gcroot<MClass^> mclass;
~UMClass() { printf("UMClass deleted\n"); }
};
#pragma managed
void main()
{
UMClass *umc = new UMClass(); umc->mclass = gcnew MClass();

822 C H A P T E R 2 0 ■ U N S A F E C + + . N E T P R O G R A M M I N G
umc->mclass->x = 4;
Console::WriteLine("Managed Print {0}", umc->mclass->x); printf("Unmanaged Print %d\n", umc->mclass->x);
delete umc;
}
Figure 20-7 shows the results of this little program.
Figure 20-7. Results of VcclrEx.exe
I added to the managed class its two destructors (dispose and finalize) to make sure that finalize is being called. This means the garbage collector is doing its job. And, as you can see from Figure 20-7, all is as it should be.
I hinted earlier that vcclr.h also contains one utility function called PtrToStringChars(). This utility function converts a managed string into a const interior pointer of type wchar_t. This handy little utility allows you to be more efficient and use the internally stored Char data directly instead of copying it to an unmanaged wchar_t array.
There is one minor catch. Remember, unmanaged functions that are expecting native pointers cannot use interior pointers. Thus, functions like wprintf() will require you to pin the pointer first before you use it.
Listing 20-9 is another “Hello World!” program, this time mixing managed and unmanaged code as well as using the PtrToStringChars() function.
Listing 20-9. Hello World PtrToStringChars() Function Style
#include "stdio.h" #include "vcclr.h"
using namespace System;
void main()
{
String ^hstr = "Hello World!";
pin_ptr<const wchar_t> pstr = PtrToStringChars(hstr);
wprintf(pstr);
}
C H A P T E R 2 0 ■ U N S A F E C + + . N E T P R O G R A M M I N G |
823 |
Summary
This chapter explored the basics of unsafe, unmanaged, and native code. You started off by examining what unsafe code is and how it differs from unmanaged and native code. You then discovered some of the major reasons why you might want to include unsafe code in your applications. Next, you examined some of the ways to make your code unsafe by mixing managed code and unmanaged/native code, unmanaged arrays, unmanaged classes, and pointers. Finally, you took a look at gcroot<T> and the PtrToStringChars() function, which simplify managed code within unmanaged code.
Now with the basics down, I’m going to move on to the final chapter of the book and examine the more advanced mixing of safe/managed code with unmanaged/native DLLs and COM objects.

826 C H A P T E R 2 1 ■ A D V A N C E D U N S A F E O R U N M A N A G E D C + + . N E T P R O G R A M M I N G
Marshaling is a topic all to its own so I cover it a little later. But if you are dealing with primitive types (char, wchar_t, short, int, float, double, etc.), you don’t need to do anything special in the way of marshaling anyway.
Calling DLLs without P/Invoke
But before I cover P/Invoke, you should know that you don’t need P/Invoke if you are willing to sacrifice safe code, nonprimitive data types, and any language but C or C++. All you have to do is develop the .NET application as you did before .NET existed. If you don’t have pre-.NET experience, here is what I mean.
One of the powerful features of C++/CLI is that you can mix and match managed and unmanaged C++ code, in most cases, almost effortlessly. Listing 21-1 shows an example of a .NET console application that uses a third-party DLL (written by me) and a call to the User32.dll’s MessageBox.
Listing 21-1. Mixing Managed and Unmanaged Code Without P/Invoke
#include "stdafx.h" #include "windows.h"
extern "C" __declspec(dllimport) long square(long value); using namespace System;
int main(array<System::String ^> ^args)
{
long Squareof4 = square(4);
Console::WriteLine(L"The square of 4 is {0}", Squareof4);
MessageBox(0, L"Hello World!", L"A Message Box", 0);
return 0;
}
As you can see, this code is just some unmanaged and managed C++ code mixed together willynilly. If not for the array<> or Console::WriteLine statements, you probably wouldn’t even have known that this is a .NET application.
If you have worked with C++ before .NET, you should have no problem with this code. To get access to the MessageBox function, you need to include windows.h, just as you would in any other Windows application without .NET. To access the square function, which resides in a DLL, a dllimport function prototype is needed.
I created the square function within a DLL so that you can see that nothing special is being done behind the scenes with MessageBox. Be careful, though, when you create NativeCode.dll (shown in Listing 21-2). Do not select any of the CLR type projects. Instead, make sure you select the Win32 Project and select DLL in the application settings. Or, if you are compiling the example from the command line, then use the /LD option like this:
cl /LD NativeCode.cpp

C H A P T E R 2 1 ■ A D V A N C E D U N S A F E O R U N M A N A G E D C + + . N E T P R O G R A M M I N G |
827 |
Listing 21-2. A Very Simple Native Code DLL
#include "stdafx.h" #include "string.h"
extern "C" __declspec(dllexport) long square(long value)
{
return value * value;
}
To get Listing 21-1 to compile, you need to change the program’s properties so that the Linker knows where the User32.lib and NativeCode.lib files are located. To do this, you just replace $(NoInherit) with the path to NativeCode.lib, as seen in Figure 21-1. This kills two birds with one stone. Removing $(NoInherit) causes User32.lib to be added to the link, while replacing it with the path to NativeCode.lib does the same for NativeCode.lib. These .lib files in turn provide information to the compiler on how to interface with their corresponding .dll files.
Figure 21-1. Updating Linker properties
Once you successfully compile the console application and the DLL, you can now execute the example. You should get something similar to Figure 21-2.