Platform Invoke (PInvoke)


Platform Invoke (P/Invoke)

So far, this chapter has covered how to communicate between COM DLLs and managed DLLs in both directions. This next section will discuss how to use managed code to invoke code that exists in unmanaged DLLs that are not registered with COM. Sometimes I feel like an old man describing 8-track tapes to teenagers when I explain to programmers that there are DLLs that have exposed functions but that don't have a visible object hierarchy, nor are they registered with COM or managed by the .NET Framework. I have actually heard people say, "You can actually do that?" Yes. You can indeed do that. In fact, the entire low-level Win32 API does that. The next section of this chapter is all about platform invoke: what it is, how to use it, and when to use it.

Introduction to Platform Invoke

Platform invoke is a concept rather than a method call. The idea behind platform invoke is that you can declare a method in C# that is external, as indicated by the keyword extern. This keyword tells the Common Language Runtime that the actual source for that method is stored elsewhere. When combined with enough attributes to identify the unmanaged DLL and unmanaged method name and parameters, the Common Language Runtime is able to marshal (see earlier sections for information about marshalling) between your managed code and the unmanaged library.

Consuming Unmanaged DLLs

When you want to make use of a method in an unmanaged DLL, you need to provide the Common Language Runtime with enough information to do the appropriate marshalling for you. First and foremost, the Common Language Runtime needs to know where to find the code for the method you're invoking. Secondly, the Common Language Runtime needs to know what the data types are of all the arguments and return values. Finally, you can also specify how you want those methods invoked for maximum compatibility with languages such as C and Delphi, which pass parameters differently and store strings differently.

Platform InvokeData Marshalling

Data marshalling with platform invocations takes place in much the same manner as with COM marshalling. The main difference is that there is no callable wrapper. The method calls are marshaled directly to the operating system (hence the term platform). Table 13.3 lists the data type translation and marshalling between C# and the operating system data types found in the Win32 API header file wtypes.h. You can find a similar chart in the MSDN library.

Table 13.3. Platform Invoke Marshalling Overview

Unmanaged Type (wtypes.h)

Unmanaged C Type

C# Type

Size

HANDLE

Void*

IntPtr

32 bits

BYTE

Unsigned char

byte

8 bits

SHORT

Short

short

16 bits

WORD

Unsigned short

ushort

16 bits

INT

Int

int

32 bits

UINT

Unsigned int

uint

32 bits

LONG

Long

int

32 bits

BOOL

Long

int

32 bits

DWORD

Unsigned long

uint

32 bits

ULONG

Unsigned long

uint

32 bits

CHAR

Char

char

Varies

LPSTR

Char*

string or StringBuilder

Varies

LPCSTR

Const char*

string or StringBuilder

Varies

LPWSTR

Wchar_t*

string or StringBuilder

Varies

LPCWSTR

Const wchar_t*

string or StringBuilder

Varies

(Unicode sizing)

FLOAT

Float

single

32 bits

DOUBLE

Double

double

64 bits


When you find yourself looking at various unmanaged DLLs (including the Win32 API), and you need to figure out how to decorate your method wrappers with the right attributes and how to declare your parameters and return values, the Table 13.3 will help you determine the data types. Just find the unmanaged data type in the left two columns and then locate the appropriate C# type and size, and you should be able to create a proper platform invoke extern method.

Everything that you do rests on the use of one main attribute: DllImportAttribute. You use this attribute to decorate an extern method in C# so that it will match up with an unmanaged function in an unmanaged DLL. Provided the match is appropriate, you will then be able to call the declared method as if it were a purely managed method, leaving the details of marshalling to and from the OS neatly tucked behind the Common Language Runtime curtains. Table 13.4 contains a list of the properties you can set on the DllImportAttribute attribute.

Table 13.4. DllImportAttribute Public Fields

Property

Description

BestFitMapping

Indicates whether to use best-fit mapping between ANSI and Unicode characters.

CallingConvention

Sets or gets the calling convention of an entry point (Cdecl, Fastcall, StdCall, ThisCall, Winapi). Winapi isn't a calling convention, but a flag to use the default for the OS on which the method is running.

CharSet

Controls the marshalling of string parameters.

EnTRyPoint

This is the name or the ordinal value of the entry point (function) to be called. If omitted, the Common Language Runtime will use the name of the C# method marked with the DllImportAttribute attribute.

ExactSpelling

Controls whether the Common Language Runtime will search the DLL for alternate entry points.

PreserveSig

If set, indicates that the managed signature is a pure translation of the unmanaged signature.

SetLastError

If set, the callee uses the SetLastError Win32 API method before returning from the attribute-decorated method.

THRowOnUnmappableChar

If set, indicates that an exception should be thrown when a Unicode character is mapped to an unknown ANSI character (appears as a ?).

dllName

This is not a public field. It is set in the constructor of the DllImportAttribute attribute only and indicates the name of the DLL in which the unmanaged code resides.


Platform Invoke SampleThe Win32 API

The following is an example of consuming an unmanaged DLL from within managed code. The problem with a lot of unmanaged DLL consumption is that the method signatures are difficult and hard to use. As a result, the following code is a quick example using one of the more simple methods in the Win32 API, sndPlaySoundA (you could also use the corresponding W function for Unicode support).

Listing 13.5. A Platform Invoke Sample
 using System; using System.Runtime.InteropServices; namespace SAMS.Evolution.CSharpUnleashed.Chapter13.Pinvoke {   /// <summary>   /// Summary description for Class1.   /// </summary>   class Class1   {     /// <summary>     /// The main entry point for the application.     /// </summary>     [STAThread]     static void Main(string[] args)     {       sndPlaySoundA(@"C:\windows\media\Windows XP Startup.wav", 0);     }     [DllImport(@"c:\windows\system32\winmm.dll", EntryPoint="sndPlaySoundA",              CallingConvention=CallingConvention.StdCall, SetLastError=true)]     public static extern unsafe int sndPlaySoundA(string pszSound, uint fuSound);   } } 

In Listing 13.5, I used the DllImportAttribute attribute to make use of a function in the winmm.dll library. The entry point (function name) is called sndPlaySoundA (ANSI convention, the Win32 API uses methods that end in W for wide string [Unicode] methods). The method will use the stdcall format for passing parameters on the stack. After the method has executed, it plays the Windows XP startup sound once in synchronous mode (indicated by the 0 parameter value).

The beauty of DllImportAttribute is that after you've declared the extern method, you can call it as if the method were pure managed code and you have to worry about setting up marshalling conventions only once.

It might seem daunting trying to manually figure out how all of these methods are supposed to be called and what the parameters are supposed to be. The good news is that a lot of the hard work has already been done for you. There are various sources for previously generated Win32 API declarations, such as the user samples area on www.gotdotnet.com and others.

When to Use Platform Invoke

When you are using languages such as C# or Visual Basic .NET, the line between when to use P/Invoke and when not to use it is pretty clearly drawn. If the particular task you want to perform is already provided for you by the .NET Base Class Library, there is no need to use Interop and platform invoke. However, if you are using Visual C++ .NET, the line is a bit more clouded. There are times when invoking the method call using native, unmanaged code from within C++ .NET will actually function faster than some of the code provided by the .NET Framework. For everyone except the most hard-core Win32 API experts, I suggest using the BCL classes whenever available, and resorting to using P/Invoke only when the task you need to perform cannot be done within managed code.



    Visual C#. NET 2003 Unleashed
    Visual C#. NET 2003 Unleashed
    ISBN: 672326760
    EAN: N/A
    Year: 2003
    Pages: 316

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net