Typeless Variables
The Windows API frequently requires typeless parameters. The idea is to pass different types of data to the same function. For example, the GetObject function handles pens, brushes, fonts, bitmaps, and palettes through the same parameter. This works fine in weakly typed languages such as C. It doesn’t work so well in Basic, a schizophrenic language that can’t make up its mind whether to be strongly typed or weakly typed. The Declare statement, at any rate, is strongly typed, most of the time. (Basic is also a typeless language, through its Variant type; but just as Basic doesn’t do pointers, Windows doesn’t do Variants.)
Every variable has a type, explicit or assumed, regardless of the host language. When you pass a variable to a function, that function must figure out the type so that it knows what to do with the variable. The type can be embedded in the data (as in Basic Variants), or it can be supplied as a separate parameter to the function. That’s the easy part. The tricky part is getting Basic to turn off its data typing so that you can pass different kinds of data. The Alias attribute of the Declare statement and the Any parameter type enable you to lie, cheat, steal, and have your way with data.
The GetObject API function illustrates several points about untyped parameters. The C version looks like this:
int GetObject(
HGDIOBJ hgdiobj, // Handle of graphics object
int cbBuffer, // Size of buffer for object information
LPVOID lpvObject // Pointer to buffer for object information
);
To use GetObject, you pass it the handle of a logical pen, a brush, a font, a bitmap, or a palette. You pass the length of the data you want to get back and the address of the variable where you want the data placed. This variable will have type LOGPEN, LOGBRUSH, LOGFONT, BITMAP, or int, depending on the data. Windows will use the handle to find the data and will then copy it to the variable.
The Basic prototype presents several problems. First, GetObject is the name of a Visual Basic function. In old versions of Visual Basic, you got an error if you tried to declare a function with an existing function name, but the current version (and version 4) offers no objections if you redefine GetObject with either a Declare statement or a Basic function. Nevertheless, don’t do it. You should rename this function. I call mine VBGetObject.
You can redefine a function to replace the built-in version with a new version. For example, you could write your own version of InStr. Any code you wrote earlier that uses the built-in InStr will work with your new version. This is how you use the renaming feature. You abuse it by giving your function the same name as the built-in version but different behavior. See the sidebar “Better Basic Through Subclassing,”page 240, for a more complete discussion of this feature.
You’ll encounter the name problem in several cases in the Windows API, and it’s likely to come up anytime you try to use a DLL written for another language. For example, the Windows API includes the _lopen, _lread, _lwrite, and _lclose functions, but you can’t write declarations for them because a Basic name can’t start with an underscore. (My type library calls them lopen, lread, lwrite, and lclose.) Basic provides the Alias attribute to let you specify that a name recognized in one way by the DLL can have a different Basic name.
The second problem is to turn off Basic’s type checking. In other words, you need a type that corresponds to a C void pointer. The Any type passed by reference is a rough equivalent. When you declare a parameter with As Any, you cancel the contract. Basic no longer promises the Windows API anything in particular. Windows promises to write no more than the specified number of bytes to the specified variable, whether they fit or not. It’s up to you to pass a variable that can accept the data. If you’re the kind of programmer who doesn’t mind working without a net, you might be getting bright ideas about using As Any in your own Basic procedures. Forget it. Basic accepts As Any only in Declare statements. Use Variant, not As Any, to write typeless functions in Basic.
The declaration for VBGetObject looks like this:
Declare Function VBGetObject Lib "GDI32" Alias "GetObjectA" ( _
ByVal hObject As Long, ByVal cbBuffer As Long, _
lpvObject As Any) As Long
Notice that the real name of the function is GetObjectA, not GetObject. I’ll explain why in the next section.
The Basic version of C’s BITMAP type looks like this:
Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type
You can pass a BITMAP variable to VBGetObject:
Dim bmp As BITMAP
§
c = VBGetObject(pbBitmap.Picture, Len(bmp), bmp)
That’s easy enough if you happen to know that the Picture property of a picture box containing a bitmap is actually a bitmap handle. (Chapter 6 explains this and related issues.) But you’d better be sure that whatever gets passed to the function is what you say it is. If you pass a bitmap handle in parameter 1, the length of a LOGPEN in parameter 2, and a LOGBRUSH variable in parameter 3, you won’t like the results.
If you ignore the safety net that Basic offers, you must be ready to accept the consequences. Or, better yet, use the safety net. You can alias as many functions as you want to the same API function, so why not do a type-safe version for each data type?
Declare Function GetObjectBrush Lib "GDI32" Alias "GetObjectA" ( _
ByVal hBrush As Long, ByVal cbBuffer As Long, _
lpBrush As LOGBRUSH) As Long
Declare Function GetObjectBitmap Lib "GDI32" Alias "GetObjectA" ( _
ByVal hBitmap As Long, ByVal cbBuffer As Long, _
lpBitmap As BITMAP) As Long
The Windows API type library provides these functions and several other GetObject variations, but don’t let the increased safety make you overconfident. If you pass a LOGPEN variable to GetObjectBrush, you get the error ByRef argument type mismatch. But if you pass a Pen handle to GetObjectBrush, neither Basic nor Windows complains. Windows gets the data type and the data from the handle, but it can’t tell whether you passed the matching length and destination variable.
Visual Basic 4 didn’t support a type library equivalent to the As Any syntax, and it didn’t support structures in type libraries. Therefore, VBGetObject had to be implemented with the Declare statement. Those limitations are gone. You’ll find VBGetObject and all its aliases in the current Windows API type library.