Platform Invocation Services (PInvoke)

Team-Fly    

 
Application Development Using Visual Basic and .NET
By Robert J. Oberg, Peter Thorsteinson, Dana L. Wyatt
Table of Contents
Chapter 17.  Interoperability


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 looks after 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; however, the converse is much more problematic . Currently, 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. You should resist this temptation , as calling unmanaged code defeats much of the purpose of .NET. Usually, there will be a native .NET Framework class method that can accomplish your aim, and you should endeavor to use .NET Framework classes wherever possible. Nonetheless, there are occasions when it is 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 SimplePInvoke example.

 graphics/codeexample.gif ' SimplePInvoke.vb Imports System Imports System.Runtime.InteropServices Module SimplePInvoke  <DllImport("user32.dll")>  _     Public Function  MessageBox  (_     ByVal hWnd As Integer, _     ByVal text As String, _     ByVal caption As String, _     ByVal type As Integer) As Integer     End Function     Public Sub Main()  MessageBox  (0, "Hello, World", "From PInvoke", 0)     End Sub End Module 

As an alternative to using the DllImport attribute syntax, you can use the traditional Declare keyword syntax to declare an external DLL function.

  Declare  Auto Function MessageBox Lib "user32.dll" (_ ByVal hWnd As Integer, _ ByVal txt As String, _ ByVal caption As String, _ ByVal Typ As Integer) As Integer 

The key step is to provide a DllImport attribute (or Declare statement) for the prototype of the function that we want to call. The function must take ordinary VB.NET data types as parameters, which match the C data types of the native function. The function will be treated as a shared 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 named parameters that can be used with the DllImport attribute. For a complete list, consult the documentation of the DllImportAttribute class in the System.Runtime.InteropServices namespace. Figure 17-12 shows the output from this little program.

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

graphics/17fig12.jpg

Marshaling ByRef Parameters

The previous PInvoke example did not demonstrate how PInvoke automatically marshals ByRef parameters for you. This is because the MessageBox takes only ingoing parameters, and ByValue was sufficient to get the parameter into the method call. The next example calls the GetComputerName and GetLastError APIs via PInvoke. The code for this example is in the PInvoke example.

 graphics/codeexample.gif ' PInvoke.vb Imports System  Imports System.Text   Imports System.Runtime.InteropServices  Public Module Test  <DllImport("kernel32.dll")> _  Public Function  GetComputerName  (_     ByVal name As StringBuilder, _  ByRef  buffer As Integer) As Boolean     End Function  <DllImport("kernel32.dll")> _  Public Function  GetLastError  () As Integer     End Function     Public Sub Main()         Dim result As Boolean = True         Dim error_code As Integer = 0  Dim name As StringBuilder = New StringBuilder(128)  Dim length As Integer = 128         result =  GetComputerName  (name, length)         If result Then             Console.WriteLine(name)         Else             error_code =  GetLastError  ()             Console.WriteLine("Error: {0:x}", error_code)         End If         Return     End Sub End Module 
Translating Types

Since GetComputerName returns a name in its first parameter, StringBuilder was used instead of String . For input-only arguments, you can use String , but outgoing parameters must not be String , since String is immutable. The ByRef keyword was placed on the length attribute because the second argument to GetComputerName is an outgoing primitive data type parameter. The Integer type is used because DWORD is a 32-bit integer quantity. For comparison, here are the prototypes of the corresponding Win32 functions:

 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. For example, 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 Function Foo() As String 

The UnmanagedType enumeration lists all the translatable types.


Team-Fly    
Top
 


Application Development Using Visual BasicR and .NET
Application Development Using Visual BasicR and .NET
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 190

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