Fixed-Length Strings in UDTs


A few Windows functions use structures (UDTs to Basic programmers) containing strings. This creates a problem because a Basic string in a Basic UDT differs somewhat from a C string in a C structure. I once made the foolish mistake of asserting on a Microsoft e-mail alias where Visual Basic is discussed that Basic programmers cannot use Windows structures containing strings. Take my word for it: don’t try to tell hardcore programmers what they can and cannot do. This was back in the old days when dealing with strings in the HLSTR format was more difficult, but even then the real hackers found a way.


In C, as in Basic, a structure can contain two types of strings. In Basic, the first type is called a fixed-length string; in C, it is called an array of characters. The WIN32_FIND_DATA type (used with FindFirstFile and friends) illustrates. It looks like this in C:

typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ]; // 260
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA;

In Basic, it looks like this:

Public Type WIN32_FIND_DATA
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName As String * 260
cAlternateFileName As String * 14
End Type

No problem. The cFileName and cAlternateFileName fields work the same in both C and Basic. You can use them just the way you would expect:

Dim fnd As WIN32_FIND_DATA
hFind = FindFirstFile(“*.*", fnd)
Debug.Print fnd.cFileName

Of course, some additional work goes on in the background. Basic fixed-length strings are stored as Unicode, but they must be converted to ANSI when passed to API functions.


Unfortunately, the type library version of the WIN32_FIND_DATA works a little bit differently. Language-independent type libraries don’t know anything about Basic fixed-length strings. You have to define this structure to store the strings in byte arrays. You can check WINBASE.IDL on the companion CD to see how this is done in a type library. Here’s what it would look like if you coded the same type in Basic:

Public Type WIN32_FIND_DATA
dwFileAttributes As Long
§
#If Unicode Then
cFileName(0 To 519) As Byte
cAlternateFileName(0 To 27) As Byte
#Else
cFileName(0 To 259) As Byte
cAlternateFileName(0 To 13) As Byte
#End If
End Type

In reality, you couldn’t use the Unicode version of such a type because you can’t write Unicode Declare statements (except with the hacks described in “Unicode API Functions,” page 86). But if you could (or if you used the type library version), using the data would look like this:

Dim fnd As WIN32_FIND_DATA
hFind = FindFirstFile(“*.*", fnd)
If UnicodeTypeLib Then
Debug.Print fnd.cFileName
Else
Debug.Print StrConv(fnd.cFileName, vbUnicode)
End If

The UnicodeTypeLib constant is defined with a value of zero in the ANSI version of the Windows API type library (WIN.TLB). It’s defined to nonzero in the Unicode version (WINU.TLB). Notice that the Unicode test is done at runtime. You could also define a Unicode constant in the IDE and test with #If rather than If. Although compile-time testing is faster, it wouldn’t work with the VBCore component that we’ll examine in Chapter 5. Having tests for the Unicode type library scattered throughout code would be ugly and difficult to maintain. Therefore, I hide the test in the BytesToStr wrapper function:

Function BytesToStr(ab() As Byte) As String
If UnicodeTypeLib Then
BytesToStr = ab
Else
BytesToStr = StrConv(ab, vbUnicode)
End If
End Function

This lets me shorten the calling code:

hFind = FindFirstFile(“*.*", fnd)
Debug.Print BytesToStr(fnd.cFileName)

The difference between fixed-length strings in type libraries and in Basic UDTs is annoying. Fortunately, Windows rarely uses fixed-length strings in structures. One solution is to write Declare statements and UDTs rather than the type
library. Although easier and more familiar, this strategy works only for ANSI
functions because Visual Basic doesn’t directly support Unicode Declare statements. For most users, this probably won’t be an issue, but in the long run I think Windows NT will be an important platform and Unicode will be the best way to target it. I use the type library solution consistently because it works for both kinds of strings.