Windows API

Windows API

The foundation upon which every Windows application is built is the Windows API. Visual Basic applications are no exception. Visual Basic, through the language and forms package, abstracts the Windows API to a set of easy-to-use statements, components, and controls. In many cases, you can build a Visual Basic application without needing to call a Windows API function directly. However, because the Visual Basic language and forms package does not represent every Windows API function available, Visual Basic supports calling Windows API functions directly. It has done so since version 1, enabling you to add capabilities to your application that cannot be implemented in the Visual Basic language. Visual Basic .NET carries this capability forward by enabling you to declare and call Windows API functions in the same way as before.

This section focuses on the language changes that affect the way you create Declare statements for API functions. It also shows how you can use classes contained in the .NET Framework to complement or replace the Windows API calls you make in your application.

Type Changes

Two changes that affect almost every Windows API function declaration involve the numeric types Integer and Long. In Visual Basic 6, an Integer is 16 bits and a Long is 32 bits. In Visual Basic .NET, an Integer is 32 bits and a Long is 64 bits. Visual Basic .NET adds a new type called Short, which, in terms of size, is the replacement for the Visual Basic 6 Integer type.

In Visual Basic .NET, when you create a new Declare statement for a Windows API function, you need to be mindful of this difference. Any parameter type or user-defined type member that was formerly a Long needs to be declared as Integer; any member formerly declared as Integer needs to be declared as Short.

Take, for example, the following Visual Basic 6 Declare statement, which contains a mix of Integer and Long parameters:

Declare Function GlobalGetAtomName Lib "kernel32" _    Alias "GlobalGetAtomNameA" (ByVal nAtom As Integer, _    ByVal lpBuffer As String, ByVal nSize As Long) _    As Long

The equivalent Visual Basic .NET declaration is as follows:

Declare Function GlobalGetAtomName Lib "kernel32" _    Alias "GlobalGetAtomNameA" (ByVal nAtom As Short, _    ByVal lpBuffer As String, ByVal nSize As Integer) _    As Integer

The Upgrade Wizard will automatically change all the variable declarations in your code to use the right size. You need to worry about the size of a type only when you are creating new Declare statements or modifying existing statements.

As Any No Longer Supported

Because in some cases you need to pass either a string or a numeric value to a Windows API function, Visual Basic 6 allows you to declare parameter types As Any. This declaration allows you to pass an argument of any type, and Visual Basic will pass the correct information when the call is made. Although this gives you greater flexibility, no type checking is performed on the argument when you call the API function. If you pass an incompatible argument type, the application may crash.

Visual Basic .NET does not support declaring Windows API parameters As Any. Instead, it allows you to declare the same API function multiple times, using different types for the same parameter.

The Windows API function SendMessage is an example of an instance in which you would use As Any in the API function declaration. Depending on the Windows message you are sending, the parameter types required for the message will vary. The WM_SETTEXT message requires you to pass a string, whereas the WM_GETTEXTLENGTH message requires you to pass 0, a numeric value, for the last parameter. In order to handle both of these messages, you create the Visual Basic 6 Declare statement for SendMessage as follows:

Private Declare Function SendMessage Lib "user32" _ Alias "SendMessageA" _    (ByVal hwnd As Long, _     ByVal wMsg As Long, _     ByVal wParam As Long, _     ByVal lParam As Any) As Long

The last parameter has been declared As Any. The following code is an example of setting the caption of the current form and then retrieving the length of the caption set:

Dim TextLen As Long Const WM_SETTEXT = &HC Const WM_GETTEXTLENGTH = &HE SendMessage Me.hwnd, WM_SETTEXT, 0, "My text" TextLen = SendMessage(Me.hwnd, WM_GETTEXTLENGTH, 0, 0&)

To implement the equivalent functionality in Visual Basic .NET, you create multiple Declare statements for the SendMessage function. In the case of the SendMessage API, you create two Declare statements, using two different types for the last parameter String and Integer as follows:

Declare Function SendMessage Lib "user32" Alias "SendMessageA" _    (ByVal hwnd As Integer, _     ByVal wMsg As Integer, _     ByVal wParam As Integer, _     ByVal lParam As String) As Integer Declare Function SendMessage Lib "user32" Alias "SendMessageA" _    (ByVal hwnd As Integer, _     ByVal wMsg As Integer, _     ByVal wParam As Integer, _     ByVal lParam As Integer) As Integer

note

You also need to change the other parameter types from Long to Integer, as discussed in the previous section.

Although having to create multiple Declare statements for the same function is more work, you benefit by getting both the flexibility of using the same function name and type checking when the call is made.

AddressOf Changes

