Platform Invocation Services (PInvoke)

for RuBoard

Platform Invocation Services, also known as "PInvoke," makes unmanaged exported DLL functions available to managed client code. PInvoke allows this to be done from managed code written in any .NET programming language. Notice that PInvoke is not the name of a class, or a method, but is just a nickname for Platform Invocation Services. PInvoke allows marshaling between CLR data types and native data types, and bridges other differences between the managed and unmanaged runtime environments. Although PInvoke is primarily used to access the Win32 APIs, it can be used to call into your own legacy DLLs that you may find are still useful. Unfortunately, PInvoke is in most circumstances a one-way street. You can use it to call from managed code into unmanaged DLL code and of course return back into managed code. PInvoke is used to access global exported DLL functions, so even though it is possible for DLLs to export class methods , they are currently not accessible via PInvoke.

If you are an experienced Windows programmer and have a good knowledge of the Win32 API, you may be tempted, after learning about PInvoke, to call a familiar Win32 API function to perform a task. A secure .NET environment, however, will not give most assemblies permission to call unmanaged code. Usually there will be a native .NET Framework class method that can accomplish your aim, and you should use .NET Framework classes whenever possible. Occasionally it will be necessary to drop down to the underlying platform, and then PInvoke is invaluable.

A Simple Example

Let's begin with a very simple example of the use of PInvoke, to call the Windows MessageBox function. Our sample program is in the directory SimplePInvoke .

 // SimplePInvoke.cs  using System;  using System.Runtime.InteropServices;  class SimplePInvoke  {  [DllImport("user32.dll", EntryPoint="MessageBoxA")]  public static extern int ShowMessage(int hWnd,        string text, string caption, int type);     public static void Main(string[] args)     {        ShowMessage(0, "Hello, World", "From PInvoke", 0);     }  } 

The key step is to place a DllImport attribute before the prototype of the function we want to call. The function must take ordinary C# data types as parameters, which have natural mappings to the C data types of the native function. The function will be treated as a static method in the class where it is defined. The one required parameter to the DllImport attribute is the name of the DLL exporting the function. There are various optional, named parameters, that can be used with DllImport . For a complete list, consult the documentation of the DllImportAttribut class in the System.Runtime.InteropServices namespace. In our example, we use the EntryPoint attribute to specify the name by which the function is exported in the DLL. The name of the static method in the class can then be different, and will be the name to be used in the C# code that calls the method. In our example, the Win32 function has the name MessageBoxA and our C# code calls the method under the name ShowMessage . Figure 14-12 shows the output from this little program.

Figure 14-12. Calling the Win32 MessageBox function through PInvoke.

graphics/14fig12.gif

Marshaling out Parameters

The previous PInvoke example did not demonstrate how PInvoke automatically marshals out parameters for you where there is a clear mapping between Win32 and the CLR types. This is because the MessageBox takes only in parameters. The next example calls the GetComputerName and GetLastError APIs via PInvoke. The code for this example is in the directory PInvoke .

 // PInvoke.cs  using System;  using System.Text;   using System.Runtime.InteropServices;  public class Test  {  [DllImport("kernel32.dll", CharSet=CharSet.Ansi)]  public static extern bool GetComputerName(  StringBuilder  name, out uint buffer);  [DllImport("kernel32.dll")]  public static extern uint GetLastError();     public static int Main(string[] args)     {        bool result = true;        uint error = 0;  StringBuilder name = new StringBuilder(128);  uint length = 128;        result = GetComputerName(name, out length);        if (result == true)           Console.WriteLine(name);        else        {           error = GetLastError();           Console.WriteLine("Error: {0:x}", error);        }         return 0;     }  } 
Translating Types

Since GetComputerName returns a name, StringBuilder was used instead of string. [5] For input only arguments you can use string . An out attribute was placed on the length attribute because the second argument to GetComputerName is a pointer. Unsigned types were used because DWORD is an unsigned 32 bit quantity. For comparison, here are the prototypes of the corresponding Win32 functions:

[5] Instances of string are immutable, so we use the StringBuilder class, which was discussed in Chapter 3.

 BOOL GetComputerName(    LPTSTR lpBuffer,  // computer name    LPDWORD lpnSize   // size of name buffer  );  DWORD GetLastError(VOID); 

Some CLR types do not map directly into unmanaged types. You have to tell the Execution Engine ( mscoree.dll ) how to translate to a BSTR. You do that by annotating the declaration with the MarshalAs attribute:

 [MarshalAs(UnmanagedType.BStr)] public string foo 

The UnmanagedType enumeration lists all the translatable types.

for RuBoard


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

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