AllApi.net

[an error occurred while processing this directive]
 
Accessing DLLs and the Windows API :

When you need capabilities that go beyond the core language and controls provided with Visual Basic, you can make direct calls to procedures contained in dynamic-link libraries (DLLs). By calling procedures in DLLs, you can access the thousands of procedures that form the backbone of the Microsoft Windows operating system, as well as routines written in other languages.

As their name suggests, DLLs are libraries of procedures that applications can link to and use at run time rather than link to statically at compile time. This means that the libraries can be updated independently of the application, and many applications can share a single DLL. Microsoft Windows itself is comprised of DLLs, and other applications call the procedures within these libraries to display windows and graphics, manage memory, or perform other tasks. These procedures are sometimes referred to as the Windows API, or application programming interface.
Note If you are using the Control Creation Edition of Visual Basic, some of the material covered in this document may not be applicable. With the full editions of Visual Basic you have the ability to create applications, ActiveX documents, and other types of components. Although some of the terminology may relate to application specific objects such as forms, in most cases the underlying concepts also apply to ActiveX control creation.

DLLs or Automation?

Another way to bring more power into Visual Basic is through Automation (formerly called OLE Automation). Using Automation is simpler than calling routines in a DLL, and it doesn't create the same level of risk that you'll hit when going straight to the Windows API. By using Automation, you can get programmatic access to a wide range of objects exposed by external applications.

Contents
* Using a DLL Procedure in Your Application
* Accessing the Microsoft Windows API
* Declaring a DLL Procedure
* Passing Strings to a DLL Procedure
* Passing Arrays to a DLL Procedure
* Passing User-Defined Types to a DLL Procedure
* Passing Function Pointers to DLL Procedures and Type Libraries
* Passing Other Types of Information to a DLL Procedure
* Converting C Declarations to Visual Basic

Using a DLL Procedure in Your Application

Because DLL procedures reside in files that are external to your Visual Basic application, you must specify where the procedures are located and identify the arguments with which they should be called. You provide this information with the Declare statement. Once you have declared a DLL procedure, you can use it in your code just like a native Visual Basic procedure.
Important When you call any DLLs directly from Visual Basic, you lose the built-in safety features of the Visual Basic environment. This means that you increase the risk of system failure while testing or debugging your code. To minimize the risk, you need to pay close attention to how you declare DLL procedures, pass arguments, and specify types. In all cases, save your work frequently. Calling DLLs offers you exceptional power, but it can be less forgiving than other types of programming tasks.


In the following example, we'll show how to call a procedure from the Windows API. The function we'll call, SetWindowText, changes the caption on a form. While in practice, you would always change a caption by using Visual Basic's Caption property, this example offers a simple model of declaring and calling a procedure.
Declaring a DLL Procedure

The first step is to declare the procedure in the Declarations section of a module:
Private Declare Function SetWindowText Lib "user32" _
Alias "SetWindowTextA" (ByVal hwnd As Long, _
ByVal lpString As String) As Long

You can find the exact syntax for a procedure by using the API Viewer application, or by searching the Win32api.txt file. If you place the Declare in a Form or Class module, you must precede it with the Private keyword. You declare a DLL procedure only once per project; you can then call it any number of times.
For More Information For more information on declare statements, see the topic "Declaring a DLL Procedure" later in this chapter.
Calling a DLL Procedure
After the function is declared, you call it just as you would a standard Visual Basic function. Here, the procedure has been attached to the Form Load event:
Private Sub Form_Load()
SetWindowText Form1.hWnd, "Welcome to VB"
End Sub


When this code is run, the function first uses the hWnd property to identify the window where you want to change the caption (Form1.hWnd), then changes the text of that caption to "Welcome to VB."

Remember that Visual Basic can't verify that you are passing correct values to a DLL procedure. If you pass incorrect values, the procedure may fail, which may cause your Visual Basic application to stop. You'll then have to reload and restart your application. Take care when experimenting with DLL procedures and save your work often.
Note Very few API calls recognize the default Variant data type. Your API calls will be much more robust if you declare variables of specific types and use Option Explicit.

Accessing the Microsoft Windows API

You can gain access to the Windows API (or other outside DLLs) by declaring the external procedures within your Visual Basic application. After you declare a procedure, you can use it like any other language feature in the product.

The most commonly used set of external procedures are those that make up Microsoft Windows itself. The Windows API contains thousands of functions, subs, types, and constants that you can declare and use in your projects. These procedures are written in the C language, however, so they must be declared before you can use them with Visual Basic. The declarations for DLL procedures can become fairly complex. While you can translate these yourself, the easiest way to access the Windows API is by using the predefined declares included with Visual Basic.

The file Win32api.txt, located in the \Winapi subdirectory of the main Visual Basic directory, contains declarations for many of the Windows API procedures commonly used in Visual Basic. To use a function, type, or other feature from this file, simply copy it to your Visual Basic module. You can view and copy procedures from Win32api.txt by using the API Viewer application, or by loading the file in any text editor.
Note The Windows API contains a vast amount of code. To find reference information on the procedures and other details included in this API set, refer to the Win32 SDK, included in the \Tools directory of your Visual Basic CD.

