Variable-Length Strings in UDTs


The other type of string is relatively rare in Windows API functions, although it shows up all the time in the common dialog functions in COMDLG32.DLL. It appears, for example, in the OPENFILENAME structure used by the GetOpen­FileName function:

typedef struct tagOFN {
DWORD lStructSize;
HWND hwndOwner;
HINSTANCE hInstance;
LPCTSTR lpstrFilter;
§
} OPENFILENAME;

In this structure, lpstrFilter is not a string but a pointer to a string whose data is located somewhere else. What a coincidence. A Basic string in the BSTR format is also a pointer to a string located somewhere else. You code this type in Basic in the obvious way:

Private Type OPENFILENAME
lStructSize As Long
hwndOwner As Long
hInstance As Long
lpstrFilter As String
§
End Type

With your type defined this way, you can simply assign the string:

opendlg.lpstrFilter = sFilter

Although this might seem obvious to new Basic programmers, it would seem like a minor miracle to any Visual Basic programmer who happened to be trapped in a time capsule during the version 4 era. Refer back to Figure 2-6, page 73, to see why this technique would never have worked in version 3. The old HLSTR format was a pointer to a pointer to characters, but the new BSTR format is a direct pointer to characters. The only difference between an LPWSTR and a BSTR is that the BSTR has a prefix. In other words, the BSTR type is a superset of the LPWSTR type, and the common subset is the only thing Windows cares about.


Don’t forget that a string used as an input buffer must be large enough to receive the data. Using strings in UDTs looks so simple and natural that it’s easy to forget that you’re dealing with an unforgiving API. Frequently, structures that have buffers to receive strings have an accompanying field containing the maximum length. Be sure to set the maximum-length field and pad your strings before assigning them to the string field. In some cases, the API will return the actual length through a field of the UDT, and you can use this to truncate the string with the Left$ function. If the length isn’t returned, you can truncate after the first null with the StrZToStr function from UTILITY.BAS. If you need to assign a null pointer to a string field, use vbNullString or sNullStr.


Unfortunately, you can’t define fields of type String in a type library. Common dialog structures such OPENFILENAME, CHOOSEFONT, and PRINTDLG must be implemented as Visual Basic UDTs. This means that the associated Declare statements must also be written in Visual Basic. And if you’ve gone that far, you might as well put the constants in the same file. The MCommonDialog module wraps the common dialog API functions in Basic-friendly procedures based on optional arguments. The dirty work of using API declarations is hidden so that once you figure out the wrappers, you never need the API functions and you don’t miss not having them in a type library. “Common Dialog Extensions” in Chapter 9 discusses this code in detail.