Advanced Aspects of PInvoke

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Seven.  Advanced .NET to COM Interop

Advanced Aspects of PInvoke

Although it's not strictly a COM Interop topic, I wanted to end this chapter by discussing some advanced aspects of PInvoke. You learned in Chapter 6 that PInvoke is a technology that allows you to make calls to functions in non-COM, unmanaged DLLs. In this section we will look at the following advanced aspects of PInvoke: Pointers, Callback Functions and Structures.

Pointers

Win32 DLLs that use pointers typically use them in one of 2 ways: (1) to return or receive an array, or (2) to return or receive a structure. We'll start our discussion of pointers by looking at a pair of Win32 API functions that demonstrate each of these usages.

The fourth parameter to the CreateFile function API function takes a pointer to a security structure as shown here.

 HANDLE CreateFile( LPCTSTR lpFileName,     DWORD dwDesiredAccess, DWORD dwShareMode,  LPSECURITY_ATTRIBUTES lpSecurityAttributes  ,     DWORD dwCreationDisposition,DWORD dwFlagsAndAttributes,       HANDLE hTemplateFile); 

The second parameter to the ReadFile function is an output parameter that is typed as a pointer to a void array. This parameter contains the data read from the file. The fourth parameter to this function returns a pointer to a DWORD that contains the number of bytes read from the file.

 BOOL ReadFile( HANDLE hFile,  LPVOID lpBuffer  ,     DWORD nNumberOfBytesToRead,  LPDWORD lpNumberOfBytesRead  ,       LPOVERLAPPED lpOverlapped ); 

Let's see how we would call these functions from managed code. The managed declaration for the CreateFile function looks as shown here.

 [DllImport("Kernel32.dll", SetLastError=true)] static extern int CreateFile(string filename,     uint desiredAccess,uint shareMode,     uint securityAttributes,     uint creationDisposition,     uint flagsAndAttributes,uint templateFile); 

Remember that you will need to add a using statement for the System.Runtime.InteropServices namespace. Notice that the fourth parameter, which is typed as a pointer to a structure in the unmanaged declaration, is typed as an unsigned integer (uint) in the managed declaration. If an unmanaged function takes a pointer to a structure, as this function does, and you want to pass a null pointer to the function, you can simply declare it as a uint and then pass in a zero value. If you actually want to pass in a structure, you must declare an equivalent, managed structure and then create your managed declaration so that the managed structure is used wherever the equivalent unmanaged structure is called for. We'll explore how to pass a pointer to a structure later. For now, we'll look at how to pass a null value for a structure.

The managed code declaration for the ReadFile looks as shown here.

 [DllImport("Kernel32.dll", SetLastError=true)] static extern bool ReadFile(int hFile,  byte[] buffer  ,       int nBytes2Read,  out int nBytesRead  ,int overlapped); 

Notice that in this case the second parameter, which is declared as an LPVOID, is mapped to a byte array in the managed declaration. You can convert this byte array to a string using a System.Text.ASCIIEncoding or a System.Text.UnicodeEncoding object depending on the encoding used to write the file. The following code opens a file using the CreateFile function and then reads the first 256 bytes from the file using the ReadFile function.

 1.  using System; 2.  using System.Runtime.InteropServices; 3.  using System.Text; 4.  const uint GENERIC_READ = 0x80000000; 5.  const uint OPEN_EXISTING = 3; 6.  [DllImport("Kernel32.dll", SetLastError=true)] 7.  static extern int CreateFile(string filename, 8.      uint desiredAccess,uint shareMode, 9.      uint securityAttributes, uint creationDisposition, 10.     uint flagsAndAttributes, uint templateFile); 11. 12. [DllImport("Kernel32.dll", SetLastError=true)] 13. static extern bool ReadFile(int hFile,byte[] buffer, 14.     int nBytes2Read,out int nBytesRead,int overlapped); 15. 16. [DllImport("Kernel32.dll")] 17. static extern bool CloseHandle(int hFile); 18. 19. private void cmdReadFile_Click(object sender, 20.     System.EventArgs e) 21. { 22.     int handle; 23.     int error; 24.     byte[] buffer; 25.     int nRead; 26.     bool bIsOkay; 27.     ASCIIEncoding encoding=new ASCIIEncoding(); 28.     buffer=new Byte[256]; 29.     handle=CreateFile(txtFileName.Text,GENERIC_READ, 30.       0,0,OPEN_EXISTING,0,0); 31.     error=Marshal.GetLastWin32Error(); 32.     if (error != 0) 33.        System.Windows.Forms.MessageBox.Show( 34.        "The error number is: " + error.ToString()); 35.     bIsOkay=ReadFile(handle,buffer,256,out nRead,0); 36.     if (bIsOkay) 37.       System.Windows.Forms.MessageBox.Show( 38.           "The number of bytes read is: " + 39.           nRead.ToString()); 40.     else 41.     { 42.         error=Marshal.GetLastWin32Error(); 43.         System.Windows.Forms.MessageBox.Show( 44.           "The error number is: " + 45.           error.ToString()); 46.     } 47.     txtFileContents.Text=encoding.GetString(buffer); 48.     CloseHandle(handle); 49. } 

