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 Invoke—Data 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 TypeC# TypeSize
HANDLEVoid*IntPtr32 bits
BYTEUnsigned charbyte8 bits
SHORTShortshort16 bits
WORDUnsigned shortushort16 bits
INTIntint32 bits
UINTUnsigned intuint32 bits
LONGLongint32 bits
BOOLLongint32 bits
DWORDUnsigned longuint32 bits
ULONGUnsigned longuint32 bits
CHARCharcharVaries
LPSTRChar*string or StringBuilderVaries
LPCSTRConst char*string or StringBuilderVaries
LPWSTRWchar_t*string or StringBuilderVaries
LPCWSTRConst wchar_t*string or StringBuilderVaries (Unicode sizing)
FLOATFloatsingle32 bits
DOUBLEDoubledouble64 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
PropertyDescription
BestFitMappingIndicates whether to use best-fit mapping between ANSI and Unicode characters.
CallingConventionSets 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.
CharSetControls the marshalling of string parameters.
EntryPointThis 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.
ExactSpellingControls whether the Common Language Runtime will search the DLL for alternate entry points.
PreserveSigIf set, indicates that the managed signature is a pure translation of the unmanaged signature.
SetLastErrorIf set, the callee uses the SetLastError Win32 API method before returning from the attribute-decorated method.
ThrowOnUnmappableCharIf set, indicates that an exception should be thrown when a Unicode character is mapped to an unknown ANSI character (appears as a ?).
dllNameThis 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 Sample—The 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:windowsmediaWindows XP Startup.wav", 0);
    }

    [DllImport(@"c:windowssystem32winmm.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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.138.97