Visual Basic Concepts

Passing Strings to a DLL Procedure

In general, strings should be passed to APIs using ByVal. 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.) BSTRs are Unicode, which means that each character takes two bytes. BSTRs typically end with a two-byte null character.

Figure 1.2 The BSTR type (each box represents two bytes)

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