Visual Basic Concepts
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 FunctionpublicnameLib "libname" [Alias "alias"] [([[ByVal]variable[As type] [,[ByVal]variable[Astype]]...])] As Type
If a procedure does not return a value, write the declare as a Sub:
Declare SubpublicnameLib "libname" [Alias "alias"] [([[ByVal]variable[Astype][,[ByVal]variable [Astype]]...])]
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.
*For More Information* See "Declare Statement" in the Language Reference.
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:
Directory containing the .exe file
Windows system directory (often but not necessarily \Windows\System)
Windows directory (not necessarily \Windows)
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|
|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/98. 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.
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
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 Windows 95 (or later), 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."