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:

  1. Directory containing the .exe file

  2. Current directory

  3. Windows system directory (often but not necessarily \Windows\System)

  4. Windows directory (not necessarily \Windows)

  5. 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/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.

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