Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

808C 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

int Madd(int a, int b)

{

return a + b;

}

void main()

{

Console::WriteLine("Unmanaged

Add

2

+

2:

{0}",

UMadd(2,

2));

Console::WriteLine("Managed

Add

3

+

3:

{0}",

Madd(3,

3));

}

By looking at the UMadd() and Madd() methods’ code, you will not see much difference. Both are simply standard C++/CLI code. Notice you even call the methods the same way, as long as they are being called within a managed code block.

If you try to call managed code within a native code block, you get the compile time error C3821 'function': managed type or function cannot be used in an unmanaged function. This makes sense as native code does not use the CLR to run, while managed code does, so there is no way for the managed code to be executed within the native code.

Another thing you need to be careful about with these directives is thatthey are only allowed at the global scope, as shown in Listing 20-1, or at the namespace scope. This means you can’t change a method or class partway through. In other words, the whole function or class can be managed or native, but not a combination.

Caution The following code is invalid due to invalid placement of the #pragma directives:

int ErrorFunction(int a, int b)

{

#pragma unmanaged

//Some unmanaged code #pragma managed

//Some managed code

}

Unsafe Code Since the #pragma unmanaged directive causes unsafe code to be generated, you need to compile it with the /clr option. If you try to compile it with the /clr:safe option, you will get a whole bunch of errors.

So what is the difference between Madd() and UMadd()? To see this, you need to use the ildasm tool, which disassembles an assembly. Figure 20-1 shows Madd() (what little you see of it) and Figure 20-2 shows UMadd().

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

809

Figure 20-1. The disassembled Madd()

Figure 20-2. The disassembled UMadd()

The disassembled version of the Madd() function shows all the MSIL required to execute the function, while the UMadd() function only shows the function declaration and the attribute SuppressUnmanagedCodeSecurityAttribute. What you don’t see is the native code that will be

invoked when this function is called (if the CLR allows unmanaged code or in this case native code to be run). In a nutshell, behind the scenes the compiler generates MSIL for Madd() and native code for UMadd().

You might be thinking, as I did originally, why is this code unsafe? The CLR has the attributes needed to find out what is unsafe and can allow code access security to do its thing. But, if you think about it, it does sort of make sense. Since the whole assembly is loaded into memory, it might still be possible for someone to access the parts of the assembly that are unsafe. (Don’t ask me how, but I’m sure some hacker out there has it figured out.) Therefore, to safeguard against this possibility, the current version of the .NET runtime defines unsafe code at an assembly level, so having any unsafe code in an assembly makes the entire assembly unsafe.

810

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

Unmanaged Arrays

One of the first things a C++ developer learns is arrays. Having coded them so long, it is easy to forget that .NET does it differently, when you want your code to be safe. (The usual culprit as to why I have unmanaged arrays in my code is that I cut and paste them in from legacy code and then forget to convert them, until I get all the errors when I try to compile with the /clr:safe option.)

The unmanaged arrays compile and work fine if you don’t use the /clr:safe option. When you examine the MSIL code generated, everything looks just fine. So why is an unmanaged array unsafe? If you have coded C++ for a while, I’m sure you know. It is very easy to overflow the stack by looping through an array too many times. (I’ve done it so many times, I’ve lost count.) There is nothing stopping a program from doing this with an unmanaged array. A managed array, on the other hand, does not allow you to go beyond the end of the array. If you try, you get a nice big exception.

Unsafe Code Unmanaged arrays, though a legal construct in C++/CLI (so long as they contain fundamental and unmanaged data types) are unsafe. If you want both arrays and safe code, you need to use managed arrays.

Listing 20-2 shows the use of unmanaged arrays within managed code.

Listing 20-2. The Unmanaged Array in Managed Code

using namespace System;

void main()

{

int UMarray[5] = {2, 3, 5, 7, 11};

for (int i = 0; i < 5; i++)

{

Console::Write("{0} ", UMarray[i]);

}

Console::WriteLine(" -- End of array");

}

There is nothing terribly special about the preceding code. But there are specific criteria about what can be contained within an unmanaged array. Personally, I think it’s easier to remember what can’t be put into them—basically managed data or anything that requires the gcnew command when creating an instance.

One thing of note, as shown in Figure 20-3, is that the code generated by the compiler is MSIL and not native code. Thus, showing unsafe code does not always mean that the code contains native code. (Though you might argue this, as a whole bunch of native code is added to the assembly when /clr or /clr:pure options are used.)

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

