Calling Routines in DLLs

In Windows, a macro can call routines in a dynamic-link library (DLL) or Word add-in library (WLL) to access capabilities that aren't directly available in WordBasic. Calls are often made to Windows API (application programming interface) routines, but they can be made to routines in any DLL that makes routines available for other programs. WLLs are a special kind of DLL written specifically for Word; WordBasic can call routines in a WLL in the same way it calls routines from a DLL.

On the Macintosh, DLL files are not available, but you can call routines stored in WLLs. In this section, the term DLL is used to refer to DLLs in Windows and Word add-in libraries in Windows or on the Macintosh.

Note

For information about writing Word add-in libraries using the Word API, see Appendix C, "Microsoft Word Application Programming Interface," in Part 3, "Appendixes."

Because DLL routines reside in files that are external to Word, you must let WordBasic know where it can find the routines you want to use. You provide this information with the Declare statement. Once you have declared a DLL routine, you can use it in your code like any other routine (although you have to be especially careful about the arguments that you pass to DLL routines).

There are two steps to using a DLL routine:

1. Tell WordBasic about the routine by using a Declare statement.

2. Make the actual call.

You declare a DLL routine only once in a macro. You then can call it any number of times from any subroutine or user-defined function used in the macro.

Declaring a DLL Routine

To declare a DLL routine, place a Declare statement outside a subroutine or
user-defined function. Typically, Declare statements are placed at the start of
the macro before any other statements.

If the routine does not return a value, declare it as a subroutine. For example:


Declare Sub SetWindowTextA Lib "User32" (hWnd As Long,\
    lpString As String)

If the routine returns a value, declare it as a function. For example:


Declare Function GetSystemMetrics Lib "User32" (num As Long) \
    As Long

In Windows 95 and Windows NT, function names are case-sensitive and must be declared exactly as they appear in the DLL.

Here are the same declarations in Word version 6.0 for Windows:


Declare Sub SetWindowText Lib "User" (hWnd As Integer, \
    lpString As String)
Declare Function GetSystemMetrics Lib "User" (num As Integer) As Integer

Note the Lib keyword in the Declare statement. The Declare statement can also contain an optional Alias keyword. The use of these keywords is explained under "Special Considerations When Declaring DLL Routines" later in this section.

For the complete syntax of the Declare statement, see Part 2, "WordBasic Reference."

Calling a DLL Routine

Once a routine is declared, you can then call it just as you would a WordBasic statement or function. The following example uses the GetSystemMetrics Windows API routine to determine whether a mouse is installed. The value 19 assigned to SM_MOUSEPRESENT is one of a number of values that can be passed to the GetSystemMetrics function. These values are listed in the Microsoft Win32® Software Development Kit.


Declare Function GetSystemMetrics Lib "User32" (num As Long) \
    As Long

Sub MAIN
    SM_MOUSEPRESENT = 19
    If GetSystemMetrics(SM_MOUSEPRESENT) Then MsgBox "Mouse installed"
End Sub

Important

WordBasic can't verify that you are passing correct values to a DLL routine. If you pass incorrect values, the routine may fail, which may cause unexpected behavior or errors in Word or the operating system. Take care when experimenting with DLL routines, and save your work often.

Special Considerations When Declaring DLL Routines

As you have probably realized, the declarations for DLL routines can be quite complex. The Microsoft Word Developer's Kit disk for Windows includes the text files WIN16API.TXT and WIN32API.TXT, which list WordBasic DLL routine declarations for the Windows API. You can search for the declarations you want in the text files, copy them, and paste them into your macro.

Once you have pasted the appropriate DLL routine declarations into your code, you simply call the routines like any other routine in your application. Because you have to take extra care when passing values to DLL routines, however, you should first read "Calling DLL Routines with Specific Variable Types" later in this section.

If you are attempting to call a routine in a DLL that is not part of the operating environment, you should consult the documentation for the DLL to determine the proper declaration for it.

Specifying the Library

The Lib LibName$ clause in the Declare statement tells WordBasic where to find the dynamic-link library. For the Windows 95 and Windows NT operating environment DLLs, this is either "User32," "GDI32," "Kernel32," or one of the other system DLLs such as "MMSystem." For Windows 3.x, this is either "User," "GDI," "Kernel," or one of the other system DLLs. For other DLLs, the LibName$ is a file specification that can include a path. For example:


Declare Function EnvString Lib "c:\win\strings.dll" (stringbuffer$, \
stringnum As Long) As Long

If you are declaring a function in a loaded DLL or WLL, you do not need to specify the path.

Using an Alias

You can use the Alias keyword to call a routine by a different name within a macro. You may need to do this if the routine name is not a valid subroutine or function name in WordBasic. If a routine has a long name, you can use Alias to substitute a shorter one. For example, this Declare statement substitutes the name WinDir for the routine's longer name (GetWindowsDirectory):


Declare Function WinDir Lib "Kernel32" Alias "GetWindowsDirectoryA" \
    (stringbuffer$, stringnum As Long) As Long

Note that the routine's real name, GetWindowsDirectoryA, follows the Alias keyword and that the alias WinDir (the name given to the routine within the macro) follows the Function keyword.

Here is the same Declare statement for Windows 3.x:


