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

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

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

818 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.

C H A P T E R 2 1

■ ■ ■

Advanced Unsafe or Unmanaged C++ .NET Programming

In the previous chapter, you dealt for the most part with the mixing of unsafe (or unmanaged) code directly into your safe (or managed) code. This approach only works if you have access to all the source code. Unfortunately, that is not a luxury that we developers always have. This chapter will address this issue by covering how C++/CLI interfaces with code that

You don’t have access to.

Accesses objects outside of .NET sandbox and can’t be accessed with a .NET language.

Is written in a language not supported by .NET.

Has a perfectly acceptable non-.NET implementation; rewriting would be a waste of time, money and/or resources.

There will be other situations where your code interfaces with some external non-.NET code that will not be implemented in .NET.

Basically, this chapter is about interfacing .NET applications with third-party DLLs or COM components. While each requires a different method to perform this interface, neither method is that difficult.

I think it funny (read: waste of time) how some books allocate a large portion of their text covering these interfaces explaining in great detail the internal flow of data and numerous other aspects. Personally, I don’t see the point. Just tell me how to do it. That’s my approach to this chapter. If you want all the other stuff, there are literally hundreds of Web sites that provide this information.

This chapter will start by examining how to interface with standard unmanaged DLLs using simple data types, and then show how to interface with more complex data types using data marshaling. Finally, I’ll move on to interfacing your .NET code with COM components.

P/Invoke

Making calls out of the .NET managed environment to unmanaged DLLs is handled by a mechanism in .NET called P/Invoke (short for Platform Invoke). The basic idea behind P/Invoke is that it finds the DLL and loads it into memory, marshals its arguments (converts from managed format to native format) so that the DLL can understand the call, makes the call to the DLL’s function, and then marshals the return value (converts from native format to managed format) so that the managed code understands the results.

825

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.