811

Figure 20-3. MSIL generated by UMArray.exe

Unmanaged Classes/Structs

The next major constructs that a C++ developer learns after the array are the class and the struct. Though similar in many ways to C++/CLI’s ref class (which I covered way back in Chapter 3), unmanaged classes have a few major differences that cause them to be unsafe. The most obvious difference, since they are unmanaged, is that they are placed in the CRT heap and not the Managed heap when instantiated. Thus, their memory is not maintained by the .NET garbage collector.

812 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

The actual coding of an unmanaged class/struct exactly matches the coding of the traditional (pre-.NET) C++ class/struct, due to the reason that unmanaged C++ code and traditional C++ code are one and the same. So, if you know how to code a C++ class or struct in a non-.NET environment, then you know how to code an unmanaged class or struct.

With .NET version 1.1 and Managed Extensions for C++, the class and struct were given the ability to be managed. With .NET version 2.0 and C++/CLI, the class has been augmented again this time with the ability to be safe as well. The funny thing (at least to me, but I do have a weird sense of humor, just ask my wife) is unmanaged classes and structs remain the default. You have to do specific things to create managed classes, but we covered all that stuff way back in Chapter 3, so let’s move on.

Prior to C++/CLI, Managed Extensions for C++ used the exact same syntax for managed and unmanaged classes and structs, except for prefixing managed classes and structs with __gc. From there on, syntax for the two were virtually the same. I know I got confused a few times (but that might be just me) and thus tried to always only use managed classes (and data types, as you may have noted if you have the previous version of this book), as it simplified my life immensely.

C++/CLI has vastly improved the readability of the code over Managed Extensions for C++. Yes, the declaration of managed and unmanaged classes and structs is still very similar (Table 20-1 shows some of the major differences), but the syntax of creating managed classes now is considerably different because of the use of handles [^] and the gcnew command for managed classes instead of pointers [*] and the new command for unmanaged classes. Though this change was primarily to make managed coding easier, it also made life easier when coding unmanaged classes, as now there is no confusing the two.

Table 20-1. Unmanaged vs. Managed Classes

Unmanaged class/struct

Managed class/struct

No prefix

Accessed via pointer or reference on the CRT heap or directly within a value type variable

When no explicit base class specified, then class is an independent root

Supports multiple inheritance

Supports friends

Can only inherit from unmanaged types

Can contain data members of type pointer to unmanaged classes but cannot contain a handle to managed classes

ref

Accessed via handle on the Managed heap or directly within a value type variable

When no explicit base class specified, then class inherits from System::Object

Does not support multiple inheritance

Does not support friends

Can only inherit from managed types

Can contain data members of type pointer to unmanaged classes and a handle to managed classes

So what does the comparison of unmanaged and managed classes add up to? I created the nonsense program shown in Listing 20-3, which tries to show the information in Table 20-1 in a different way. I threw in the value class to round out the example, as the value class is sort of an unmanaged managed class. I also did not include friends in the example, as only unmanaged classes support them.

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

813

Listing 20-3. Mixing Managed and Unmanaged Classes

using namespace System;

class

ClassMember {};

 

ref

class

RefClassMember {};

 

value class ValueClassMember {};

class Class

 

 

 

{

 

 

 

 

public:

 

 

 

//

RefClassMember

rc;

// Can't embed instance ref class

//

RefClassMember

^hrc;

// Can't embed handle to ref class

 

ValueClassMember

vc;

 

//

ValueClassMember ^hvc;

// Can't embed managed value class

 

ValueClassMember *pvc;

 

 

ClassMember

c;

 

 

ClassMember

*pc;

 

 

int x;

 

 

 

 

void write() { Console::WriteLine("Class x: {0}", x); }

};

 

 

 

 

ref

class RefClass

 

 

{

 

 

 

 

public:

 

 

 

 

RefClassMember

rc;

 

 

RefClassMember

^hrc;

 

 

ValueClassMember

vc;

 

 

ValueClassMember ^hvc;

 

 

ValueClassMember *pvc;

 

//

ClassMember

c;

// Can't embed instance of class

 

ClassMember

*pc;

 

 

int x;

 

 

 

 

void write() { Console::WriteLine("RefClass x: {0}", x); }

};

 

 

 

 

