Sending Strings to the Windows API


Sending input strings to the Windows API is simple and direct. Your API calls look pretty much the same as calls to Basic procedures. The trick is in defining the declarations.


The designers of Visual Basic enabled Basic-to-C conversion by overloading the ByVal attribute to mean something other than what its name implies—that is, they lied. Passing a string by value doesn’t actually pass it by value. Doing so would imply that all the bytes of the string are placed on the stack. Instead, Basic simply passes by value a pointer to the string. In Visual Basic 3, Basic also needed to ensure that the HLSTR was null terminated, which sometimes meant that the string had to be copied to a temporary string for processing by Windows and then copied back to the real Basic version afterward in case Windows modified the string copy. Those days are over because BSTRs are already null terminated. Now Basic makes a temporary copy for a completely different reason. But I’ll get to Unicode conversion later.


In the Windows API documentation, C string parameters have the type LPCTSTR if the string is to be used only as input. (The C in the name indicates constant.) Parameters have the type LPTSTR if the string is to be used as an output buffer filled by the function. The T in the names indicates that the string could be either ANSI (LPCSTR or LPSTR) or Unicode (LPWCSTR or LPWSTR), depending on constants passed to the C compiler at compile time. I’ll examine this issue in more detail later on.


For example, the FindWindow documentation looks like this:

HWND FindWindow(
LPCTSTR lpszClassName, // Address of class-name string
LPCTSTR lpszWindow // Address of window-name string
);

You can declare the function as follows:

Declare Function FindWindow Lib "USER32" Alias "FindWindowA" ( _
ByVal lpszClassName As String, ByVal lpszWindow As String) As Long

First notice the alias to FindWindowA. It turns out that USER32.DLL contains two different FindWindow functions, neither of which is named FindWindow. FindWindowW handles 16-bit Unicode strings. FindWindowA handles the 8-bit ANSI character strings that most programmers are used to. For now, all you need to know is that every Win32 function that deals with strings requires similar aliases. You will probably want to use the ANSI version in most cases because it works for both Windows 95 and Windows NT, but you’ll get slightly better performance in Windows NT–only programs if you use the Unicode version. If you use Declare statements, you have no choice: you must use the ANSI alias because the Declare statement is still crippled and unable to deal with Unicode directly. You have to use a type library (like the one supplied with this book) to use the Unicode versions.


Now assume that you want to call FindWindow to find the window handle of a running instance of the Calculator program. You could write the following code:

hWnd = FindWindow(“SciCalc", “Calculator”)

SciCalc is the class name of the Calculator window, and Calculator is the name shown in the window title bar. (Chapter 6 explains window classes and titles; for now, we’re concerned only with the syntax.)


What’s really going on here? The string SciCalc appears to be 7 characters long, but because it includes a terminating null character—Chr$(0)—the real length is 8 characters. Internally, the string also includes a preceding Long that contains the length of the string. Therefore, the string uses 12 bytes of memory, although the stored length is 7, which is what Basic would expect. Basic needs to know the length of the string at all times. The Basic Len function grabs the length out of the preceding length placeholder (using the SysStringLen function) without checking the characters. When you pass the string to the Windows API, however, the length is lost. Because it’s written in C, Windows doesn’t know or care how long a string is.


If you look at this exchange as a contract, Basic promises that when it passes a string with the ByVal attribute, the string will be null terminated and the address of the first character will be passed on the stack. The Windows API promises that if the string is constant (LPCTSTR), it will not modify the string or assume anything about the characters after the terminating null.


But this contract is less than bulletproof. Windows expects that the string will contain only one null character, the last. Basic makes no such promise. A passed string could have multiple null characters, which are perfectly legal in a Basic string. You as the programmer must ensure that the strings you pass don’t have inappropriate embedded nulls. As a practical matter, however, this usually isn’t a problem because few Basic programmers embed nulls in strings intended for Windows functions.