On lines 1 through 3 I have a trio of "using" statements. The DllImport attribute resides in the System.Runtime.InteropServices namespace and the ASCIIEncoding class resides in the System.Text namespace. On lines 4 and 5 I declare a pair of constants that I will use in the CreateFile function on line 28. The managed equivalent of the CreateFile function is declared on lines 7 through 10. The managed equivalent of the ReadFile function is declared on lines 12 through 14. I declare a managed equivalent of the CloseHandle function on lines 16 and 17. I need the CloseHandle function to clean up the handle that I receive from the CreateFile function. On lines 22 through 26 I declare some variables . On lines 27 and 28 I declare an instantiate an ASCII-Encoding object and a byte array respectively. On lines 29 and 30 I call the CreateFile function. Notice that I pass zero for the fourth parameter to this function. Passing zero is how I pass in a NULL pointer. The CreateFile function returns a handle which I store in a variable of type int. On line 35 I call the ReadFile function passing in the handle that I received from the CreateFile function. The second parameter to ReadFile is the byte array that will contain the data from the file. The third parameter specifies that I will read at most 256 bytes. The fourth parameter is an output parameter that contains the number of bytes read from the file. Lines 40 through 46 contain error-handling code and on line 47 I use the ASCIIEncoding object to convert the byte array to a string. On line 48 I close the file handle. I won't go into C-style arrays or Safe Arrays because the procedure for handling them in PInvoke is exactly the same as that for COM Interop.

Callback Functions

There are a number of Win32 API functions that use callbacks. A good example is the EnumWindows function, which enumerates all the top-level windows on the screen. For each window the EnumWindows function will invoke a callback function that you define passing in a handle to each window. The unmanaged definition of the EnumWindows function is shown here.

 BOOL EnumWindows(WNDENUMPROC lpEnumFunc,LPARAM lParam); 

The first parameter to the EnumWindows function is a function pointer that points to a WNDENUMPROC, which has the following prototype.

 BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam); 

The second parameter to the EnumWindows function is an application-specific value that is passed through to the EnumWindowsProc callback as its second parameter. The first parameter passed to the EnumWindowsProc callback is the handle for each window.

In order to call the EnumWindows function from managed code I have to first define a managed code delegate that is the equivalent of the EnumWindowsProc as shown here.

 public delegate bool EnumCallBack(int hwnd, int lParam); 

Next I have to create the usual managed code declaration for the EnumWindows function as shown here.

 [DllImport("user32")] public static extern int EnumWindows(       EnumCallBack aCallBack, int data); 