value class ValueClass

 

{

 

 

 

 

public:

 

 

 

//

RefClassMember

rc;

// Can't embed instance ref class

 

RefClassMember

^hrc;

 

 

ValueClassMember

vc;

 

 

ValueClassMember ^hvc;

 

 

ValueClassMember *pvc;

 

//

ClassMember

c;

// Can't embed instance of class

 

ClassMember

*pc;

 

int x;

void write() { Console::WriteLine("ValueClass x: {0}", x); }

};

814 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

class ClassChildClassParent : public Class {};

 

//

OK

//class ClassChildRefClassParent : public RefClass {};

//

Error

//class ClassChildValueClassParent : public ValueClass {};

//

Error

//ref class RefClassChildClassParent : public Class {};

//

Error

ref

class RefClassChildRefClassParent : public RefClass {};

//

OK

//ref class RefClassChildValueClassParent : public ValueClass {};

//

Error

//value class ValueClassChildClassParent : public Class {};

//

Error

//value class ValueClassChildRefClassParent : public RefClass {};

//

Error

//value class ValueClassChildValueClassParent : public ValueClass {};

//

Error

void main()

 

 

 

 

 

{

 

 

 

 

 

 

 

// Stack

 

 

 

 

 

 

Class

_class;

 

 

 

 

 

RefClass

refclass;

 

// Not really on the

stack

 

ValueClass valueclass;

 

 

 

 

 

// Handle

 

 

 

 

 

//

Class

^hclass

= gcnew Class();

// Not allowed

 

 

 

RefClass

^hrefclass

= gcnew RefClass();

 

 

 

 

ValueClass ^hvalueclass

= gcnew ValueClass();

 

 

 

 

// Pointer

 

 

 

 

 

 

Class

*pclass

= new Class();

 

 

 

//

RefClass

*prefclass

= new RefClass();

// Not allowed

 

 

 

ValueClass *pvalueclass

= & valueclass;

 

 

 

 

// Reference

 

 

 

 

 

Class

&rfclass

= *new Class();

 

 

 

//RefClass &rfrefclass = *gcnew RefClass(); // Not allowed ValueClass &rfvalueclass = valueclass;

_class.x

= 1;

 

refclass.x

= 2;

 

valueclass.x

= 3;

 

hrefclass->x

= 4;

 

hvalueclass->x = 5;

 

pclass->x

= 6;

 

pvalueclass->x = 7;

 

rfclass.x

= 8;

 

rfvalueclass.x = 9;

 

_class.write();

 

// prints 1

refclass.write();

// prints 2

valueclass.write();

// prints 9

hrefclass->write();

// prints 4

hvalueclass->write();

// prints 5

pclass->write();

// prints 6

pvalueclass->write();

// prints 9

rfclass.write();

// prints 8

rfvalueclass.write();

// prints 9

}

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

815

Pointers

If you have spent any time writing C++ code in the past, I’m sure you have come to realize that pointers are essential to C++ development, but also a necessary evil. Basically, it’s a “you can’t live with them, can’t live without them” relationship. Some of the greatest code has been developed using pointers, but also some of the nastiest bugs.

Unmanaged C++ data types can be placed in one of two places, the stack or the heap. When you are dealing with pointers, you are generally dealing with heap data. But pointers can point to almost anything (if the program has the rights), so a pointer can also point to an element of the runtime stack or possibly locations directly within the Windows O/S, though usually that is not allowed. Pointers can be created in a number of ways:

Placing the address directly into the pointer

Arithmetically calculated from another pointer

Copied from an existing object

Using the new command

Just looking at the preceding list should make it obvious why pointers are not safe. In fact, the first two methods of creating pointers should make you cringe. Think what a field day hackers could have with these methods and thus why they are not supported by handles.

Unsafe Code Pointer arithmetic is probably one of the most powerful and at the same time unsafe operations available to a C++ programmer.

Copying a pointer from an existing object seems harmless enough. But even this has a problem if the object is derived from a managed type. The location of the object pointed to in the managed heap memory can move during the garbage collection process, because not only does the garbage collector delete unused objects in managed heap memory, it also compacts it. Thus, it is possible that a pointer may point to the wrong location after the compacting process. Fortunately, C++/CLI provides two ways of solving pointer movement: the interior pointer and the pinned pointer. I’ll cover both in more detail later in the chapter.

