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.
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.
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.
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
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
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
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.
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.
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.
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.
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.
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.
18.221.111.22