Certain API functions, such as EnumWindows, require a pointer to a callback function. When you call the API function, Windows calls the callback function you provided. In the case of EnumWindows, Windows will call the function for each top-level window contained on the screen.

Visual Basic 6 allows you to declare Windows API functions that take callback function pointers by declaring the function pointer parameter as Long, to represent a 32-bit pointer. You call the API function passing the subroutine to serve as the callback function, qualified by the AddressOf keyword.

Visual Basic .NET still supports the AddressOf keyword, but instead of returning a 32-bit integer, it returns a delegate. A delegate is a new type in Visual Basic .NET that allows you to declare pointers to functions or to class members. This means that you can still create Declare statements for Windows API functions that take a callback function pointer as a parameter. The difference is that instead of declaring the parameter type as a 32-bit integer, you need to declare the function parameter as a delegate type.

The following Visual Basic 6 code demonstrates how to use AddressOf to pass a pointer to a callback function:

Declare Function EnumWindows Lib "USER32" _    (ByVal EnumWindowsProc As Long, _     ByVal lParam As Long) As Boolean Declare Function GetWindowText Lib "USER32" Alias "GetWindowTextA" _    (ByVal hwnd As Long, ByVal TextBuffer As String, _     ByVal TextBufferLen As Long) As Long Dim TopLevelWindows As New Collection Sub Main()         Dim WindowCaption As Variant         ' Enumerate all top-level windows, EnumWindowsCallback will    ' be called    EnumWindows AddressOf EnumWindowsCallback, 0         ' Echo list of captions to Immediate window    For Each WindowCaption In TopLevelWindows       Debug.Print WindowCaption    Next      End Sub Function EnumWindowsCallback(ByVal hwnd As Long, _ ByVal lParam As Long) _ As Boolean      Dim TextBuffer As String    Dim ActualLen As Long    ' Get window caption        TextBuffer = Space(255)    ActualLen = GetWindowText(hwnd, TextBuffer, Len(TextBuffer))    TextBuffer = Left(TextBuffer, ActualLen)         ' Put window caption into list    If TextBuffer <> "" Then       TopLevelWindows.Add TextBuffer    End If         ' Return True to keep enumerating    EnumWindowsCallback = True      End Function

When you use the Visual Basic .NET Upgrade Wizard to upgrade the above code, you get the following result:

Declare Function EnumWindows Lib "USER32" _    (ByVal EnumWindowsProc As Integer, ByVal lParam As Integer) _       As Boolean Declare Function GetWindowText Lib "USER32" Alias "GetWindowTextA" _    (ByVal hwnd As Integer, ByVal TextBuffer As String, _     ByVal TextBufferLen As Integer) As Integer     Dim TopLevelWindows As New Collection     Public Sub Main()           Dim WindowCaption As Object           'UPGRADE_WARNING: Add a delegate for AddressOf EnumWindowsCallback    EnumWindows(AddressOf EnumWindowsCallback, 0)           For Each WindowCaption In TopLevelWindows       System.Diagnostics.Debug.WriteLine(WindowCaption)    Next WindowCaption        End Sub     Function EnumWindowsCallback(ByVal hwnd As Integer, _                              ByVal lParam As Integer) As Boolean           Dim TextBuffer As String    Dim ActualLen As Integer           TextBuffer = Space(255)    ActualLen = GetWindowText(hwnd, TextBuffer, Len(TextBuffer))    TextBuffer = Left(TextBuffer, ActualLen)           If TextBuffer <> "" Then       TopLevelWindows.Add(TextBuffer)    End If           ' Return True to keep enumerating    EnumWindowsCallback = True        End Function

The upgraded code contains a single compiler error: AddressOf expression cannot be converted to Integer because Integer is not a delegate type. The error occurs on the following statement:

EnumWindows(AddressOf EnumWindowsCallback, 0)

To fix the error, you need to change the Declare declaration for EnumWindows to accept a delegate type for the EnumWindowsProc parameter instead of an Integer type. The easiest way to create a delegate declaration for the EnumWindowsCallback function is to perform the following steps:

  1. Copy and paste the subroutine declaration in your code that is to serve as the callback function.

  2. Insert the Delegate keyword at the beginning of the declaration.

  3. Change the function name by appending the word Delegate to it. The delegate name must be unique.

In the previous example, we can create the delegate declaration by copying and pasting the following function signature to the section of code containing the Declare statements:

Function EnumWindowsCallback(ByVal hwnd As Integer, _                              ByVal lParam As Integer) As Boolean

Insert the keyword Delegate at the beginning of the function declaration given previously, and change the name by appending Delegate to the original function name EnumWindowsCallback becomes EnumWindowsCallback Delegate. You end up with a delegate declaration for a function with the form name and parameters of EnumWindowsCallback:

