Consuming API Functions

The process of accessing and using unmanaged code inside the Win32 API is straightforward. First, you must identify the DLL function and the library containing that function. Common Win32 API libraries include the following:

  • GDI32.dll provides Graphics Device Interface (GDI) functions for drawing, printing, and font manipulation.

  • Kernel32.dll provides functions to access low-level OS features like memory management.

  • User32.dll provides functions for menus, timers, and Windows message handling.

Note

If you are unfamiliar with what the API has to offer, we suggest perusing the MSDN SDK documentation or taking a look at the title from which this book derives its name, Visual Basic Programmer's Guide to the Win32 API.


The next step in accessing the Win32 API from managed code is to declare the API function inside a class or module. We suggest you wrap your API functions with a class. This provides easier access from other managed code and is often easier to maintain. Finally, you call the function directly, passing it the necessary parameters.

The code in Listing A.1 illustrates three simple API calls from managed code. We first create a class called EventLog. Inside this class we declare the functions OpenEventLog, ClearEventLog, and CloseEventLog. We then simply call the API functions from inside the sub Main of a module.

Listing A.1. Event Log API Functions
Imports System.Runtime.InteropServices

Public Class EventLog

    'purpose: exposes event log api functions

    'opens the specified log (sourceName) for the specified
    '   server (serverName) and returns a handle to it
    Declare Auto Function OpenEventLog Lib "Advapi32.dll" _
        (ByVal serverName As String, ByVal sourceName As String) _
        As Integer
    'clears the specified event log  (hEventLog) and optionally
    '   backs it up to a file (backupFileName)
    Declare Auto Function ClearEventLog Lib "Advapi32.dll" _
        (ByVal hEventLog As Integer, ByVal backupFileName As String) _
        As Integer

    'closes the specified event log (hEventLog)
    Declare Auto Function CloseEventLog Lib "Advapi32.dll" _
        (ByVal hEventLog As Integer) As Integer

End Class

Module Module1

    Sub Main()

        'purpose: call event log api functions

        'local scope
        Dim handleLog As Integer

        'return a handle to the event log on the local box
        handleLog = EventLog.OpenEventLog( _
            serverName:=vbNullString, sourceName:="Application")

        'clear the event log
        EventLog.ClearEventLog( _
            hEventLog:=handleLog,
backupFileName:=vbNullString)

        'close the event log
        EventLog.CloseEventLog(hEventLog:=handleLog)

    End Sub

End Module

Note that these API functions have equivalents within the Framework Class Library inside of the System.Diagnostics namespace. Also notice that each function declared inside the class actually becomes a method of that class.

Of course, there are a number of additional settings available to you when using Platform Invoke. The rest of this section details those settings.

Declaring Unmanaged Functions

Visual Basic .NET developers have two options available when declaring a function. The first option uses the traditional declare syntax to which VB developers of old have become accustomed. The second option uses attribute classes to define a function as an unmanaged declaration to be passed to Platform Invoke. Most of the time, you can get away with the old style of declaration. However, many of the Platform Invoke settings that .NET provides are not available to this style of declaration.

The following code declares the Beep function inside a module using the typical declare syntax.

Module Module1

    'beep function (frequency and duration)
    Declare Unicode Function Beep Lib "Kernel32.dll" _
        (ByVal dwFreq As Integer, ByVal dwDuration As Integer)

    Sub Main()

        'call function
        Beep(300, 1000)

    End Sub

End Module

Platform Invoke has a set of default values that indicate how the service operates. These values are available for you to override and tweak to your needs. For declarations that require additional settings, use the DllImportAttribute class available to you through the System.Runtime.InteropServices namespace. This class allows you to mark a method as being handled by Platform Invoke and passed to an unmanaged API.

The following code declares the same function as the preceding but using the DllImportAttribute class.

Imports System.Runtime.InteropServices

Module Module1

    'beep function (frequency and duration)
    <DllImport("Kernel32.dll", CharSet:=CharSet.Unicode)> _
    Public Function Beep(ByVal dwFreq As Integer, _
        ByVal dwDuration As Integer)

        'passes to Kernel32 through Platform Invoke
    End Function

    Sub Main()

        'call function
        Beep(300, 1000)

    End Sub

End Module

The functionality of the two code blocks is identical. For the majority of function declarations it comes down to personal preference. Some will like the better readability and .NET-like code written with the attribute class while others will want to stick with the old VB-looking declarations.

Sometimes, however, you will need to access specific settings inside of Platform Invoke. Tweaking these settings can only be done via the attribute class. For this reason, we suggest always using the attribute class syntax.

EntryPoint

The EntryPoint object field inside of the DllImportAttribute class allows you to set the functions alias. This is equivalent to using the Alias keyword inside a standard Declare statement. An alias is simply an alternate name for your function outside of the Win32 API's name. Aliases are useful when your code needs to comply with a specific naming standard or you wish to declare multiple versions of the same DLL function with different names.