Declare Function WinDir Lib "Kernel" Alias "GetWindowsDirectory" \
    (stringbuffer$, stringnum As Integer) As Integer

Now you can use this shorter name to call the GetWindowsDirectory routine:


WinPath$ = String$(145, "*")
worked = WinDir(WinPath$, Len(WinPath$))
MsgBox WinPath$

Calling DLL Routines with Specific Variable Types

Many DLL routines require or return variable types that are not supported in WordBasic. WordBasic, in turn, supports variable-length strings, which are not supported by most DLL routines. Therefore, you must take care when passing variables to and receiving variables from DLL routines.

DLL Routines That Use Strings

Windows 95 or Windows NT functions that use strings are typically available in two versions: ANSI and Unicode. Word version 7.0 and Word version 6.0 for Windows NT are ANSI applications and must, therefore, use ANSI versions of the API functions. The ANSI API functions have an "A" appended to the function name. For instance, the Windows 3.x function GetWindowsDirectory is GetWindowsDirectoryA in Windows 95 and Windows NT.

DLL Routines That Modify Strings

A DLL routine can modify a WordBasic string variable it receives as an argument. (Strings are always passed to DLL routines by reference.) Be careful when calling a DLL routine that modifies a string. A DLL cannot increase the length of a WordBasic string; it simply writes beyond the end of the string if
it is not long enough. This corrupts other areas of memory. You can avoid this problem by making the string argument long enough that the DLL routine can never write past the end of it.

For example, the GetWindowsDirectory routine returns the path for the Windows folder in its first argument:


Declare Function GetWindowsDirectoryA Lib "Kernel32" \
    (stringbuffer As String, stringnum As Long) As Long

A safe way to call this routine is to make the returned argument at least 255 characters long by filling it with characters — in this case, asterisk (*) characters — before it is passed by reference to the DLL routine:


path$ = String$(255, "*")
worked = GetWindowsDirectory(path$, Len(path$))

Most DLL routines (and all the routines in the Windows API) expect standard
C strings, which end in a "null character" (ANSI 0). Word converts all strings that
it passes to DLL routines to null-terminated strings. Visual Basic includes the ByVal keyword to specify that a string should be passed as a null-terminated string, but this keyword is not supported (or necessary) in WordBasic.

Note

Windows DLL routines generally do not return strings longer than 255 characters. While this is true of many other libraries, always consult the documentation for the routine in question.

The Double, Integer, and Long Variable Types

Two variable types are supported within WordBasic: strings and numbers (double-precision floating-point numbers). But in the Declare statement only, WordBasic supports two additional types, Integer and Long:

Integer and Long variables are used to export values from and import values to WordBasic. When WordBasic receives an Integer or Long value from a DLL routine, the value is converted into a double-precision floating-point value, the standard WordBasic numeric type.

If a function in Windows 3.x uses the type Integer, the corresponding function in Windows 95 and Windows NT uses Long.

In addition, the variable type Double is available in Declare. Double is a double-precision floating-point number equivalent to the standard WordBasic numeric type. When you define a variable as Double in a Declare instruction, it is the same as defining it as a numeric variable elsewhere in WordBasic.

Structured Variable Limitation

Some DLL routines take or receive variables referred to as "structures" in C,
as "records" in Pascal, or as "user-defined types" in Visual Basic. DLL documentation often uses the C terminology. WordBasic does not support structured variables (other than dialog records), so these routines calling DLL routines that return or require structured variables may cause unpredictable results.

Arrays

You can pass individual elements of an array in the same way you pass any variable. For example, you can use the sndPlaySound routine to play a series of .WAV files stored in an array:


Declare Function sndPlaySoundA Lib "WINMM"(sound$, flag As Integer) \
    As Long

Sub MAIN
WAVEFILECOUNT = 10
Dim WaveFile$(WAVEFILECOUNT - 1)
For i = 0 To WAVEFILECOUNT - 1
    worked = sndPlaySoundA(WaveFile$(i), 0)
Next i
End Sub

WordBasic does not support passing entire arrays to DLL routines.

Converting Common Declarations

The routines in DLLs are most commonly documented using C language syntax. To call them from WordBasic, you must translate them into valid Declare statements and call them correctly. When translating, you may find the following table useful; it lists common C language declarations and their WordBasic equivalents.

C language declaration

In WordBasic declare as

Call with

Pointer to a string (LPSTR)

S As String or S$

A string variable

Pointer to an integer (LPINT)

L As Long

A long variable

Pointer to a long integer (LPDWORD)

L As Long

A long variable

Pointer to a structure (for example, LPRECT)

No equivalent

No equivalent


C language declaration

In WordBasic declare as

Call with

Integer (INT, UINT, WORD, BOOL)

L As Long (I As Integer in Windows 3.x)

A long variable (an integer variable in Windows 3.x)

Handle (hWnd, hDC, hMenu, and so on)

h As Long (h As Integer in Windows 3.x)

A long variable (an integer variable in Windows 3.x)

Long (DWORD, LONG)

L As Long

A long variable

Pointer to an array of integers

No equivalent

No equivalent

Pointer to a void (void *)

No equivalent

No equivalent


If the routine's return value is void, it should be declared as a subroutine (see "Declaring a DLL Routine" earlier in this section).