Using the API Viewer Application

The API Viewer application enables you to browse through the declares, constants, and types included in any text file or Microsoft Jet database. After you find the procedure you want, you can copy the code to the Clipboard and paste it into your Visual Basic application.
Figure 1.1 The API Viewer application



To load the Win32api.txt file, choose Load Text File from the File menu. After the file is loaded, you can view entries in the Available Items list box by selecting Declares, Constants, or Types in the API Type drop-down list box. To search for specific items in the file, use the Search button.

To load a Jet database API file, choose Load Database File from the File menu. You can then search for specific items in the database by typing the first letter of the item you want to find.
Adding Procedures to Your Visual Basic Code

Once you have found a procedure you want, choose the Add button to add the item to the Selected Items box. You can add as many items as you like. To remove an entry from the Selected Items box, select the item, then choose the Remove button.

To copy the items from the Selected Items list box to the Clipboard, choose the Copy button. All of the items in the list will be copied. You can then open your Visual Basic project and go to the module in which you want to place the API information. Position the insertion point where you want to paste the declarations, constants, and/or types, and then choose Paste from the Edit menu.
Converting Text Files to Jet Database Files

To optimize speed, you can convert the Win32api.txt file into a Jet database file, because it is much faster to display the list when opening a database than when opening a text file.

To convert the text file, start the API Viewer application, then choose the Load Text File command from the File menu and open the .txt file. A message will appear asking if you want to convert the .txt file to a database file. Choose Yes to confirm the conversion. If you choose No, you can still convert the file later by choosing the Convert Text to Database command from the File menu.
Loading an API File Automatically from the Command Line

You can specify a text or database file on the command line for Apilod32.exe so that the file is automatically loaded when you start API Viewer. Use the following syntax to load the file you choose when you start the API Viewer application:
Apilod32.exe {/T|/D} filename

Argument
Description

/T
API Viewer will load the file as a text file. /T must be uppercase.
/D
API Viewer will load the file as a database file. /D must be uppercase.
filename
The path of the file you want to open.


There must be a space between /T or /D and the filename argument. An error message will be displayed if the file is not found. If you specify a file that is not a database or text file, an error message will be displayed when you try to load the file.

For example, you might enter the following command line for API Viewer in the Windows Program Item Properties dialog box (by choosing Properties from the File menu):
C:\VB\Winapi\Apilod32.exe /D C:\VB\Winapi\Win32api.mdb

Viewing the Win32api.txt file with a Text Editor

You can also load the Win32api.txt file in a text editor, such as Microsoft Word or WordPad, to locate the procedures you want to use. Again, you just copy the procedures from the file to a Visual Basic module to use them in your application.
Tip Don't load the Win32api.txt file into a module. This is a large file, and it will consume a lot of memory in your application. You will generally use only a handful of declarations in your code, so selectively copying the declarations you need is much more efficient.

Using Procedures from Other Sources

If you are attempting to call a procedure in a DLL that is not part of the operating system, you must determine the proper declaration for it. The topic "Declaring a DLL Procedure" explains the syntax of the Declare statement in detail.
Declaring a DLL Procedure

Even though Visual Basic provides a broad set of predefined declares in the Win32api.txt file, sooner or later you'll want to know how to write them yourself. You might want to access procedures from DLLs written in other languages, for example, or rewrite Visual Basic's predefined declares to fit your own requirements.

To declare a DLL procedure, you add a Declare statement to the Declarations section of the code window. If the procedure returns a value, write the declare as a Function:

Declare Function publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])] As Type


If a procedure does not return a value, write the declare as a Sub:

Declare Sub publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])]


DLL procedures declared in standard modules are public by default and can be called from anywhere in your application. DLL procedures declared in any other type of module are private to that module, and you must identify them as such by preceding the declaration with the Private keyword.

Procedure names are case-sensitive in 32-bit versions of Visual Basic. In previous, 16-bit versions, procedure names were not case-sensitive.

Specifying the Library

The Lib clause in the Declare statement tells Visual Basic where to find the .dll file that contains the procedure. When you're referencing one of the core Windows libraries (User32, Kernel32, or GDI32), you don't need to include the file name extension:
Declare Function GetTickCount Lib "kernel32" Alias _
"GetTickCount" () As Long


For other DLLs, the Lib clause is a file specification that can include a path:
Declare Function lzCopy Lib "c:\windows\lzexpand.dll" _
(ByVal S As Integer, ByVal D As Integer) As Long


If you do not specify a path for libname, Visual Basic will search for the file in the following order:
1. Directory containing the .exe file
2. Current directory
3. Windows 32-bit system directory (often but not necessarily \Windows\System32)
4. Windows 16-bit system directory (often but not necessarily \Windows\System)
5. Windows directory (not necessarily \Windows)
6. Path environment variable


The following table lists the common operating environment library files.
Dynamic Link Library
Description