The following are examples of setting aliases. The first creates an alias using the Declare syntax and the Alias keyword for a Unicode version of the Beep function. Note that the alias is a string literal.

Declare Unicode Function BeepUnicode Lib "Kernel32.dll" Alias "Beep" _
    (ByVal dwFreq As Integer, ByVal dwDuration As Integer)

The second example uses the DllImportAttributeClass to create an ANSI version of our Beep function. Notice that we set the field EntryPoint this time, rather than using the Alias keyword.

<DllImport("Kernel32.dll", CharSet:=CharSet.Ansi, EntryPoint:="Beep")> _
Public Function BeepAnsi(ByVal dwFreq As Integer, _
    ByVal dwDuration As Integer)
    'passes to Kernel32 through Platform Invoke
End Function
							

CharSet

The CharSet enumeration inside of DllImportAttribute controls how the Platform Invoke service marshals strings and finds function names inside an unmanaged DLL.

As you saw with the beep function, many libraries export both a Unicode and an ANSI version of the same function. The ANSI version typically carries the letter A at its end (MessageBoxA). The Unicode usually carries the letter W as a suffix (MessageBoxW). CharSet allows you to specify which function you are trying to access. The enumeration has the following three values: ANSI, Unicode, and Auto. The first two are self-explanatory while the last simply passes the job of determining to the Platform Invoke service. If you do not specify CharSet inside of VB, the default is ANSI.

Note

ANSI is a 1-byte character set that is typically used for operating systems running Windows 95/98. Unicode applies to Windows NT/2000/XP and uses a 2-byte character set.


Like EntryPoint, CharSet allows you to set its value either using a keyword or by using the enumeration. The following are examples of setting CharSet. The first uses the keyword Auto and the Declare syntax.

Declare Auto Function Beep Lib "Kernel32.dll" _
    (ByVal dwFreq As Integer, ByVal dwDuration As Integer)

The second example uses the DllImportAttributeClass and the CharSet enumeration to create a Unicode version of our Beep function.

<DllImport("Kernel32.dll", CharSet:=CharSet.Unicode)> _
Public Function Beep(ByVal dwFreq As Integer, _
    ByVal dwDuration As Integer)
    'passes to Kernel32 through Platform Invoke
End Function

ExactSpelling

The ExactSpelling field of DllImportAttribute works directly with the CharSet enumeration to manage how the compiler and Platform Invoke interprets your request for a given function name. The following are possible combinations and their descriptions:

  • CharSet.Ansi and ExactSpelling = True— Platform Invoke only searches for the exact name you specify.

  • CharSet.Unicode and ExactSpelling = True— Platform Invoke only searches for the exact name you specify.

  • CharSet.Ansi and ExactSpelling = False— Platform Invoke searches for the unmangled name first (MessageBox) and ANSI-mangled name second (MessageBoxA).

  • CharSet.Unicode and ExactSpelling = False— Platform Invoke searches for the unmangled name first (MessageBox) and Unicode-mangled name second (MessageBoxW).

Note that ExactSpelling is set to True by default when using VB .NET. Also, note that you cannot override this value to False unless you use the attribute-like syntax. The following is an example where we force ourselves to call the version of the function tagged with the letter “A” as in MessageBoxA.

<DllImport("user32.dll", ExactSpelling:=True, charSet:=CharSet.Ansi)> _
Public Function MessageBoxA(ByVal hWnd As Integer, _
    ByVal messageText As String, ByVal dialogCaption As String, _
    ByVal type As Integer) As Integer

    'passes to user32.dll through Platform Invoke
End Function
							

CallingConventions

The CallingConventions enumeration inside of the DllImportAttribute class allows you to specify the conventions used when calling the function. The default value of this enumeration is WinAPI. As calling the Win32 API from managed code is the focus of this chapter, we will leave it to the readers to peruse the other values of this enumeration should they require their use. Setting this enumeration is only possible using the attribute-like syntax for function declarations.

PreserveSig

The PreserveSig object field is used to indicate whether the HRESULT or Retval should be suppressed. The default value for PreserveSig is True; that is, the signature transformations are preserved. Setting this value to False indicates that you wish the HRESULT to be transformed into the Retval and thus discarded. This field is only accessible via the DllImportAttribute class and the attribute-like syntax for function declarations.

SetLastError

The SetLastError object field is used to indicate that the API should allow you to call the GetLastError API call after a given API call. This was common practice for VB developers. The GetLastError API allowed you to determine whether an error occurred while executing a given API call. For this reason, inside of VB, the default value of the field is True. This field is only available to those using the attribute-like syntax for function declarations.

Passing Structures and Classes

