Getting Strings from the Windows API
Now let’s look at the other reason for passing strings—to get a string back from Windows. In Basic, if you want a procedure to return a string, you can simply make the procedure a function with a String return type. This doesn’t work well in C and many other languages, for reasons I won’t examine. Suffice it to say that you could never get two language designers to agree on a format for string returns.
The safe, portable way to get a string back from a procedure is to pass the address where the string is to be placed along with the maximum length of the string. Figure 2-7 on the following page shows an abbreviated version of how this works when using normal ANSI Declare statements. You might need to study “Unicode Versus Basic,” page 83, to fully understand the Unicode part of the transaction.
Let’s step through the parts in more detail. The C version of the GetWindowText function looks like this:
int GetWindowText(
HWND hWnd, // Handle of window
LPTSTR lpString, // Address of buffer for text
int nMaxCount // Maximum number of characters to copy
);
The Basic version looks like this:
Declare Function GetWindowText Lib "USER32" Alias "GetWindowTextA" ( _
ByVal hWnd As Long, ByVal lpString As String, _
ByVal nMaxCount As Long) As Long
The following fragment uses this function the long way. (We’ll consider shortcuts later.)
Dim sTemp As String, sTitle As String, c As Integer
sTemp = String$(255, 0)
c = GetWindowText(hProject, sTemp, 256)
sTitle = Left$(sTemp, c)
Let’s look carefully again at this call as a contract. Basic promises that it will null-terminate a string and pass its address (although null-terminating the string is wasted in this case because Windows doesn’t care about nulls in output strings). Windows promises that it will not write more than the maximum character count
Figure 2-7. Life as a string.
passed to it into the address it receives, that it will append a null to whatever it writes (the null is included in the maximum number written), and that it will return the actual number of non-null characters written in the return value.
Neither side promises enough room in memory for the characters to be written. That part is up to you. If you pass a 10-byte string but claim that the size is 255, Windows might cheerfully write 245 bytes of data into whatever resides in memory next to your string. You will be in for a rude surprise, possibly much later in your program, when you try to use whatever used to be in that memory before Windows overwrote it.
The example ensures that the string is long enough by changing sTemp to a string of 255 null characters, using the String$ function. (You actually get 256 nulls because Basic always appends a null.) You can use the Space$ function to create a space-padded string in the same way. Another way to ensure that you
never overwrite anything useful is to pass the actual length of the string as the
maximum:
c = GetWindowText(hProject, sTemp, Len(sTemp) + 1)
If you do this when the actual length of sTemp is 0, you won’t overwrite anything but you will get a truncated version of the window title that might give the mistaken impression that the window has no title when in fact you simply didn’t allow space for the title.
Let’s assume that you provide a 255-character string and that the title of the window is Hello. You get back from the function the 5 characters of the string, followed by a null, followed by 249 nulls. GetWindowText returns 5. When a C program sees this string, it assumes from the placement of the first null character that the string is 5 characters long. Basic doesn’t care about embedded nulls and assumes that the string is 255 characters long. If you really want a Basic string of 5 characters, you must assign it. The return value gives you the information you need to do this politely with the Left$ function. Incidentally, you could use just one string for both the temporary and the permanent versions:
sTitle = String$(255, 0)
c = GetWindowText(hProject, sTitle, 256)
sTitle = Left$(sTitle, c)
You might expect that it would be more efficient to pass a fixed-length string than to use the String$ function to pad a string. Well, that might be true in some situations for stand-alone fixed-length strings. I don’t know because I never use fixed-length strings anywhere other than in UDTs. The problem is that fixed-length strings are incompatible with many COM features. You can’t use them in methods and properties of public classes. They make a lot more sense in UDTs where you want all your data, including strings, to be in a fixed-size chunk. The TimeIt program has a test that compares variable- and fixed-length strings for various operations. Results are mixed, and they might be different with different tests, but generally I found fixed-length strings to be inefficient for the most common operations.
You can avoid padding a string every time you need to pass one to Windows. Define the string at the module level, and pad it out only once—in Form_Initialize or Class_Initialize. Just make sure that this string is really temporary and large enough for any API call. Never change its size, don’t use it for anything except API calls, and always copy the relevant portion to a permanent string immediately after use. That way, later calls won’t overwrite useful data.