|
This section is going to look at interop considerations that apply only to managed C++ code.
No marshaling is required between managed and unmanaged types that have the same representation, such as Int32 and int . Other types will need to be converted. Table 12-3 shows how types are represented in the Win32 API, in unmanaged C++, in managed C++, and in the .NET Framework.
Windows | C++ | Managed C++ | .NET Framework |
---|---|---|---|
HANDLE | void* | void* | IntPtr , UIntPtr |
BYTE | unsigned char | unsigned char | Byte |
SHORT | short | short | Int16 |
WORD | unsigned short | unsigned short | UInt16 |
INT | int | int | Int32 |
UINT | unsigned int | unsigned int | UInt32 |
LONG | long | long | Int32 |
ULONG | unsigned long | unsigned long | UInt32 |
DWORD | unsigned long | unsigned long | UInt32 |
FLOAT | float | float | Single |
DOUBLE | double | double | Double |
BOOL | long | bool | Boolean |
CHAR | char | char | Char |
LPSTR | char* | [in] String* , [in,out] , StringBuilder* | [in] String , [in,out] , StringBuilder |
LPCSTR | const char* | String* | String |
LPWSTR | wchar_t* | [in] String* , [in,out] , StringBuilder* | [in] String , [in,out] , StringBuilder |
LPCWSTR | const wchar_t* | String* | String |
Marshaling of structures and arrays is covered in Chapter 13, along with more details of string marshaling.
The previous section introduced the concept of pinningobtaining a pointer to a managed object and telling the garbage collector not to move the object while the pointer is still in scope.
In managed C++, you use the __pin keyword to create a pinning pointer from a __gc pointer, and the action of creating a pinning pointer pins the object in memory. Listing 12-5 contains an example. You can find this sample in the Chapter12\CppPin folder of the books companion content.
#using<mscorlib.dll> usingnamespaceSystem; #include<iostream> usingnamespacestd; //Amanagedclass __gcclassGcClass { public: intval; GcClass(intn):val(n){} }; #pragmaunmanaged voidUnmanagedFunction(int*pn) { cout<< "nis " <<*pn<<endl; } #pragmamanaged voidmain() { //Createamanagedinstance GcClass*pgc=newGcClass(3); //Createapinningpointer GcClass__pin*ppin=pgc; //Passamembertothefunction UnmanagedFunction(&ppin->val); //Zerooutthepinningpointer ppin=0; }
A managed class contains an integer member, whose address I want to pass to an unmanaged function. This means that the address of the managed instance must not change for the duration of the unmanaged function call. I create a pinning pointer, which locks the object in memory, and can then pass the address of a data member to the function. When the pinning pointer is zeroed outor goes out of scopethe instance will be free to move.
Youll need to take special care when calling functions in DLLs that are class members . When a C++ class member function is compiled, the compiler generates a mangled name a name for the function that encodes its actual name, plus details of the class it belongs to, and its argument and return types. If you use Platform Invoke to call such a function, youll need to specify the mangled name and use the appropriate calling convention.
Here is an example that shows how to call a C++ member function. First lets look at the declaration of the class and the exported function:
classMyClass { public: intSquare(inti){returni*i;} };
And here is the Platform Invoke prototype you can use to call it:
[DllImport("MyDll.dll",EntryPoint="?Square@MyClass@@QAEHH@Z", CallingConvention=CallingConvention::ThisCall)] extern "C" intSquare(IntPtrpThis,inti);
Tip | You can find the exported name of a C++ member function by using the Dumpbin.exe utility with the /exports option to examine the DLL. |
The EntryPoint parameter gives the exported name of the member function, but the programmer can call it using the more friendly name Square . Because the function is a C++ member function but is not static, it needs to be passed a pointer to an object of the appropriate type, which will act as the this pointer for the function. You can see the this pointer being passed as the first parameter. You also need to choose the correct calling convention: for C++ member functions, this will be the ThisCall convention, which assumes that the first parameter in the call will be the this pointer.
Youve seen how Platform Invoke is used to call unmanaged functions from managed code, but many times in managed C++ code this appears to happen automatically. For instance, consider the following class definition:
#include<iostream> usingnamespacestd; __gcclassGcClass { public: intval; GcClass(intn):val(n) { cout<< "GcClass::ctor" <<endl; } };
The constructor for the managed class uses unmanaged iostream functionality to display a message, without any need for coding Platform Invoke prototypes .
The It Just Works (or IJW) mechanism allows C++ coders to call unmanaged functions in DLLs simply by including the relevant header file and linking with a link library. Because the combination of a header file and link library contains all the information needed by Platform Invoke, in the simplest cases no further work is needed by the programmer.
Simple types will be marshaled automatically, as described in Table 12-3. Sometimes, however, youll need to marshal data manually. This is especially true where arguments consist of strings, arrays, and structures. Marshaling of these types is covered in Chapter 13.
Both IJW and Platform Invoke use the same underlying mechanism, but there are some cases when you might prefer one to the other.
The IJW mechanism has several features that might provide an advantage over Platform Invoke in some situations:
There is no need for Platform Invoke prototypes.
IJW can be slightly faster than Platform Invoke because the developer will have done any pinning that is needed.
Marshaling is more explicit, and this can help developers decide on the most efficient marshaling method.
If youre calling a function more than once, it will be more efficient to marshal the data once and then call it multiple times.
IJW also has some disadvantages:
Marshaling needs to be specified in code rather than declared by attributes.
Marshaling code is inline, which might interfere with program logic.
Marshaling APIs return IntPtr for pointers, so extra ToPointer calls will be needed to extract the underlying pointer.
The advantages of Platform Invoke can be summarized as follows :
It works the same way in every .NET language.
There is no need to write marshaling code.
It is a simple declarative mechanism.
There are, however, some disadvantages to Platform Invoke:
Marshaling is performed for every call. This can be wasteful if the same call is being made several times.
Some functions are not suitable for calling via Platform Invoke. For example, consider an unmanaged function that dynamically allocates memory for a string and returns a char* . The return type will be mapped onto a String* , but the caller has no way of deallocating this memory after it has been used. In this case, IJW provides a better mechanism.
C++ is unique in the .NET world, in that it has two separate ways to invoke unmanaged functions. Which method you choose will depend on how you are proposing to use the unmanaged functions that you call.
|