Not even using the new command is safe, as the memory allocated is on the CRT heap and is not maintained by the CLR. Using the new command requires you to maintain the allocated memory yourself and when done call the delete command. I know this sounds okay, but I’m afraid very few of us are perfect when it comes to writing code, and I’m pretty sure one day you will forget to deallocate memory, deallocate it too soon, overwrite it, or do any of the other nasty mistakes revolving around pointers.

Interior Pointer

As I harped previously, pointers are extremely powerful, and it would be a great loss to the C++ to lose this aspect of the language. C++/CLI realizes this and has added what it calls interior pointers. Interior pointers are fundamentally pointers to managed objects.

I hope your alarms went off with the last sentence. Remember, managed objects can move. So let’s be a little more accurate. An interior pointer is a superset of the native pointer and can do anything that can be done by the native pointer. But not only does it point to a managed object, when the garbage collector moves the object, the interior pointer changes its address to continue to point to it.

816

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

By the way, interior pointers are safe! Well, I better qualify that. You can use the pointers as you see fit, and they are safe. Just don’t change the value of the pointers or manipulate them using pointer arithmetic. Listing 20-4 is a somewhat complicated example showing a safe program using interior pointers.

Listing 20-4. Safe Interior Pointers using namespace System;

ref class Point

{

public: int X;

};

 

void main()

 

{

 

Point ^p = gcnew Point();

 

interior_ptr<Point^> ip1 = &p;

// Interior pointer to Point

(*ip1)->X = 1;

// Assign 1 to the member variable X

Console::WriteLine("(&ip1)={0:X}\tp->X={1}\t(*ip1)->X={2}",

(int)&ip1, p->X, (*ip1)->X);

 

interior_ptr<int> ip2 = &p->X;

// Pointer to Member variable X

*ip2 += (*ip1)->X;

// Add X to an interior pointer of itself

Console::WriteLine("(&ip2)={0:X}\t*ip2={1}", (int)&ip2, *ip2);

}

Notice I can assign numbers to the value of the interior pointer. I just can’t change the address that the pointer is pointing to. Well, actually, I can, but then the code is no longer safe.

Figure 20-4 shows the results of this little program.

Figure 20-4. Results of IntPtr.exe

I’ve been writing about pointer arithmetic long enough. Let’s look at Listing 20-5 and see an example. This example adds the first eight prime numbers together. It does this by adding the value of the same pointer eight times, but each time the value is added the address of the pointer has advanced the size of an int. This example really doesn’t need an interior pointer and can be written many other (safe) ways.

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

817

Listing 20-5. Interior Pointer and Pointer Arithmetic and Comparision

using namespace System;

void main()

{

array<int>^ primes = gcnew array<int> {1,2,3,5,7,11,13,17};

interior_ptr<int> ip = &primes[0];

// Create the interior pointer

int total = 0;

 

while(ip != &primes[0] + primes->Length)

// Comparing pointers

{

 

total += *ip;

 

ip++;

// Add size of int to ip not 1

}

 

Console::WriteLine("Sum of the first 8 prime numbers is {0}", total);

}

Figure 20-5 shows the results of this little program.

Figure 20-5. Results of IntPtrArth.exe

Pinning Pointers

If you are a seasoned traditional C++ programmer, you probably saw immediately a problem with the handle’s ability to change addresses. There is no fixed pointer address to access the object in memory. In prior versions of C++/CLI (Managed Extensions for C++), the same syntax was used for addressing managed and unmanaged data. Not only did this lead to confusion, but it also did not make it apparent that the pointer was managed and thus could change. With the new handle syntax, it is far less confusing and readily apparent that the object is managed.

Unfortunately, the volatility of the handle address also leads to the problem that passing a handle to a managed object, as a parameter to an unmanaged function call, will fail. To solve this problem, C++/CLI has added the pin_ptr<> keyword, which stops the CLR from changing its location during the compacting phase of garbage collection. The pointer remains pinned so long as the pinned pointer stays in scope or until the pointer is assigned the value of nullptr.

Unsafe Code The pin_ptr<>, since it deals with providing specific address locations into memory, is an unsafe operation.

The pin_ptr<> uses template syntax where you place the type of object you want to pin within the angle [<>] brackets. For example:

pin_ptr<int>

I covered templates in Chapter 4.