Delegate Function EnumWindowsCallbackDelegate(ByVal hwnd As Integer, _    ByVal lParam As Integer) _    As Boolean

Now that the delegate declaration for the callback function is in place, you need to change the parameter type for the EnumWindows Declare statement from Integer to the delegate type EnumWindowsCallbackDelegate, as follows:

Declare Function EnumWindows Lib "USER32" _    (ByVal EnumWindowsProc As EnumWindowsCallbackDelegate, _     ByVal lParam As Integer) As Boolean

The code will compile and run without error, producing the same results as the original Visual Basic 6 code.

Passing User-Defined Types to API Functions

When you pass a Visual Basic 6 user-defined type to an API function, Visual Basic passes a pointer to the memory containing the user-defined type; the API function sees the members of the user-defined type in the same order as they were declared in Visual Basic. This is not the case for Visual Basic .NET, however. If you declare a user-defined type, the order of the members is not guaranteed to be the order in which they appear in the code. The .NET runtime may reorganize the members of a user-defined type in a way that is most efficient for the user-defined type to be passed to a function. To guarantee that the members are passed exactly as declared in the code, you need to use marshalling attributes.

Marshaling Attributes

A marshaling attribute is an attribute that you attach to a structure member or subroutine parameter declaration to specify how the member or parameter should be stored in memory and passed to the function. For example, if you are calling an API function named MyFunction, written in C or C++, that takes a VARIANT_BOOL a 2-byte Boolean type parameter, you can use the MarshalAs attribute to specify the parameter type that the API function expects. Normally, a Boolean parameter is passed using 4 bytes, but if you include UnmanagedType.VariantBool as a parameter to the MarshalAs attribute, the parameter is marshaled in other words, passed as a 2-byte VARIANT_BOOL argument. The following code demonstrates how to apply the MarshalAs attribute to your API function declarations:

Declare Sub MyFunction Lib "MyLibrary.dll" _    (<MarshalAs(UnmanagedType.VariantBool)> ByVal MyBool As Boolean)

The MarshalAs attribute is contained in the System.Run time.InteropServices namespace. In order to call MarshalAs without qualification, you need to add an Imports statement at the top of the module for System.Runtime.InteropServices, as follows:

Imports System.Runtime.InteropServices

For structures passed to API functions, declare the structure using the StructLayout attribute to ensure compatibility with Visual Basic 6. The StructLayout attribute takes a number of parameters, but the two most significant for ensuring compatibility are LayoutKind and CharSet. To specify that the structure members should be passed in the order in which they are declared, set LayoutKind to LayoutKind.Sequential. To ensure that string parameters are marshaled as ANSI strings, set the CharSet member to CharSet.Ansi.

Suppose you are calling an API function named MyFunction in a DLL named MyLibrary, which takes as a parameter a structure called MyStructure. MyStructure contains a string and a fixed-length string type, declared in Visual Basic 6 as follows:

Type MyStructure    MyStringMember As String    MyFixedLenStringMember As String * 32 End Type

An approximately equivalent Visual Basic .NET declaration is

Structure MyStructure    Dim MyStringMember As String    Dim MyFixedLenStringMember As String End Structure

The above declaration is approximate because it is missing attributes to make it fully equivalent with the Visual Basic 6 declaration. It is missing attributes on the structure to specify layout and is also missing an attribute on MyFixedLenStringMember to mark it as a fixed-length string.

Because you cannot guarantee that the .NET runtime will pass the string members in the order declared for example, the .NET runtime may reorganize the structure in memory so that MyFixedLenStringMember comes first you need to add marshalling attributes to the structure as follows:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _ Structure MyStructure    Dim MyStringMember As String    Dim MyFixedLenStringMember As String End Structure

Visual Basic .NET does not natively support fixed-length strings. However, when you pass a structure containing a string to an API function, you can change the string member to a character array and include a MarshalAs attribute to tell the .NET runtime to pass a string of a fixed size. To do this, declare the structure member MyFixedLenStringMember as a Char array instead of a String. Include the UnmanagedType.ByValArray and SizeConst arguments to the MarshalAs attribute. If the structure is going to be used with any of the Visual Basic file functions for example, if the structure serves as a record in a random access file you need to add the VBFixedString attribute. You include or omit the MarshalAs and VBFixedString attributes depending on how the structure is used in your code. In this case, since we are passing the structure to a Windows API function, you need to add the MarshalAs attribute to the MyFixedLenStringMember structure member but omit the VBFixedString attribute, as follows:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _ Structure MyStructure    Dim MyStringMember As String    'Note: Change MyFixedLenStringMember to Char array.  You must    '      pad this member to 32 characters before calling the API     '      function.    MarshalAs(UnmanagedType.ByValArray, SizeConst:=32) _    Dim MyFixedLenStringMember() As Char End Structure

