One of the most annoying things about maintaining component- based software is determining the actual version number of every component that your application uses. I have done this tedious task more times than I would like to admit. This is typically accomplished by using Explorer, finding the file (in the abyss I call Windows\System), right-clicking the file, choosing the Properties menu item, choosing the Version tab... blah, blah, blah. Did you ever try to explain this process over the phone to a user who isnt computer savvy? After the fifth or sixth object module, the user gets a nice lesson on using Windows Explorer, but that isnt really the result you want and this essential information is required for technical support to be able to quickly and effectively determine the version and load path of any one of the software components used in your system.
Tired of this repetitive work, I created a software routine that, given a filename, returns the load path and the version of that file extracted from the version resource of that file (if available). This is pretty straightforward. Win32 calls and structures. However, there was a catch: I needed to implement this in both C and in Visual Basic. OK, big deal, right? Programmers have been calling the Win32 API from Visual Basic for years. But, the wrinkle that I ran into was that the structure that contains resource information aligns portions of the memory block on a 32-bit boundary. This makes the structure size unknown until runtime. This is no big deal in C, using pointers, but how do you access it from a language like Visual Basic that prefers to avoid pointers? Read on and Ill show you how I did it.
Defining the Interface
Since I think in C, I naturally solved the problem in C first, creating a function named GetDllVersion(). The C source for this function (and a simple demonstration program) is in VerRes.c (Listing 1), and it has the following prototype:
int GetDllVersion( const char* filename, char* loadpath, int maxloadpath, int &maj, int &min, int &rev, int &bld )
filename is a string containing the name of the file the caller wants to gather information about. This should be a PE format file of some kind: DLL, OCX, .exe, etc.
loadpath must point to a buffer of at least maxloadpath characters. GetDllVersion() will store in loadpath the path that LoadLibrary() would use to load the module named in filename.
maj, min, rev, and bld are the addresses where GetDllVersion() will store the version information it obtains for the target module. They represent, respectively: the major version, the minor version, the revision number, and the build number.
GetDllVersion() starts by attempting to load the specified file by calling Win32s LoadLibrary(). If LoadLibrary() cant locate the module, then GetDllVersion() returns FALSE. Otherwise, LoadLibrary() returns an instance handle to the loaded module. GetDllVersion() then calls GetModuleFileName(), which will supply the actual load path of the filename parameter that was found by LoadLibrary(). GetDllVersion() copies this path to the loadpath parameter so that the caller can use it later.
The Version Resource
Once you have a loaded modules handle, any resource in that module is available through various Win32 functions. I used LoadResource() to obtain a handle to the VS_VERSION_INFO resource, and LockResource() to obtain a pointer to that resource. The first parameter of LoadResource() is the name of the resource being requested. The second parameter is the RT_VERSION resource type. #1 means resource ID 1, which is the default number used when you add a version resource in Visual Studio. This seems to be the default ID for the version resource, so I went with it. This was determined empirically by loading a compiled file in Visual Studio as a resource. The VS_VERSION_INFO structure is defined in LoadVersion.cpp because it really isnt defined in windows.h, but if you do a help search in Developer Studio it will seemingly appear as a valid structure. The reason it isnt defined in the header files is because this structure is dynamic - its length isnt known until you start to traverse it. The first four elements are fixed in size. However, the structure I am really interested in, VS_FIXEDFILEINFO, is located right after the Padding1. The padding parameter is defined in MSDN as: Contains as many zero words as necessary to align the Value member on a 32-bit boundary, which makes its length unknown.
What exactly does the MSDN definition mean? It means that the value or VS_FIXEDFILEINFO structure is on a DWORD boundary in memory. An item that is DWORD aligned means that the structure is located at an offset that is a multiple of four bytes. For example: valid DWORD aligned addresses might be: 0x0000, 0x0004, 0x0008, 0x0010, etc. Unaligned accesses are illegal on some hardware architectures, but merely slower on 80x86 architectures.
Walking Along
To find the VS_FIXEDFILEINFO structure, I first assign a byte pointer to the Padding1 member:
char * p = (char *) pInf->Padding1;
Next, I increment this pointer in byte increments until it is aligned on a DWORD boundary. This is accomplished in the while condition: (((long)p)&0x04) != 0). As I walk along one byte at a time, I continue to refresh a VS_FIXEDFILEINFO pointer so that when the while condition fails, I will have a memory pointer ready to dereference the version information. The last step is to extract the version numbers and assign them to the output parameter passed by the callee.
Visual Basic Implementation
I have to admit that Visual Basic is the easiest and most robust ActiveX container application development platform that I know. When I write ActiveX components, I always test them with Visual Basic. This is why I needed this algorithm implemented in Visual Basic and because many of my front-end user interfaces are written in Visual Basic. Setting up a pointer and traversing a memory structure is simple to do in C because of its loose type casting. It was trivial for me to simply assign my character pointer to a VS_FIXEDFILEINFO structure by typecasting. This is a very powerful tool to the C programmer (a tool that has its dark side as well,) and the C compiler makes no judgments on my intentions. Visual Basic has stronger type casting, and worst of all, it has no pointer types.
I have implemented the exact algorithm from the description above in Visual Basic in VerRes.Bas (Listing 2). All of the API calls are the same, but the Visual Basic implementation has a few exceptions. Once the version resource is locked, the memory pointer is assigned to a long data type. This works out well to do the search for the 32-bit boundary as well.
The difficulty is in extracting the version information once I have located it. I have a long data type that is pointing to my VS_FIXEDFILEINFO, but since I cant dereference a long, Im pretty much beat, right? Not really. All I need to do is copy Len(VS_FIXEDFILEINFO) bytes from the memory pointed to by my long pointer into a Visual Basic type of VS_FIXEDFILEINFO.
I have overloaded a Win32 function (RtlMoveMemory()) to a private function named CopyMemoryFromPointer() to do exactly that. I use the term overloaded because I really cant describe it in a better way: I play a trick on Visual Basic by using the ByVal source parameter as my long pointer and a ByRef destination parameter as my Visual Basic type variable. Once inside Win32s RtlMoveMemory(), the two parameters are treated as void pointers, and Win32 happily copies the data from the source data block to the destination. Upon return, I can now dereference the VS_FIXEDFILEINFO structure and extract the version number from the resource. This works well with any type variables. As long as I know the size so that I can create memory in Visual Basic to hold the variable, I can copy any one type to another using VarPtr() to get the address of a variable. Thats typecasting in Visual Basic!
Summary
Both the listings include main() subroutines that will allow them to be built as a test program. The C version can be built as a Win32 console application and takes the input filename as a command-line argument. The Visual Basic version also requires a filename command-line argument and will display a message box with the results of the run. The main() subroutines can easily be removed to incorporate each module into your own project.
Its worth noting that the next version of Visual Basic (.NET) has a variety of incompatibilities. VarPtr() is one of the features that Microsoft has removed from the language and labeled a legacy feature. You can read more about these incompatibilities at: msdn.Microsoft.com/library/techart/vb6tovbdotnet.htm.
Jim Gentilin is Director of Sofware Engineering at OmniTester Corporation. Hes been programming professionally for 11 years and spends his free time as a husband, an electronics hobbyist, and as the guitarist for the Diablo Sandwich Band (www.diablosandwich.com) who regularly appears in clubs on the Jersey shore.