Strings in Callback Procedures


Enumeration of windows and fonts is a relatively simple matter, but enumeration of resources brings us to another level. I won’t get to what resources are or what you can do with them until later. The important thing here is that the callback procedures for the resource functions take string arguments.


Here’s what EnumResourceTypes looks like in C:

BOOL EnumResourceTypes(
HMODULE hModule, // Resource-module handle
ENUMRESTYPEPROC lpEnumFunc, // Pointer to callback function
LONG lParam // Application-defined parameter
);

The ENUMRESTYPEPROC parameter requires a procedure that looks like this:

BOOL CALLBACK EnumResTypeProc(
HANDLE hModule, // Resource-module handle
LPTSTR lpszType, // Pointer to resource type
LONG lParam // Application-defined parameter
);

The question is, how will you write an EnumResTypeProc that takes an LPTSTR parameter in Basic? Your first try might look something like this:

Function EnumResTypeProc(ByVal hModule As Long, _ 
ByVal lpszType As String, _
lParam As Long) As Long

That’s how you would write the parameters if you were writing a Declare statement for a function to be called from Basic. You’d use a String type for lpszType, knowing that Basic would automatically translate to Unicode. But you’re not writing a Declare statement; you’re writing a function. And it won’t be called by Basic; it will be called by Windows. And Windows hasn’t a clue about the String type. The only way Windows is going to understand what to do is if you write the function like this:

Function EnumResTypeProc(ByVal hModule As Long, _
ByVal lpszType As Long, _
lParam As Long) As Long

Windows will pass you a pointer to the string, but then what will you do with it in Basic? By now you should have figured out what you can do when the API passes a pointer: pass it on. The hack to convert an API pointer requires several API calls, so I wrap it in a function called PointerToString:

Function PointerToString(p As Long) As String
Dim c As Long
c = lstrlenPtr(p)
PointerToString = String$(c, 0)
If UnicodeTypeLib Then
CopyMemoryToStr PointerToString, ByVal p, c * 2
Else
CopyMemoryToStr PointerToString, ByVal p, c
End If
End Function

This looks simple, but if measured in the hours it took me to figure it out, PointerToString would be a pretty long function. First you get the length of the string. The normal version of the lstrlen function takes a string argument, but in this case we have a pointer, not a string, so you must use the lstrlenPtr alias. Keep in mind that lstrlenPtr might actually be lstrlenA or lstrlenW, depending on whether the API type library you have loaded is WIN.TLB or WINU.TLB. In either case, it returns the internal string length so that you can create a Basic string to hold it.


The length is returned in characters, but CopyMemory expects bytes. The Unicode version (as indicated by the test of UnicodeTypeLib) must double the character count. The ANSI version will actually be working on a temporary ANSI copy of the string created for the Unicode conversion. “Dealing with Strings,” page 72, describes what goes on behind the scenes in more detail. Given PointerToString, you can define the contents of the EnumResTypeProc:

Function EnumResTypeProc(ByVal hModule As Long, ByVal lpszType As Long, _
lParam As Long) As Long
If lpszType < 65535 Then
' Enumerate resources by ID
Call EnumResourceNamesID(hNull, lpszType, _
AddressOf EnumResNameProc, lParam)
Else
' Enumerate resources by string name
Call EnumResourceNamesStr(hNull, PointerToString(lpszType), _
AddressOf EnumResNameProc, lParam)
End If
EnumResTypeProc = True
End Function

Uh-oh. You can see how PointerToString is used to convert lpszType to a string, but the rest of the code raises more questions than it answers. What does it mean to pass a resource ID, and why would the value of the lpszType pointer be relevant? Those questions have more to do with resources than callbacks, so I’ll defer them until later.