Platform Invoke

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Six.  An Introduction to COM Interop

Platform Invoke

Although the focus of this book is COM Interop, I would be remiss if I didn't discuss Platform Invocation Service (PInvoke), which allows you to call non-COM DLLs, Windows API and System functions. To use PInvoke perform the following steps:

  • Declare a function in your managed code that has an equivalent parameter list and return value to the unmanaged function. You will need to use the static and extern keyword in your function declaration.

  • Use the System.Runtime.InteropServices.DllImport attribute on your function declaration to (among other things) indicate which unmanaged DLL the implementation of the method resides in.

  • Optionally specify custom marshaling attributes for the parameters and return values if necessary.

  • Call the declared function the same way you would call any managed code.

We'll explore the first two steps in this chapter and we'll talk about the third step in Chapter 7. Let's look at the simplest example using PInvoke. In this example we'll call the GetCurrentThreadId function which resides in the Kernel system DLL (kernel32.dll). The GetCurrentThreadId method is defined as follows :

 DWORD GetCurrentThreadId(VOID); 

The following code shows the function declaration you would need to call the GetCurrentThreadId Windows API function from managed code. This declaration should go in the same managed source file from which you will call the GetCurrentThreadId function.

 using System.Runtime.InteropServices; [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId(); 

On the first line we have a "using" statement for the System.Runtime.InteropServices namespace. On the next two lines we declare the managed equivalent for the GetCurrentThreadId function. Notice that we use the static and extern keywords to indicate that the method is contained in an external DLL. On line 2 we use the DllImport attribute to indicate that the GetCurrentThreadId resides in the kernel32.dll system DLL. You can find out which System DLL a Windows API function is implemented in using the Platform SDK documentation.

One of the biggest challenges when using PInvoke is mapping the types of the parameters and return values of the original, unmanaged function to the correct managed type. Fortunately, the mapping that I showed you in Table 6-1 applies to PInvoke as well as COM. The GetCurrentThreadId method returns a DWORD, which is actually a typedef for unsigned long. For your convenience, Table 6-7 shows the mapping from some common Win32 types to CTS and C# types.

Table 6-7. Mapping Win32 types to .NET types

Win32 API (wtypes.h)

CTS Type

C# Type

HANDLE/INT/LONG/BOOL

System.Int32

int

UINT/ULONG/DWORD

System.Uint32

uint

BYTE

System.Byte

byte

SHORT

System.UInt16

ushort

WORD

System.Int16

short

BOOL

System.Boolean

int

CHAR

System.Sbyte

sbyte

LPSTR/LPWSTR

System.String in, System.Text.StringBuilder out

string in, System.Text.StringBuilder out

LPCSTR/LPCWSTR

System.String

string

FLOAT

System.Single

float

DOUBLE

System.Double

double

Looking at Table 6-7 you can see that a DWORD maps to the CTS type System.Uint32 or simply uint in C#, this is why the managed declaration of the function returns a uint.

The following snippet shows how we would call the GetCurrentThreadId method from managed code.

 private void Test_Thread(object sender,       System.EventArgs e) {    lblThreadID.Text=GetCurrentThreadId().ToString(); } 

Notice that there is nothing special about the calling code. We call the GetCurrentThreadId function the same way we would call any function.

Let's look at a slightly more complicated example. We'll call the MessageBox Win32 API function. The MessageBox function displays a message box; it is defined as follows in the Win32 API:

 int MessageBox(HWND hWnd,LPCTSTR lpText, LPCTSTR lpCaption,UINT uType); 

This function is implemented in the system DLL called User32.dll. Like many Windows functions that deal with strings there are actually 2 versions of this function in User32.dll. There is a function called MessageBoxA that is used with ANSI (single-byte) strings and a method called MessageBoxW that is used with Unicode (multi-byte) strings. You can call the MessageBoxA and MessageBoxW functions explicitly if you want to, but in most cases when you are writing code in C/C++ you can just call the MessageBox function, your compiler and linker will figure out which function to use depending on whether you choose to do a Unicode or ANSI build.

The simplest code that you can write to declare the MessageBox function for use by managed code is shown below.

 using System.Runtime.InteropServices; [DllImport("User32.dll")] public static extern int MessageBox(int hWnd,string pText,           string pCaption,uint uType); 

Managed code that calls this function is shown below.

 private void cmdPInvoke_Click(object sender, System.EventArgs e) {   string strText="Hello VS Live!";   string strCaption="PInvoke Demo";   MessageBox(0,strText,strCaption,0); } 

The MessageBox function has 2 strings in its parameter list: the caption and the text that is displayed in the message box. The CLR will call either MessageBoxA (for Ansi strings) or MessageBoxW (for Unicode strings) depending on how you choose to marshal the strings. You can choose to marshal the string as an ANSI or Unicode string using the CharSet property on the DllImport attribute as shown here.

 [DllImport("User32.dll",  CharSet=CharSet.Unicode  )] public static extern int MessageBox(int hWnd,string pText,           string pCaption,uint uType); 

In this case we are specifying that the strings should be marshaled as Unicode, so the CLR will pass the text and caption strings in Unicode format to the MessageBoxW function. There are 4 possible values for the CharSet field as shown in Table 6-8.

Table 6-8. Values for the CharSet enumeration

Value

Description

Ansi

Strings are marshaled in single byte Ansi format

None

Obsolete (same as Ansi)

Unicode

Strings are marshaled in 2-byte, Unicode format

Auto

Unicode on Win NT and 2K, Ansi on Windows 9x

If you don't want the CLR to determine which function to call, you can set the ExactSpelling property on the DllImport attribute to true. This will tell the CLR to interpret the specified function name literally. If you change the function declaration to look as follows:

 [DllImport("User32.dll",  ExactSpelling=true  , CharSet=CharSet.Unicode)] public static extern int MessageBox(int hWnd,string pText,   string pCaption,uint uType); 

you will receive a runtime error that says that the runtime could not find an entry point called MessageBox. You will now have to explicitly specify which MessageBox function you want to use as follows:

 [DllImport("User32.dll",  ExactSpelling = true  , CharSet=CharSet.Unicode)] public static extern int  MessageBoxW  (int hWnd,string pText,     string pCaption,uint uType); 

You will also have to change your calling code to use MessageBoxW instead of MessageBox.

 private void cmdPInvoke_Click(object sender,     System.EventArgs e) {     string strText="Hello VS Live!";     string strCaption="PInvoke Demo";  MessageBoxW  (0,strText,strCaption,0); } 

If you don't like having to use the name MessageBoxW in your calling code, you can use the EntryPoint property to specify a different name for the function when it is called from managed code. The following function declaration will allow you to call the MessageBox function as ShowPopup from managed code.

 [DllImport("User32.dll", ExactSpelling = true, CharSet=CharSet.Unicode,  EntryPoint="MessageBoxW"  )] public static extern int  ShowPopup  (int hWnd,string pText,       string pCaption,uint uType); 

Here is managed code that calls the ShowPopup function (which is actually calling MessageBoxW).

 private void cmdPInvoke_Click(object sender, System.EventArgs e) {   string strText="Hello VS Live!";   string strCaption="PInvoke Demo";  ShowPopup  (0,strText,strCaption,0); } 

Note

Calling the Show method on the System.Windows.Forms.MessageBox is the recommended way to display a message box in the .NET Framework. I show you how to call the MessageBox API function as an example of what you could do, and not what you should do.


The last DllImport property that we will look at in this chapter is the SetLastError property. Set this property to true to tell the CLR that the unmanaged function that you are calling will call the Win32 API SetLastError function before returning. If SetLastError is true, the CLR will call GetLastError and cache the return value. You can access the error value by calling Marshal.GetLastWin32Error. The following code demonstrates how to use SetLast-Error and Marshal.GetLastWin32Error with the CreateFile Win32 API function:

 1.  const uint GENERIC_READ = 0x80000000; 2.  const uint OPEN_EXISTING = 3; 3.  [DllImport("Kernel32.dll", SetLastError=true)] 4.  static extern int CreateFile(string filename, 5.    uint desiredAccess,uint shareMode, 6.    uint attributes,uint creationDisposition, 7.    uint flagsAndAttributes,uint templateFile); 8.  [DllImport("Kernel32.dll")] 9.  static extern bool CloseHandle(int hFile); 10. private void cmdCreateFile_Click(object sender, 11.   System.EventArgs e) 12. { 13.   int error, handle, nRead; 14.   byte[] buffer; 15.   bool bIsOkay; 16.   ASCIIEncoding encoding=new ASCIIEncoding(); 17.   buffer=new Byte[256]; 18.   handle=CreateFile(txtFileName.Text, 19.     GENERIC_READ, 20.     0,0,OPEN_EXISTING,0,0); 21.   error=Marshal.GetLastWin32Error(); 22.   if (error != 0) 23.     System.Windows.Forms.MessageBox.Show( 24.       "The error number is: " + 25.       error.ToString()); 26. // 27. // Do something with the file... 28. // 29.    CloseHandle(handle); 30. } 

Lines 1 and 2 contain declarations of constants that we need to use the CreateFile Win32 API function. Lines 3 thru 7 contain a managed code declaration of the CreateFile function. Notice that we set the SetLastError property on DllImport to true on line 3. Lines 8 and 9 contain a managed code declaration for the CloseHandle function. On line 18 we open a file. Because we set the SetLastError property to true in our managed code declaration for the CreateFile function we can call Marshal.GetLastWin32Error( ) to retrieve any errors from the call to CreateFile. On lines 22 thru 25 we display the error if there is one, and on line 29 we close the file.

We will talk about advanced aspects of PInvoke in Chapter 7, including how to deal with: pointers, callback functions, structures, and custom marshaling.


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

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