Advapi32.dll Advanced API services library supporting numerous APIs including many security and Registry calls
Comdlg32.dll Common dialog API library
Gdi32.dll Graphics Device Interface API library
Kernel32.dll Core Windows 32-bit base API support
Lz32.dll 32-bit compression routines
Mpr.dll Multiple Provider Router library
Netapi32.dll 32-bit Network API library
Shell32.dll 32-bit Shell API library
User32.dll Library for user interface routines
Version.dll Version library
Winmm.dll Windows multimedia library
Winspool.drv
Print spooler interface that contains the print spooler API calls

Working with Windows API Procedures that Use Strings

When working with Windows API procedures that use strings, you'll need to add an Alias clause to your declare statements to specify the correct character set. Windows API functions that contain strings actually exist in two formats: ANSI and Unicode. In the Windows header files, therefore, you'll get both ANSI and Unicode versions of each function that contains a string.

For example, following are the two C-language descriptions for the SetWindowText function. You'll note that the first description defines the function as SetWindowTextA, where the trailing "A" identifies it as an ANSI function:
WINUSERAPI
BOOL
WINAPI
SetWindowTextA(
HWND hWnd,
LPCSTR lpString);


The second description defines it as SetWindowTextW, where the trailing "W" identifies it as a wide, or Unicode function:
WINUSERAPI
BOOL
WINAPI
SetWindowTextW(
HWND hWnd,
LPCWSTR lpString);


Because neither function is actually named "SetWindowText," you need to add an Alias clause to the declare to point to the function you want to reference:
Private Declare Function SetWindowText Lib "user32" _
Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal _
lpString As String) As Long


Note that the string that follows the Alias clause must be the true, case-sensitive name of the procedure.
Important For API functions you use in Visual Basic, you should specify the ANSI version of a function, because Unicode versions are only supported by Windows NT - not Windows 95. Use the Unicode versions only if you can be certain that your applications will be run only on Windows NT-based systems.

Passing Arguments by Value or by Reference

By default, Visual Basic passes all arguments by reference. This means that instead of passing the actual value of the argument, Visual Basic passes a 32-bit address where the value is stored. Although you do not need to include the ByRef keyword in your Declare statements, you may want to do so to document how the data is passed.

Many DLL procedures expect an argument to be passed by value. This means they expect the actual value, instead of its memory location. If you pass an argument by reference to a procedure that expects an argument passed by value, the procedure receives incorrect data and fails to work properly.

To pass an argument by value, place the ByVal keyword in front of the argument declaration in the Declare statement. For example, the InvertRect procedure accepts its first argument by value and its second by reference:
Declare Function InvertRect Lib "user32" Alias _
"InvertRectA" (ByVal hdc As Long, _
lpRect As RECT) As Long


You can also use the ByVal keyword when you call the procedure.
Note When you're looking at DLL procedure documentation that uses C language syntax, remember that C passes all arguments except arrays by value.


String arguments are a special case. Passing a string by value means you are passing the address of the first data byte in the string; passing a string by reference means you are passing the memory address where another address is stored; the second address actually refers to the first data byte of the string. How you determine which approach to use is explained in the topic "Passing Strings to a DLL Procedure" later in this chapter.
Nonstandard Names

Occasionally, a DLL procedure has a name that is not a legal identifier. It might have an invalid character (such as a hyphen), or the name might be the same as a Visual Basic keyword (such as GetObject). When this is the case, use the Alias keyword to specify the illegal procedure name.

For example, some procedures in the operating environment DLLs begin with an underscore character. While you can use an underscore in a Visual Basic identifier, you cannot begin an identifier with an underscore. To use one of these procedures, you first declare the function with a legal name, then use the Alias clause to reference the procedure's real name:
Declare Function lopen Lib "kernel32" Alias "_lopen" _
(ByVal lpPathName As String, ByVal iReadWrite _
As Long) As Long


In this example, lopen becomes the name of the procedure referred to in your Visual Basic procedures. The name _lopen is the name recognized in the DLL.

You can also use the Alias clause to change a procedure name whenever it's convenient. If you do substitute your own names for procedures (such as using WinDir for GetWindowsDirectoryA), make sure that you thoroughly document the changes so that your code can be maintained at a later date.
Using Ordinal Numbers to Identify DLL Procedures

In addition to a name, all DLL procedures can be identified by an ordinal number that specifies the procedure in the DLL. Some DLLs do not include the names of their procedures and require you to use ordinal numbers when declaring the procedures they contain. Using an ordinal number consumes less memory in your finished application and is slightly faster than identifying a procedure in a DLL by name.
Important The ordinal number for a specific API will be different with different operating systems. For example, the ordinal value for GetWindowsDirectory is 432 under Win95, but changes to 338 under Window NT 4.0. In sum, if you expect your applications to be run under different operating systems, don't use ordinal numbers to identify API procedures. This approach can still be useful when used with procedures that are not APIs, or when used in applications that have a very controlled distribution.


