April 1998
Download Apr98CQAcode.exe (22KB)
Paul DiLascia is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992) and a freelance consultant and
writer-at-large. He can be reached at askpd@pobox.com
or http://pobox.com/~askpd.
|
Q How can I tell which version of a particular module (DLL) is installed on my system? I am trying to figure out which version of comctl32.dll is installed. I have seen code that calls GetProcAddress to try getting various functions like InitCommonControlsEx to determine the version based on which functions are present. This seems kludgy to me. What is the right way to get the version?
A Well, there are two ways: the easy way and the hard way. The easy way is to call a special new entry DllGetVersion. The only problem is, while comctl32.dll supports this function, not all DLLs have it. If there's no DllGetVersion, you have to use the hard waynamely, the FileVersion API, which is perhaps one of the most confusing APIs you will ever come across.
|
Figure 1 VersionDlg in action |
The fields are mostly self-explanatory: dwPlatformID is DLLVER_PLATFORM_WINDOWS (value = 1) for Windows® 95, or DLLVER_PLATFORM_NT (value = 2) for Windows NT®. Once you have DLLVERSIONINFO defined, you can call DllGetVersion, which has the
following signature: Since DllGetVersion may not exist in any given DLL, you have to do the standard thing, calling GetProcAddress and handling the case of a NULL return. I wrote a CModuleVersion class with a DllGetVersion function that encapsulates all the details (see ModulVer.cpp in Figure 2). To use CModuleVersion, you can write:
DllGetVersion is a relatively new thing that works fine for comctl32, which implements DllGetVersionbut what about DLLs that don't? For example, shell32.dll doesn't implement DllGetVersion, as Figure 3 shows. For such
modules, you have to use the dreaded and bizarre GetFileVersionInfo and
VerQueryValue functions defined
in winver.h.
|
Figure 3 No DllGetVersion Info |
|
Figure 5 Version Tab |
The version APIs are quite confusing, as you will soon find out, but CModuleVersion makes it easy. CModuleVersion is derived from VS_FIXEDFILEINFO (see Figure 6), the structure that contains the "fixed" part of the version info, which includes the major and minor version numbers and other stuff that's in DLLVERSIONINFO. To use CModuleVersion, all you have to do is write: |
|
To access the variable information like CompanyName and whatever else the author of the module might have put in it, you can use another function, CModuleVersion:: GetValue. For example, after the following code executes, sCompanyName will be something like "XYZ Corp" or "Acme Corporation": |
|
CModuleVersion hides all the grungy work of getting the informationand believe me, it's grungy! If all you want to do is use CModuleVersion, you can stop now; if you want to know how it works, read on.
Assuming CModuleVersion::GetFileVersionInfo can load the module and get an HINSTANCE, it calls ::GetFileVersionInfoSize to find out how big the version info is, then allocates a buffer and calls GetFileVersionInfo to fill it. The raw buffer (CModuleVersion::m_pVersionInfo) is a chunk of data that contains both fixed and variable information. VerQueryValue sets a pointer to the start of whatever particular information you're interested in. For example, to get the fixed info (VS_FIXEDFILEINFO), you must write: |
|
buf is the full information returned from GetFileVersion-Info. The string "\" ("\\" in C), is the root of the information if you think of it as a directory (shades of the Registry). VerQueryValue sets lpvi to the start of the VS_FIXEDFILEINFO and iLen to its length.
So much for getting the fixed info. Getting the variable info is even stranger, because first you need to know what the language ID and code page are. In Windows, a code page specifies a character set, which is a mapping between character glyphs and 1 or 2-byte values that represent them. The standard ANSI code page is 1252; Unicode is 1200. Figure 7 lists the language IDs and code pages. The Translation key in the file info in Figure 4 specifies the module's language ID and code page. I use my own TRANSLATION struct to get this information in CModuleVersion. |
|
To retrieve the language information, CModuleVersion uses VerQueryValue with \VarFileInfo\Translation as the key. |
|
Once you know the language ID and code page, you can get the variable information like CompanyName and InternalName. To do it, you must build a query in the form |
|
where <langID> is the language ID in ASCII hex (0409 for US English), <codepage> is the codepage in similar format (04e4 for 1252, the ANSI code page), and <keyname> is the key you want, like CompanyName. To build the query, you need to use sprintf or CString:: Format to build a string like |
|
then feed this string to VerQueryValue. If it all seems terribly confusing and a bit like witchcraft, don't worryit is. Fortunately, CModuleVersion::GetValue hides all this nonsense, so all you have to write is: |
|
With CModuleVersion implemented, VersionDlg is a piece of cake. It's a simple dialog with an edit field for the module name and an ON_EN_CHANGE handler named CVersionDialog::OnChangedModule, which MFC calls whenever the user types something into the module name field. OnChangedModule uses CModuleVersion to get the version info using both GetFileVersionInfo and GetDllVersion, then it displays the information, if any, in two static text fields in the dialog. Easy.
There's one final trick I should mention. GetFileVersionInfo, VerQueryValue, and the other file version functions live in a library calledwhat elseversion.lib, with which you must link. To avoid annoying "undefined symbol" errors during linking, ModuleVer.h uses the little-known but useful #pragma comment syntax to make your project link with version.lib even if you forget to add it to the Input Libraries list on the Link page in your Project Settings. |
|
Now, many of you may be wondering why all this is so important and who ever needs to look at this stuff anyway? Typically, you only need to get the variable strings like CompanyName and LegalCopyright if you're writing some kind of tool that displays file properties. But you might find it useful to use CModuleVersion to extract the file info from your own app, for example, to display the version info in an About dialog or splash screen. If you use CModuleVersion, you only have to modify the version info in its one rightful placethe resource fileand the dialog or splash screen will show the current info automatically.
Another important use of the version info is to find out which language a particular DLL is written for, so your code can behave appropriately. In the rapidly evolving world of Windows-based programming, where new versions of DLLs ship by the day, you'll soon find yourself more and more often writing code that looks like this: |
|
It's just a sad fact of life, and one reason I suspect the Redmondtonians introduced DllGetVersion as a quick way to get the version number without having to confront the dreaded GetFileVersionInfolet alone deal with language IDs and code pages (which are really only needed for the variable stuff like CompanyName).
It's no accident you asked in particular about comctl32.dll. This module has become one of the biggest banes to programmers, at least as far as versionitis goes. My poor email box has been flooded with questions lately from readers whose programs have broken because some free download from Microsoft put a new version of comctl32.dll on their customers' machines. Next month, I'll try to explain what's with all the versions of comctl32.dll, some of the new toolbar features, and how to circumvent bugs with MFC's CToolBar. For now, all I have room to say is that in case you didn't notice, there's an even newer version of comctl32.dll than version 4.71 (which shipped with Microsoft® Internet Explorer 4.0)namely, 4.72. And finally, praise the Lord, Microsoft has a policy for redistributing comctl32.dll with your app! You can't distribute comctl32 by itself, but you can distribute it as part of an update packet that installs other files as well. For the full poop, see http://msdn.microsoft.com/developer/downloads/files/ 40comupd.htm. Read it now, before a new version comes out! |
|
From the April 1998 issue of Microsoft Systems Journal.