The following code shows how to use the EnumWindows function from managed code.

 1.  public delegate bool EnumCallBack(int hwnd,int lParam); 2.  [DllImport("user32")] 3.  public static extern int EnumWindows( 4.      EnumCallBack aCallBack, int data); 5.  [DllImport("user32")] 6.  public static extern void GetWindowText(int hwnd, 7.      StringBuilder x, int maxCount); 8. 9.  private void cmdEnumerateWindows_Click(object sender, 10.     System.EventArgs e) 11. { 12.     EnumCallBack aCallBack=new 13.         EnumCallBack(MyCallBack); 14.     lstWindows.Items.Clear(); 15.     EnumWindows(aCallBack, 0); 16. } 17. private bool MyCallBack(int hWnd,int data) 18. { 19.     StringBuilder sbCaption=new StringBuilder(256); 20.     GetWindowText(hWnd,sbCaption,sbCaption.Capacity); 21.     if (sbCaption.Length > 0) 22.         lstWindows.Items.Add(sbCaption.ToString()); 23.     else 24.         lstWindows.Items.Add(hWnd.ToString()); 25.     return true; 26. } 

On line 1 I declare the delegate that maps to the WNDENUMPROC call back function. The managed code declaration of the EnumWindows function can be found on lines 2 through 4. I also declare a managed code equivalent of the GetWindowText Win32 API function on lines 6 and 7. I will use this function to print out the caption of each window. Lines 9 through 16 contain some test code that calls EnumWindows. On lines 12 and 13 I create a new instance of the callback delegate that points to a method called MyCallBack. On line 14 I clear a listbox that holds the window captions and then on line 15 I call the EnumWindows function. The MyCallBack function is shown on lines 17 through 26; it simply adds the Window caption to a listbox or if there is no caption, it adds the value of the Windows handle to the listbox. On line 19 I declare and instantiate a StringBuilder object. Remember that we have to use the System.Text.StringBuilder class for output parameters that are strings, because the System.String class is immutable. On line 20 I call the GetWindowText function to retrieve the window caption into the StringBuilder. On lines 21 through 24 we add the caption to a list box. Don't forget that you will need "using" statements for the System.Runtime.InteropServices and System.Text namespaces in order to compile this code.

Structures

Many Win32 functions accept a structure or a pointer to a structure as an argument. In order to call these functions you have to create managed code equivalents of these structures. For instance, consider the GetSystemTime function, which is declared as follows .

 VOID GetSystemTime(LPSYSTEMTIME lpSystemTime); 

The return value of this function is a pointer to a SYSTEMTIME structure that is returned as an output parameter. The SYSTEMTIME structure is declared as shown here.

 typedef struct _SYSTEMTIME {     WORD wYear;     WORD wMonth;     WORD wDayOfWeek;     WORD wDay;     WORD wHour;     WORD wMinute;     WORD wSecond;     WORD wMilliseconds; } SYSTEMTIME, *PSYSTEMTIME; 

In order to use this function from managed code, I start by creating a managed code equivalent of the SYSTEMTIME structure.

 [StructLayout(LayoutKind.Sequential)] public struct MySystemTime {     public ushort wYear;     public ushort wMonth;     public ushort wDayOfWeek;     public ushort wDay;     public ushort wHour;     public ushort wMinute;     public ushort wSecond;     public ushort wMilliseconds; } 

I use the StructLayout attribute to specify how the fields in the structure should be layed out in memory. In this case, I specify that they should be laid out sequentially in the order in which they appear, just like a C structure.

Note

Other possible values for the LayoutKind enumeration are: Auto (the default), which specifies that we want the runtime to choose an appropriate layout and Explicit, which allows you to explicitly specify the byte offset of each field in the structure.


Next I declare fields for the managed structure that match the type and size of the fields in the unmanaged, SYSTEMTIME structure. The managed, MySystemTime structure that I created has the same memory footprint as the unmanaged SYSTEMTIME structure. Finally, I add a managed code declaration for the GetSystemTime function that uses the managed code structure wherever the SYSTEMTIME structure is called for.

 [DllImport("Kernel32.dll")] public static extern void GetSystemTime(       out MySystemTime st); 

The following code shows how easy it is to call the GetSystemTime function from managed code once you have followed the preceding steps.

 private void cmdGetDateTime_Click(object sender,     System.EventArgs e) {     MySystemTime stm;     GetSystemTime(out stm);     txtDateTime.Text=stm.wDay.ToString() + "/" +           stm.wMonth + "/" + stm.wYear; } 

Notice that I declare an instance of the MySystemTime managed structure and then I call the GetSystemTime method passing in the managed structure as an output parameter. After the method returns I can print out the contents of the managed structure


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