Export (0) Print
Expand All

Platform Invocation Services

Visual Studio .NET 2003

The common language runtime provides Platform Invocation Services, or PInvoke, that enables managed code to call C-style functions in native dynamic-linked libraries (DLLs). The same data marshaling is used as for COM interoperability with the runtime and for the "It Just Works," or IJW, mechanism.

An important and unique feature of Managed Extensions for C++ is that you can use unmanaged APIs directly. Data marshaling is handled automatically. If you do not require customized data marshaling, you do not need to use PInvoke.

The samples in this section just illustrate how PInvoke can be used. PInvoke can simplify customized data marshaling because you provide marshaling information declaratively in attributes instead of writing procedural marshaling code.

PInvoke and the DllImport Attribute

The following example shows the use of PInvoke in a Managed Extensions for C++ program. The native function puts is defined in msvcrt.dll. The DllImport attribute is used for the declaration of puts.

Example

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

[DllImport("msvcrt", CharSet=CharSet::Ansi)]
extern "C" int puts(String *);

int main()
{
   String * pStr = S"Hello World!";
   puts(pStr);
}

Output

Hello World!

The following version of the same example shows IJW in action.

Example

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

#include <stdio.h>

int main()
{
   String * pStr = S"Hello World!";
   char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer(); 
   
   puts(pChars);
   
   Marshal::FreeHGlobal(pChars);
} 

Output

Hello World!

The example illustrates some of the advantages and disadvantages of both methods.

Advantages of IJW

  • There is no need to write DLLImport attribute declarations for the unmanaged APIs the program uses. Just include the header file and link with the import library.
  • The IJW mechanism is slightly faster (for example, the IJW stubs do not need to check for the need to pin or copy data items since that is done explicitly by the developer).
  • It clearly illustrates performance issues. In this case, the fact that you are translating from a Unicode string to an ANSI string and that you have an attendant memory allocation and deallocation. For example, in this case, a developer writing the code using IJW would realize that calling _putws and using PtrToStringChars would be better for performance.
  • If you need to call many unmanaged APIs using the same data, marshaling it once up front and passing the marshaled copy around is much more efficient than re-marshaling every time.

Disadvantages of IJW

  • Marshaling needs to be specified explicitly in code rather than by attributes (which in many cases like this one have suitable defaults).
  • The marshaling code is inline, where it is more invasive in the flow of the application logic.
  • Since the explicit marshaling APIs return IntPtr types for 32-bit to 64-bit portability, extra ToPointer calls need to be used.

Like many places in Managed Extensions, the specific method exposed by Managed Extensions is the more efficient, explicit method, at the cost of some additional complexity.

If the application uses mainly unmanaged data types or if it calls more unmanaged APIs than .NET Framework APIs, using the IJW feature will generally be preferable. To call an occasional unmanaged API in a mostly managed application, the choice is more subtle.

Marshaling Arguments

With PInvoke, no marshaling is required between managed and C++ native primitive types with the same form. For example, no marshaling is required between Int32 and int, and Double and double.

Marshaling is required for types that do not have the same form. This includes char, string, and struct types. The following table shows the mappings used by the marshaler for various types.

wtypes.h C++ Managed Extensions Common language runtime
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
BOOL long bool Boolean
DWORD unsigned long unsigned long UInt32
ULONG unsigned long unsigned long UInt32
CHAR char char Char
LPSTR char * String * [in], StringBuilder * [in, out] String [in], StringBuilder [in, out]
LPCSTR const char * String * String
LPWSTR wchar_t * String * [in], StringBuilder * [in, out] String [in], StringBuilder [in, out]
LPCWSTR const wchar_t * String * String
FLOAT float float Single
DOUBLE double double Double

The marshaler automatically pins memory allocated on the runtime heap if its address is passed to an unmanaged function. Pinning prevents the garbage collector from moving the allocated block of memory during compaction.

In the example shown earlier in this topic, the CharSet parameter of DllImport specifies how managed Strings should be marshaled; in this case, they should be marshaled to ANSI strings for the native side.

Marshaling information for individual arguments of a native function can be specified using the MarshalAs attribute. There are several choices for marshaling a String * argument: BStr, ANSIBStr, TBStr, LPStr, LPWStr, and LPTStr. The default is LPStr.

Example

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

[DllImport("msvcrt", EntryPoint="puts")]
extern "C" int puts([MarshalAs(UnmanagedType::LPWStr)] String *);

int main()
{
   String * pStr = S"Hello World!";
   puts(pStr);
}

Output

H

In this example, the string is marshaled as a double-byte Unicode character string, LPWStr. The output is just the first letter of Hello World! because the second byte of the marshaled string is null, and puts interprets this as the end-of-string marker.

The MarshalAs attribute is in the System::Runtime::InteropServices namespace. The attribute can be used with other data types such as arrays.

PInvoke with Windows APIs

PInvoke is also convenient for calling functions in Windows.

In this example, a Managed Extensions program interoperates with the MessageBox function that is part of the Win32 API.

Example

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HWND;
[DllImport("user32", CharSet=CharSet::Ansi)]
extern "C" int MessageBox(HWND hWnd, String * pText, String * pCaption, unsigned int uType);

int main()
{
   String * pText = S"Hello World! ";
   String * pCaption = S"PInvoke Test";
   MessageBox(0, pText, pCaption, 0);
}

The output is a message box with the title PInvoke Test and the text Hello World! in it.

The marshaling information is also used by PInvoke to look up functions in the DLL. In user32.dll there is in fact no MessageBox function, but CharSet=CharSet::Ansi enables PInvoke to use MessageBoxA, the ANSI version, instead of MessageBoxW, which is the Unicode version. In general, using Unicode versions of unmanaged APIs should be preferred because that eliminates the translation overhead from the native Unicode format of .NET Framework string objects to ANSI.

When Not to Use PInvoke

Using PInvoke is not suitable for all C-style functions in DLLs. For example, suppose there is a function MakeSpecial in mylib.dll declared as follows.

char * MakeSpecial(char * pszString);

If we use PInvoke in a Managed Extensions application, we might write something similar to:

[DllImport("mylib")]
extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String *);

The difficulty here is that we cannot delete the memory for the unmanaged string returned by MakeSpecial. Other functions called via PInvoke return a pointer to an internal buffer that does not need to be deallocated by the user. In this case, using the IJW feature is the obvious choice.

Performance Considerations

PInvoke has an overhead of between 10 and 30 x86 instructions per call. In addition to this fixed cost, marshaling creates additional overhead. There is no marshaling cost between blittable types that have the same representation in managed and unmanaged code. For example, there is no cost to translate between int and Int32.

For higher performance, it may be necessary to have fewer PInvoke calls that marshal as much data as possible, rather than have more calls that marshal less data per call. Or somewhat more memorably: prefer a chunky over a chatty API.

Show:
© 2014 Microsoft