To declare a DLL procedure by ordinal number, use the Alias clause with a string containing the number sign character (#) and the ordinal number of the procedure. For example, the ordinal number of the GetWindowsDirectory function has the value 432 in the Windows kernel; you can declare the DLL procedure as follows:
Declare Function GetWindowsDirectory Lib "kernel32" _
Alias "#432" (ByVal lpBuffer As String, _
ByVal nSize As Long) As Long


Notice that you could specify any valid name for the procedure in this case, because Visual Basic is using the ordinal number to find the procedure in the DLL.

To obtain the ordinal number of a procedure you want to declare, you can use a utility application, such as Dumpbin.exe, to examine the .dll file. (Dumpbin.exe is a utility included with Microsoft Visual C++.) By running Dumpbin on a .dll file, you can extract information such as a list of functions contained within the DLL, their ordinal numbers, and other information about the code.

For More Information For more information on running the Dumpbin utility, refer to the Microsoft Visual C++ documentation.
Flexible Argument Types

Some DLL procedures can accept more than one type of data for the same argument. If you need to pass more than one type of data, declare the argument with As Any to remove type restrictions.

For example, the third argument in the following declare (lppt As Any) could be passed as an array of POINT structures, or as a RECT structure, depending upon your needs:
Declare Function MapWindowPoints Lib "user32" Alias _
"MapWindowPoints" (ByVal hwndFrom As Long, _
ByVal hwndTo As Long, lppt As Any, _
ByVal cPoints As Long) As Long


While the As Any clause offers you flexibility, it also adds risk in that it turns off all type checking. Without type checking, you stand a greater chance of calling the procedure with the wrong type, which can result in a variety of problems, including application failure. Be sure to carefully check the types of all arguments when using As Any.

When you remove type restrictions, Visual Basic assumes the argument is passed by reference. Include ByVal in the actual call to the procedure to pass arguments by value. Strings are passed by value so that a pointer to the string is passed, rather than a pointer to a pointer. This is further discussed in the section "Passing Strings to a DLL Procedure."
Passing Strings to a DLL Procedure

In general, strings should be passed using ByVal, unless you are passing them to an OLE 2.0 API or a Visual Basic procedure. Visual Basic uses a String data type known as a BSTR, which is a data type defined by Automation (formerly called OLE Automation). A BSTR is comprised of a header, which includes information about the length of the string, and the string itself, which may include embedded nulls. A BSTR is passed as a pointer, so the DLL procedure is able to modify the string. (A pointer is a variable that contains the memory location of another variable, rather than the actual data.) Most BSTRs are Unicode, which means that each character takes two bytes. BSTRs typically end with a two-byte two null character.
Figure 1.2 The BSTR type



The procedures in most DLLs (and in all procedures in the Windows API) recognize LPSTR types, which are pointers to standard null-terminated C strings (also called ASCIIZ strings). LPSTRs have no prefix. The following figure shows an LPSTR that points to an ASCIIZ string.
Figure 1.3 The LPSTR type




If a DLL procedure expects an LPSTR (a pointer to a null-terminated string) as an argument, pass the BSTR by value. Because a pointer to a BSTR is a pointer to the first data byte of a null-terminated string, it looks like an LPSTR to the DLL procedure.

For example, the sndPlaySound function accepts a string that names a digitized sound (.wav) file and plays that file.
Private Declare Function sndPlaySound Lib "winmm.dll" _
Alias "sndPlaySoundA" (ByVal lpszSoundName As String, _
ByVal uFlags As Long) As Long


Because the string argument for this procedure is declared with ByVal, Visual Basic passes a BSTR that points to the first data byte:
Dim SoundFile As String, ReturnLength As Long
SoundFile = Dir("c:\Windows\System\" & "*.wav")
Result = sndPlaySound(SoundFile, 1)


In general, use the ByVal keyword when passing string arguments to DLL procedures that expect LPSTR strings. If the DLL expects a pointer to an LPSTR string, pass the Visual Basic string by reference.

When passing binary data to a DLL procedure, pass a variable as an array of the Byte data type, instead of a String variable. Strings are assumed to contain characters, and binary data may not be properly read in external procedures if passed as a String variable.

If you declare a string variable without initializing it, and then pass it by value to a DLL, the string variable is passed as NULL, not as an empty string (""). To avoid confusion in your code, use the vbNullString constant to pass a NULL to an LPSTR argument.
Passing Strings to DLLs that Use Automation

Some DLLs may be written specifically to work with Automation data types like BSTR, using procedures supplied by Automation.

Because Visual Basic uses Automation data types as its own data types, Visual Basic arguments can be passed by reference to any DLL that expects Automation data types. Thus, if a DLL procedure expects a Visual Basic string as an argument, you do not need to declare the argument with the ByVal keyword, unless the procedure specifically needs the string passed by value.

Some DLL procedures may return strings to the calling procedure. A DLL function cannot return strings unless it is written specifically for use with Automation data types. If it is, the DLL probably supplies a type library that describes the procedures. Consult the documentation for that DLL.

For More Information For information on Automation data types, see the OLE 2 Programmer's Reference, published by Microsoft Press.
Procedures That Modify String Arguments

A DLL procedure can modify data in a string variable that it receives as an argument. However, if the changed data is longer than the original string, the procedure writes beyond the end of the string, probably corrupting other data.

You can avoid this problem by making the string argument long enough so that the DLL procedure can never write past the end of it. For example, the GetWindowsDirectory procedure returns the path for the Windows directory in its first argument:
Declare Function GetWindowsDirectory Lib "kernel32" _
Alias "GetWindowsDirectoryA" (ByVal lpBuffer As _
String, ByVal nSize As Long) As Long


A safe way to call this procedure is to first use the String function to set the returned argument to at least 255 characters by filling it with null (binary zero) characters:
Path = String(255, vbNullChar)
ReturnLength = GetWindowsDirectory(Path, Len(Path))
Path = Left(Path, ReturnLength)


Another solution is to define the string as fixed length:
Dim Path As String * 255
ReturnLength = GetWindowsDirectory(Path, Len(Path))


Both of these processes have the same result: They create a fixed-length string that can contain the longest possible string the procedure might return.
Note Windows API DLL procedures generally do not expect string buffers longer than 255 characters. While this is true for many other libraries, always consult the documentation for the procedure.


When the DLL procedure calls for a memory buffer, you can either use the appropriate data type, or use an array of the byte data type.
Passing Arrays to a DLL Procedure

You can pass individual elements of an array the same way you pass a variable of the same type. When you pass an individual element, it will be passed as the base type of the array. For example, you can use the sndPlaySound procedure to play a series of .wav files stored in an array:
Dim WaveFiles(10) As String
Dim i As Integer, worked As Integer
For i = 0 to UBound(WaveFiles)
worked = sndPlaySound(WaveFiles(i), 0)
Next i


Sometimes you may want to pass an entire array to a DLL procedure. If the DLL procedure was written especially for Automation, then you may be able to pass an array to the procedure the same way you pass an array to a Visual Basic procedure: with empty parentheses. Because Visual Basic uses Automation data types, including SAFEARRAYs, the DLL must be written to accommodate Automation for it to accept Visual Basic array arguments. For further information, consult the documentation for the specific DLL.

If the DLL procedure doesn't accept Automation SAFEARRAYs directly, you can still pass an entire array if it is a numeric array. You pass an entire numeric array by passing the first element of the array by reference. This works because numeric array data is always laid out sequentially in memory. If you pass the first element of an array to a DLL procedure, that DLL then has access to all of the array's elements.

As an example, consider how you can use an API call to set tab stops within a text box There are internal tab stops in multiple-line (but not single-line) text box controls: If the text in the text box contains tab characters (character code 9), the text following the tab character is aligned at the next tab stop. You can set the position of these tab stops by calling the SendMessage function in the Windows API and passing an array that contains the new tab stop settings.
Private Declare Function SendMessageSetTabs Lib _
"user32" Alias "SendMessageA" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long, _
lParam As Any) As Long
Const EM_SETTABSTOPS = &HCB

Sub ChangeTabs(anyText As TextBox, tabcount As Integer)
Dim i As Integer
Dim alngTabs() As Long
Dim lngRC As Long
ReDim alngTabs(tabcount - 1)
For i = 0 To UBound(alngTabs)
alngTabs(i) = (i + 1) * 96
' Set value to specify tabs in "dialog units."
Next i
' Call with null pointer to empty existing
' tab stops.
lngRC = SendMessageSetTabs(anyText.hwnd, _
EM_SETTABSTOPS, 0, vbNullString)
' Pass first element in array; other elements
' follow it in memory.
lngRC = SendMessageSetTabs(anyText.hwnd, _
EM_SETTABSTOPS, tabcount, alngTabs(0))
anyText.Refresh
End Sub


When you call this procedure, you specify the name of the text box and the number of tab stops you want to use for the indent. For example:
Private Sub Command1_Click()
ChangeTabs Text1, 4
End Sub


This approach will also work for string arrays. A DLL procedure written in C treats a string array as an array of pointers to string data, which is the same way Visual Basic defines a string array.

For More Information For more information on SAFEARRAYs and other Automation data types, see the Microsoft Press book, OLE 2 Programmer's Reference.
Passing User-Defined Types to a DLL Procedure

Some DLL procedures take user-defined types as arguments. (User-defined types are referred to as "structures" in C and as "records" in Pascal.) As with arrays, you can pass the individual elements of a user-defined type the same way you would pass ordinary numeric or string variables.

You can pass an entire user-defined type as a single argument if you pass it by reference. User-defined types cannot be passed by value. Visual Basic passes the address of the first element, and the rest of the elements of a user-defined type are stored in memory following the first element. Depending on the operating system, there may also be some padding.

For example, several procedures in the operating environment DLLs accept a user-defined type for a rectangle, which has the following structure:
Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type


Two of the procedures that accept a rectangle are DrawFocusRect, which draws a dotted outline around the specified rectangle, and InvertRect, which inverts the colors of the specified rectangle. To use the procedures, place these declarations in the Declarations section of a form or standard module:
Declare Function DrawFocusRect Lib "User32" Alias _
"DrawFocusRect" (ByVal hdc As Long, _
lpRect As RECT) As Long

Declare Function InvertRect Lib "User32" Alias _
"InvertRect" (ByVal hdc As Long, _
lpRect As RECT) As Long

Dim MouseRect As RECT


Now you can use the following Sub procedures to call the DLLs:
Private Sub Form_MouseDown (Button As Integer, _
Shift As Integer, X As Single, Y As Single)
ScaleMode = 3
If Button And 1 Then
MouseRect.Left = X
MouseRect.Top = Y
MouseRect.Right = X
MouseRect.Bottom = Y
End If
End Sub

Private Sub Form_MouseUp (Button As Integer, _
Shift As Integer, X As Single, Y As Single)
ScaleMode = 3
If Not (Button And 1) Then
MouseRect.Right = X
MouseRect.Bottom = Y
InvertRect hDC, MouseRect
End If
End Sub

Private Sub Form_MouseMove (Button As Integer, _
Shift As Integer, X As Single, Y As Single)
ScaleMode = 3
If Button And 1 Then
DrawFocusRect hDC, MouseRect
MouseRect.Right = X
MouseRect.Bottom = Y
DrawFocusRect hDC, MouseRect
End If
End Sub


User-defined types can contain objects, arrays, and BSTR strings, although most DLL procedures that accept user-defined types do not expect them to contain string data. If the string elements are fixed-length strings, they look like null-terminated strings to the DLL and are stored in memory like any other value. Variable-length strings are incorporated in a user-defined type as pointers to string data. Four bytes are required for each variable-length string element.
Note When passing a user-defined type that contains binary data to a DLL procedure, store the binary data in a variable of an array of the Byte data type, instead of a String variable. Strings are assumed to contain characters, and binary data may not be properly read in external procedures if passed as a String variable.

Passing Function Pointers to DLL Procedures and Type Libraries

If you're familiar with the C programming language, function pointers may be familiar to you. If you're not, the concept merits some explanation. A function pointer is a convention that enables you to pass the address of a user-defined function as an argument to another function you've declared for use within your application. By using function pointers, you can now call functions like EnumWindows to list the open windows on the system, or EnumFontFamilies to catalog all of the current fonts. You can also use them to gain access to many other functions from the Win32 API that have not previously been supported in Visual Basic.

For Visual Basic 5.0, several limitations apply to the use of function pointers. For details, see "Limitations and Risks with Function Pointers" later in this topic.
Learning About Function Pointers

The use of function pointers is best illustrated with an example. To start, look at the EnumWindows function from the Win32 API:
Declare Function EnumWindows lib "user32" _
(ByVal lpEnumFunc as Long, _
ByVal lParam as Long ) As Long


EnumWindows is an enumeration function, which means that it can list the handle of every open window on your system. EnumWindows works by repeatedly calling the function you pass to its first argument (lpEnumFunc). Each time EnumWindows calls the function, EnumWindows passes it the handle of an open window.

When you call EnumWindows from your code, you pass a user-defined function to this first argument to handle the stream of values. For example, you might write a function to add the values to a list box, convert the hWnd values to window names, or take whatever action you choose.

To specify that you're passing a user-defined function as an argument, you precede the name of the function with the AddressOf keyword. Any suitable value can be passed to the second argument. For example, to pass the function MyProc as an argument, you might call the EnumWindows procedure as follows:
x = EnumWindows(AddressOf MyProc, 5)


The user-defined function you specify when you call the procedure is referred to as the callback function. Callback functions (or "callbacks," as they are commonly called) can perform any action you specify with the data supplied by the procedure.

A callback function must have a specific set of arguments, as determined by the API from which the callback is referenced. Refer to your API documentation for information on the necessary arguments and how to call them.
Using the AddressOf Keyword

Any code you write to call a function pointer from Visual Basic 5.0 must be placed in a standard .BAS module - you can't put the code in a class module or attach it to a form. When you call a declared function using the AddressOf keyword, you should be aware of the following conditions:
* AddressOf can only be used immediately preceding an argument in an argument list; that argument can be the name of a user-defined sub, function, or property.
* The sub, function, or property you call with AddressOf must be in the same project as the related declarations and procedures.
* You can only use AddressOf with user-defined subs, functions, or properties - you cannot use it with external functions declared with the Declare statement, or with functions referenced from type libraries.
* You can pass a function pointer to an argument that is typed As Any or As Long in a declared Sub, Function, or user-defined type definition.

Storing a Function Pointer in a Variable

At times, you may need to store a function pointer in an intermediate variable before passing it to the DLL. This is useful if you want to pass function pointers from one Visual Basic function to another. It's required if you are calling a function like RegisterClass, where you need to pass the pointer through an argument to a structure (WndClass), which contains a function pointer as one of its elements.

To assign a function pointer to an element in a structure, you write a wrapper function. For example, the following code creates the wrapper function FnPtrToLong, which can be used to put a function pointer in any structure:
Function FnPtrToLong (ByVal lngFnPtr As Long) As Long
FnPtrToLong = lngFnPtr
End Function


To use the function, you first declare the type, then call FnPtrToLong. You pass AddressOf plus your callback function name for the second argument.
Dim mt as MyType
mt.MyPtr = FnPtrToLong(AddressOf MyCallBackFunction)

Subclassing

Subclassing is a technique that enables you to intercept Windows messages being sent to a form or control. By intercepting these messages, you can then write your own code to change or extend the behavior of the object. Subclassing can be complex, and a thorough discussion of it is beyond the scope of this book. The following example offers a brief illustration of the technique.
Important When Visual Basic is in break mode, you can't call vtable methods or AddressOf functions. As a safety mechanism, Visual Basic simply returns 0 to the caller of an AddressOf function without calling the function. In the case of subclassing, this means that 0 is returned to Windows from the WindowProc. Windows requires nonzero return values from many of its messages, so the constant 0 return may create a deadlock situation between Windows and the Visual Basic, forcing you to end the process.


This application consists of a simple form with two command buttons. The code is designed to intercept Windows messages being sent to the form and to print the values of those messages in the Immediate window.

The first part of the code consists of declarations for the API functions, constant values, and variables:
Declare Function CallWindowProc Lib "user32" Alias _
"CallWindowProcA" (ByVal lpPrevWndFunc As Long, _
ByVal hwnd As Long, ByVal Msg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

Declare Function SetWindowLong Lib "user32" Alias _
"SetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Const GWL_WNDPROC = -4
Global lpPrevWndProc As Long
Global gHW As Long


Next, two subroutines enable the code to hook into the stream of messages. The first procedure (Hook) calls the SetWindowLong function with the GWL_WNDPROC index to create a subclass of the window class that was used to create the window. It then uses the AddressOf keyword with a callback function (WindowProc) to intercept the messages and print their values in the Immediate window. The second procedure (Unhook) turns off subclassing by replacing the callback with the original Windows procedure.
Public Sub Hook()
lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
AddressOf WindowProc)
End Sub

Public Sub Unhook()
Dim temp As Long
temp = SetWindowLong(gHW, GWL_WNDPROC, _
lpPrevWndProc)
End Sub

Function WindowProc(ByVal hw As Long, ByVal uMsg As _
Long, ByVal wParam As Long, ByVal lParam As Long) As _
Long
Debug.Print "Message: "; hw, uMsg, wParam, lParam
WindowProc = CallWindowProc(lpPrevWndProc, hw, _
uMsg, wParam, lParam)
End Function


Finally, the code for the form sets the initial hWnd value, and the code for the buttons simply calls the two subroutines:
Private Sub Form_Load()
gHW = Me.hwnd
End Sub

Private Sub Command1_Click()
Hook
End Sub

Private Sub Command2_Click()
Unhook
End Sub

Limitations and Risks with Function Pointers

Working with function pointers can be unforgiving. You lose the stability of Visual Basic's development environment any time you call a DLL, but when working with function pointers, it can be especially easy to cause the application to fail and to lose your work. Save often and back up your work as necessary. Following are notes on some areas that require special attention when working with function pointers:
* Debugging. If your application fires a callback function while in break mode, the code will be executed, but any breaks or steps will be ignored. If the callback function generates an exception, you can catch it and return the current value. Resets are prohibited in break mode when a callback function is on the stack.
* Thunks. Thunking is the way that Windows enables relocatable code. If you delete a callback function in break mode, its thunk is modified to return 0. This value will be correct most of the time - but not all of the time. If you delete a callback function in break mode and then type it again, it's possible that some callees will not know about the new address. Thunks aren't used in the .exe 3/4 the pointer is passed directly to the entry point.
* Passing a function with the wrong signature. If you pass a callback function that takes a different number of arguments than the caller expects, or mistakenly calls an argument with ByRef or ByVal, your application may fail. Be careful to pass a function with the correct signature.
* Passing a function to a Windows procedure that no longer exists. When subclassing a window, you pass a function pointer to Windows as the Windows procedure (WindowProc). When running your application in the IDE, however, it's possible that the WindowProc will be called after the underlying function has already been destroyed. This will likely cause a general protection fault and may bring down the Visual Basic development environment.
* "Basic to Basic" function pointers are not supported. Pointers to Visual Basic functions cannot be passed within Visual Basic itself. Currently, only pointers from Visual Basic to a DLL function are supported.

Passing Other Types of Information to a DLL Procedure

Visual Basic supports a wide range of data types, some of which may not be supported by the procedures in certain dynamic-link libraries. The following topic describes how to handle some of the special cases you may find when using Visual Basic variables with DLL procedures.
Passing Null Pointers

Some DLL procedures may sometimes expect to receive either a string or a null value as an argument. If you need to pass a null pointer to a string, declare the argument As String and pass the constant vbNullString.

For example, the FindWindow procedure can determine if another application is currently running on your system. It accepts two string arguments, one for the class name of the application, and another for the window title bar caption:
Declare Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long


Either of these arguments can be passed as a null value. Passing a zero-length string ("") does not work, however, as this passes a pointer to a zero-length string. The value of this pointer will not be zero. You instead need to pass an argument with the true value of zero. The easiest way to do this is by using the constant value vbNullString for the appropriate argument:
hWndExcel = FindWindow(vbNullString, "Microsoft Excel")


Another way to handle this situation is to rewrite the declare to substitute a Long data type for the argument that you want to pass as null, and then call that argument with the value 0&. For example:
Declare Function FindWindowWithNull Lib "user32" -
Alias "FindWindowA" (ByVal lpClassName As Long, _
ByVal lpWindowName As String) As Long

hWndExcel = FindWindow(0&, "Microsoft Excel")

Passing Properties

Properties must be passed by value. If an argument is declared with ByVal, you can pass the property directly. For example, you can determine the dimensions of the screen or printer in pixels with this procedure:
Declare Function GetDeviceCaps Lib "gdi32" Alias _
"GetDeviceCaps" (ByVal hdc As Long, _
ByVal nIndex As Long) As Long


You can also pass the hDC property of a form or the Printer object to this procedure to obtain the number of colors supported by the screen or the currently selected printer. For example:
Private Sub Form_Click ()
Const PLANES = 14, BITS = 12
Print "Screen colors ";
Print GetDeviceCaps(hDC, PLANES)* 2 ^ _
GetDeviceCaps(hDC, BITS)
Print "Printer colors ";
Print GetDeviceCaps(Printer.hDC, PLANES) * _
2 ^ GetDeviceCaps(Printer.hDC, BITS)
End Sub


To pass a property by reference, you must use an intermediate variable. For example, suppose you want to use the GetWindowsDirectory procedure to set the Path property of a file list box control. This example will not work:
ReturnLength = GetWindowsDirectory(File1.Path,_
Len(File1.Path))


Instead, use the following code to set the property:
Dim Temp As String, ReturnLength As Integer
Temp = String(255, 0)
ReturnLength = GetWindowsDirectory(Temp, Len(Temp))
Temp = Left(Temp, ReturnLength)
File1.Path = Temp


Use this technique with numeric properties if you want to pass them to DLL procedures that accept arguments by reference.
Using Handles with DLLs

A handle is a unique Long value defined by the operating environment. It is used to refer to objects such as forms or controls. The operating environment DLL procedures make extensive use of handles - handles to windows (hWnd), handles to device contexts (hDC), and so on. When a procedure takes a handle as an argument, always declare it as a ByVal Long. DLL functions that return a handle can be declared as Long functions. Handles are identifier (ID) numbers, not pointers or numeric values; never attempt mathematical operations on them.

The hWnd property of forms and nongraphical controls and the hDC property of forms and picture box controls supply valid handles that you can pass to DLL procedures. Like any other property passed to a DLL procedure, they can be passed only by value.
Passing Variants

Passing an argument of type Variant is similar to passing any other argument type, as long as the DLL procedure uses the Automation VARIANT data structure to access the argument data. To pass Variant data to a argument that is not a Variant type, pass the Variant data ByVal.
Converting C Declarations to Visual Basic

The procedures in DLLs are most commonly documented using C language syntax. To call these procedures from Visual Basic, you need to translate them into valid Declare statements and call them with the correct arguments.

As part of this translation, you must convert the C data types into Visual Basic data types and specify whether each argument should be called by value (ByVal) or implicitly, by reference (ByRef). The following table lists common C language data types and their Visual Basic equivalents for 32-bit versions of Windows.
C language data type
In Visual Basic declare as
Call with

ATOM
ByVal variable As Integer
An expression that evaluates to an Integer
BOOL
ByVal variable As Long
An expression that evaluates to a Long
BYTE
ByVal variable As Byte
An expression that evaluates to a Byte
CHAR
ByVal variable As Byte
An expression that evaluates to a Byte
COLORREF
ByVal variable As Long
An expression that evaluates to a Long
DWORD
ByVal variable As Long
An expression that evaluates to a Long
HWND, HDC, HMENU, etc. (Windows handles)
ByVal variable As Long
An expression that evaluates to a Long
INT, UINT
ByVal variable As Long
An expression that evaluates to a Long
LONG
ByVal variable As Long
An expression that evaluates to a Long
LPARAM
ByVal variable As Long
An expression that evaluates to a Long
LPDWORD
variable As Long
An expression that evaluates to a Long
LPINT, LPUINT
variable As Long
An expression that evaluates to a Long
LPRECT
variable As type
Any variable of that user-defined type
LPSTR, LPCSTR
ByVal variable As String
An expression that evaluates to a String
LPVOID
variable As Any
Any variable (use ByVal when passing a string)
LPWORD
variable As Integer
An expression that evaluates to an Integer
LRESULT
ByVal variable As Long
An expression that evaluates to a Long
NULL
As Any or
ByVal variable As Long
ByVal Nothing or ByVal 0& or vbNullString
SHORT
ByVal variable As Integer
An expression that evaluates to an Integer
VOID
Sub procedure
Not applicable
WORD
ByVal variable As Integer
An expression that evaluates to an Integer
WPARAM
ByVal variable As Long
An expression that evaluates to a Long

 
Copyright © 1998-2000, The KPD-Team.
Send mail to KPDTeam@Allapi.net with comments about this web site.
This site is located at
http://www.Allapi.net/