In addition to the ByValArray and SizeConst attributes, the code contains a comment to warn you that the ByValArray attribute requires you to pass a string padded out to exactly the size specified by SizeConst. This means that if you want to pass 3 characters you must append 29 spaces to the end of the string to create a buffer 32 characters in length. You can use the FixedLengthString class provided in the compatibility library to create strings that are automatically padded out to the desired size. For example, if you want to set the MyFixedLenStringMember to a 32-character fixed-length string containing the characters 123, here s how you would do it:

Dim ms As MyStructure ms.MyFixedLenStringMember = New VB6.FixedLengthString(32, "123")

With both the StructLayout and MarshalAs attributes in place, the structure will be passed in memory to an API function in the same way as the equivalent Visual Basic 6 Type End Type structure is passed.

note

Note The Upgrade Wizard incorrectly upgrades fixed-length strings contained in structures. Instead of declaring the string as a Char array, the wizard declares the upgraded fixed-length string as a String. The wizard applies the ByValTStr attribute instead of the ByValArray attribute. The upgraded code will compile and run. However, there is a subtle problem that you need to be aware of: the last character in the fixed-length string buffer is set to Null when passed to an API function. If, for example, you are passing a 5-character fixed-length string (as part of a structure to an API function) containing 12345 , only 1234 will be passed. The last character, 5 , is replaced with a Null character. To fix this problem, you need to change the member type from String to Char array and change the MarshalAs attribute from ByValTStr to ByValArray. Review your code to make sure you are using the FixedLengthString class to create strings that you assign to the member variable. Keep in mind that if the structure is not passed to an API function you can remove the MarshalAs attribute, and you don t need to review your code in this case.

ObjPtr and StrPtr Not Supported

Visual Basic 6 includes helper functions that return a 32-bit address for an object (ObjPtr) or a string (StrPtr). Visual Basic .NET does not support these functions. Moreover, it does not allow you to obtain the memory address for any type.

In cases where you must have control over underlying memory, the .NET Framework provides a pointer type known as System.IntPtr. The System.Run time.InteropServices.Marshal class contains a number of functions that you can use to obtain a pointer type for a given type. For example, the functions AllocHGlobal, GetComInterfaceForObject, and OffSetOf all return pointers to memory. You can also use the GCHandle structure to obtain a handle to an element contained in Garbage Collector managed memory. In addition, you can obtain a pointer to the memory by having the Garbage Collector pin the memory to a single memory location and not move it.

The following Visual Basic 6 example calls CopyMemory to copy a source string to a destination string. It uses StrPtr to obtain the memory addresses of the source and destination strings.

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _    (ByVal lpDest As Long, ByVal lpSource As Long, _     ByVal cbCopy As Long) Sub Main()    Dim sourceString As String, destString As String         sourceString = "Hello"    destString = "      World"         CopyMemory ByVal StrPtr(destString), _               ByVal StrPtr(sourceString), 10         MsgBox destString End Sub

Because there is no direct equivalent for StrPtr in Visual Basic .NET, you must use a different approach. To obtain a memory address to a string in Visual Basic .NET, you call GCHandle.Alloc to associate a handle with the string. Calling AddrOfPinnedObject on the handle allows you to obtain an IntPtr to the string buffer. Finally, you can obtain the memory address by calling IntPtr.ToInt32 to get the address value. It s more work than using StrPtr, but it achieves the same result. Here is the equivalent Visual Basic .NET code for the Visual Basic 6 code just given:

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _    (ByVal lpDest As Integer, ByVal lpSource As Integer, _     ByVal cbCopy As Integer)     Public Sub Main()    Dim sourceString As String, destString As String           sourceString = "Hello"    destString = "      World"    Dim sourceGCHandle As GCHandle = _       GCHandle.Alloc(sourceString, GCHandleType.Pinned)    Dim sourceAddress As Integer = _       sourceGCHandle.AddrOfPinnedObject.ToInt32    Dim destGCHandle As GCHandle = _       GCHandle.Alloc(destString, GCHandleType.Pinned)    Dim destAddress As Integer = _       destGCHandle.AddrOfPinnedObject.ToInt32    CopyMemory(destAddress, sourceAddress, 10)    sourceGCHandle.Free()    destGCHandle.Free()    MsgBox(destString) End Sub



Upgrading Microsoft Visual Basic 6.0to Microsoft Visual Basic  .NET
Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual Basic .NET w/accompanying CD-ROM
ISBN: 073561587X
EAN: 2147483647
Year: 2001
Pages: 179

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