Enumerating Stuff
The most common, and often easiest, functions that take callbacks are the enumeration functions. You can enumerate windows, fonts, resources, GDI objects, and all sorts of other stuff. The test program TestEnum (TENUM.VBP) shown in Figure 2-8 enumerates some Windows things. Let’s start with the easiest things: windows.
Figure 2-8. Enumerating.
The EnumWindows function will enumerate all the top-level windows and, for each window, do whatever you tell it to do. The C prototype looks like this:
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc, // Pointer to callback function
LPARAM lParam); // Application-defined value
WNDENUMPROC is an unintelligible typedef that looks something like a spilled bucket of worms:
typedef BOOL (CALLBACK* WNDENUMPROC)(HWND, LPARAM);
To summarize, this is what C calls a function pointer type. It means that the EnumWindows function expects the address of a function that returns BOOL and has two parameters—an HWND and an LPARAM. The C compiler will not accept anything else—it will fail, for example, if it gets the address of a function that takes three arguments or returns a double type. When Windows receives a call to EnumWindows, it will iterate through its internal window list and call the given function pointer with the appropriate arguments for each window. It will accept a BOOL return that indicates whether to keep iterating. You define the function and pass its address.
Of course, under the surface, a pointer is a pointer. If you could get past C’s type protection, you could pass EnumWindows the address of a function with ten arguments, or the address of an array of Variants, or the address of a String of graffiti. EnumWindows would pass control to whatever junk happened to be at the address you passed it, and after doing a few random things, it would crash. But of course a thing like that couldn’t happen in Basic because Basic is a type-safe language. Or at least it used to be.
In this case, Visual Basic is a lot less type-safe than C or C++. The Declare for EnumWindows looks like this:
Public Declare Function EnumWindows Lib "USER32" ( _
ByVal lpEnumFunc As Long, lParam As Any) As Long
Notice that all that wimpy nonsense about return values and parameter types is gone. The address of lpEnumFunc is a Long containing a pointer—just like in assembly language. The lParam is an LPARAM in C, which is just a typedef for Long, but that’s OK because C allows us to typecast that Long to an address, a string, or whatever other data we want. The Basic equivalent is to use As Any by reference so that you can pass anything. The Windows API type library defines LPARAM as LPVOID—the type library equivalent of As Any (see “Type Variables,” page 69).
Now all you need is a function to pass and a way to get its address. Here’s the function:
Public lstEnumRef As ListBox
Function EnumWndProc(ByVal hWnd As Long, lParam As Long) As Long
' Increment count
lParam = lParam + 1
' Get window title and insert into ListBox
Dim s As String
s = WindowTextFromWnd(hWnd)
If s <> sEmpty Then
lstEnumRef.AddItem s
(continued)
lstEnumRef.ItemData(lstEnumRef.NewIndex) = hWnd
End If
' Return True to keep enumerating
EnumWndProc = True
End Function
And here’s how you tell Windows to call the function:
f = EnumWindows(AddressOf EnumWndProc, c)
lblResult = "Window count: " & c
The c variable passed to EnumWindows is the lParam argument received by the EnumWndProc procedure. The variable is passed by reference so that the same variable (not a fresh copy) will be passed each time. In the example, it’s a count of the number of times EnumWndProc is called, but you could pass any data that you want to be read or modified for each window.
The hWnd parameter of EnumWndProc is passed by Windows itself as it iterates through the available windows. EnumWndProc uses the hWnd parameter to get some text from the window and write that text to a ListBox. In this case, the data is written to a global ListBox reference variable, which was set to a real ListBox in Form_Load of the form that owns the ListBox. Finally EnumWndProc returns True. If you wanted to iterate through all the windows until you found one that met certain conditions, you could return False at the chosen window to stop the iteration. I’ll do this in a few pages.