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.