A number of unmanaged DLL functions require you to pass structures of data. Structures are similar to VB's UDTs of old. When passing these structures/classes it is usually necessary to define additional information about the sequence of the members of a given structure. To do so, you use the StructLayoutAttribute class. This class is of the type LayoutKind enumeration. This enumeration has the following three values:

  • Auto indicates that the runtime can reorder the members in the structure. This option is not meant for calling unmanaged DLL functions and should not be used.

  • Explicit indicates that the members should be ordered explicitly as set using the FieldOffset attribute applied to each item in the structure. The FieldOffset attribute indicates a physical position.

  • Sequential orders the items of the given structure as they appear in the type definition.

Listing A.2 provides an example of passing a data structure, represented by the class SystemTime, to a DLL function. We first set the system time by calling SetSystemTime and passing it an instance of the SystemTime class. Notice that SystemTime is tagged as a StructLayout with a LayoutKind of sequential. We then retrieve the system time by passing another instance of SystemTime to the GetSystemTime function. Notice that we must make this pass ByRef.

Listing A.2. Passing a Class
Imports System.Runtime.InteropServices

'declare a structured class with properties
<StructLayout(LayoutKind.Sequential)> _
Public Structure SystemTime
  Public wYear As Short
  Public wMonth As Short
  Public wDayOfWeek As Short
  Public wDay As Short
  Public wHour As Short
  Public wMinute As Short
  Public wSecond As Short
  Public wMiliseconds As Short
End Structure

Public Class SysTime

  'declare the set system time function
  Declare Auto Sub SetSystemTime Lib "Kernel32.dll" _
     (ByRef sysTime As SystemTime)
  'declare the get system time function
  Declare Auto Sub GetSystemTime Lib "Kernel32.dll" _
      (ByRef sysTime As SystemTime)

End Class

Public Class SetGetTime

  Public Shared Sub Main()

    'local scope
    Dim sTimeSet As New SystemTime()
    Dim sTimeGet As New SystemTime()

    'set the properties of our set time instance
    sTimeSet.wYear = 2001
    sTimeSet.wMonth = 8
    sTimeSet.wDayOfWeek = 7
    sTimeSet.wDay = 26
    sTimeSet.wHour = 10
    sTimeSet.wMinute = 30
    sTimeSet.wSecond = 0
    sTimeSet.wMiliseconds = 0

    'call set time
    SysTime.SetSystemTime(sysTime:=sTimeSet)

    'call get time
    SysTime.GetSystemTime(sysTime:=sTimeGet)

    'write time to screen
    Console.WriteLine(sTimeGet.wHour & ":" & sTimeGet.wMinute)

  End Sub

End Class

Callback Functions

Creating callback functions is a typical task of the Win32 API developer. Callback functions work directly with the unmanaged code to execute a given task. For example, suppose that you needed to execute the same task on a group of objects. The API call would find the first object and return it to your callback function. Your callback function would execute another task and the API would then find the next object. This is illustrated by Figure A.2.

Figure A.2. Callback functions.


As you can see, callbacks are implemented by passing the DLL function a pointer to the callback function. You must first determine if your DLL function requires callbacks prior to your using them. To do so, you can often check the argument of a given parameter. An argument whose prefix is lp and whose suffix is Func usually indicates a callback. The prefix stands for Long Pointer and the suffix, Function.

After you've identified a need for a callback function, you implement it via delegates. Platform Invoke converts a given delegate to a familiar callback format for you.

Listing A.3 represents a DLL function that makes a callback to a managed function. We initially create a delegate (CallBack) that has a Windows handle as its first parameter. Note that EnumWindowsProc defines the proper format for our delegate used with the DLL function EnumWindows. Next, we declare the EnumWindows function and set its first parameter to a type of our delegate, CallBack. We then define the class that will actually receive the delegate calls, ReceiveCallBack. This function takes the Windows handle and writes it to the console. Finally, create a simple Main in our module to call the EnumWindows API. We then pass the AddressOf ReceiveCallBack to it. This is standard for delegates in VB. Note that the ReceiveCallBack function automatically returns True. This indicates to the API function that we wish to continue. Returning False would halt the callbacks.

The result of this code is a list of all the top-level Windows handles displayed inside the console.

Listing A.3. Callback
Imports System.Runtime.InteropServices

'create a delegate for callback
Public Delegate Function CallBack( _
    ByVal hwnd As Integer, _
    ByVal lParam As Integer) As Boolean

Module Module1

    'declare the dll function that enumerates all windows
    '   and returns them to the callback
    Declare Function EnumWindows Lib "user32" ( _
        ByVal lpEnumFunc As CallBack, _
        ByVal lParam As Integer) As Integer

    Public Function ReceiveCallBack(ByVal hwnd As Integer, _
        ByVal lParam As Integer) As Boolean

        'write the windows handle out
        Console.WriteLine(hwnd)

        'indicate to API that we wish to continue
        Return True

    End Function

    Sub Main()

        'call the api and pass the pointer
        EnumWindows(AddressOf ReceiveCallBack, 0)

        'pause
        Do While Not Console.ReadLine = "s" : Loop

    End Sub

End Module

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

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