Environment: Visual Studio 6.0. Uses STL.
Windows has a built-in version concept, and it’s a good idea to make use of this by version stamping all your binary modules. By doing this, you gain the following advantages:
- Users can easily identify the version of your program that they are using.
- When you load a module, you can verify its version much more reliably with a version stamp than using a date stamp.
- Some Windows components, such as the Windows Installer, use the version stamp to secure against overriding a newer module with an older one.
A module in this article refers to either an executable or a dll.
Why Is a Version Stamp Better than a Date Stamp?
Because a date stamp gets changed and updated after build time. For instance, if your application gets written to a CD, the date stamp is whatever the burner software assigns at write time. If a user downloads a module from the Internet or extracts it from a zip file, the date stamp marks the time the file was created through one of these actions. Various utilities also touch the date stamp. You could potentially have a newer build from older sources. In short, there is no correlation between the date stamp and the sources comprising the build.
A version stamp, on the other hand, can tell you exactly when that module was built. It also can capture some other information, such as what the original filename was, in case the module gets renamed. It contains company and legal information as well.
Version Stamp Anatomy
The main part of the version stamp is the version itself. There are four parts to the version. A sample version looks like this: 2.1.3.37
.
A version resource in Windows has twelve fields altogether. Examples are “CompanyName,” “FileDescription,” and “LegalCopyright.” There are two version fields, one for the file and one for the product. These often will be the same, but can be different when you release patches for a product. For instance, say your product consists of an executable and two DLLs. If you release a version of the product, and later release an update to one of the DLLs, that module will have a different file version number from the others.
Assigning a Build Number
There are different philosophies on how to assign build numbers. One approach, described in the Microsoft Support Article On Auto Incrementing Build Numbers, is to increase the build number every time the build script executes. The advantage to this approach is that each build is guaranteed to have a unique build number. The disadvantage is that there is no correlation between a build number and when the build was performed. This is more than a hypothetical situation because builds are performed rarely at the beginning of the development cycle, more frequently towards the end.
Another approach is to make the build number be determined by when the build was done. A typical way to do this is by counting the number of days from a given day—for instance, the project’s start—to the day of the build. The advantage to this approach is that for any given version, a support rep or engineer can determine when that build was done. The disadvantage is that multiple builds on the same day will get the same build number.
Personally, I prefer the latter approach. After building software for twelve years, I have run into having to perform separate builds on the same day twice. (Performing the same build more than once should result in the same build number). In neither case was it a big problem. Checking in a version of the Resource Script with zero as the build number, then having the official build script assign the number each time, also allows users, engineers, and others to distinguish between official builds and private builds done by developers.
Usage
Adding a version stamp to any module, whether a DLL or executable, is simple. A version stamp is a type of resource. To add the version information, use “Insert/Resource,” and choose the bottom entry in the resource type list: Version. This will insert a default version stamp. In your resource script, it will be called “VS_VERSION_INFO;” do not change this name.
A user can examine the version information from the Explorer. By right-clicking the module, choosing “Properties” from the context menu, and then clicking the “Version” tab, the version stamp is presented in the properties window.
If a module does not have a version stamp, the “Version” tab does not appear in the Explorer properties dialog.
Following is a sample of the version resource in the RC file. It corresponds to the version properties window displayed below. You would rarely need to edit the version resource directly.
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,1,0,249
PRODUCTVERSION 1,1,0,249
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK “StringFileInfo”
BEGIN
BLOCK “040904b0”
BEGIN
VALUE “Comments”, “\0”
VALUE “CompanyName”, “Hewlett-Packard Company\0”
VALUE “FileDescription”, “HPVersioner\0”
VALUE “FileVersion”, “1, 1, 0, 249\0”
VALUE “InternalName”, “HPVersioner\0”
VALUE “LegalCopyright”, “Copyright ) 2003\0”
VALUE “LegalTrademarks”, “\0”
VALUE “OriginalFilename”, “HPVersioner.exe\0”
VALUE “PrivateBuild”, “\0”
VALUE “ProductName”, “HPVersioner — Commandline
Version Markup Utility\0”
VALUE “ProductVersion”, “1, 1, 0, 249\0”
VALUE “SpecialBuild”, “\0”
END
END
BLOCK “VarFileInfo”
BEGIN
VALUE “Translation”, 0x409, 1200
END
END
To use this resource from your code, you access a VS_FIXEDFILEINFO structure. You don’t get this by the normal FindResource()/LoadResource()
API functions. Instead, use GetFileVersionInfo()
and VerQueryValue()
. To load the version information from a file, use the following lines:
DWORD dwLen, dwUseless;
LPTSTR lpVI;
std::string companyName;
UINT verMajor;dwLen = GetFileVersionInfoSize((LPTSTR)szFile, &dwUseless);
if (dwLen==0)
return 0;lpVI = (LPTSTR) GlobalAlloc(GPTR, dwLen);
if (lpVI)
{
DWORD dwBufSize;
VS_FIXEDFILEINFO* lpFFI;
BOOL bRet = FALSE;
WORD* langInfo;
UINT cbLang;
TCHAR tszVerStrName[128];
LPVOID lpt;
UINT cbBufSize;GetFileVersionInfo((LPTSTR)szFile, NULL, dwLen, lpVI);
if (VerQueryValue(lpVI, _T(“\\”),
(LPVOID *) &lpFFI, (UINT *) &dwBufSize)){
//We now have the VS_FIXEDFILEINFO in lpFFI
verMajor = HIWORD(lpFFI->dwFileVersionMS);
}
//Get the Company Name.
//First, to get string information, we need to get
//language information.
VerQueryValue(lpVI, _T(“\\VarFileInfo\\Translation”),
(LPVOID*)&langInfo, &cbLang);
//Prepare the label — default lang is bytes 0 & 1
//of langInfo
wsprintf(tszVerStrName, _T(“\\StringFileInfo\\
%04x%04x\\%s”),
langInfo[0], langInfo[1], _T(“CompanyName”));
//Get the string from the resource data
if (VerQueryValue(lpVI, tszVerStrName, &lpt, cbBufSize))
companyName.assign((LPTSTR)lpt); //*must* save this//Cleanup
GlobalFree((HGLOBAL)lpVI);
}
Notice that we don’t have to clean up after each call to VerQueryValue()
. We delete the lpVI pointer at the end. This also invalidates all the string values, so we must save the strings into other variables before we free lpVI.
Wrapper Class and HPVersioner Utility
Attached to this article are two useful downloads.
The first is a Version update utility, HPVersioner. It will find a VS_VERSION_INFO structure in your RC file and update it with the build number du jour. It follows the time-based approach, as described above. For information on the syntax and usage of HPVersioner, download the utility zip file and look at Readme.txt.
The other download is the sources for HPVersioner. This includes a class, CVersionInfo, that provides easy access to a version stamp. A CVersionInfo object can populate itself from a string (of the form “1.0.2.17”), an HMODULE, a VS_FIXEDFILEINFO, another CVersionInfo, and so forth. If instantiated from an HMODULE or a file, it populates three strings from the version stamp: ProductName, CompanyName, and LegalCopyright. Other strings will be easy to add.
The CVersionInfo constructor that takes a string will parse the string as a version info, such as “1.0.2.17”. This will not populate ProductName, CompanyName, or LegalCopyright. To load version info from a file, use the method SetFromFilename()
.
We generally expect version numbers to appear separated by the period, such as “1.2.1.23.” In the RC file, the version numbers are separated by comma, as in “1,2,1,23.” For this reason, CVersionInfo has the capability to render itself in either format. The former is called AsString()
; the latter is AsResource()
.
CVersionInfo contains assignment and comparison operators. This allows you to easily manipulate and compare versions of modules. You can also use the CVersionInfo class to easily display version stamp information in your About box or Splash screen.
Sample usage of the CVersionInfo class:
HINSTANCE hmodDll = LoadLibrary(_T(“MyAppMod.dll”));if (hmodDll)
{
CVersionInfo verApp(GetModuleHandle(NULL));
CVersionInfo verModule(hmodDll);//Don’t use the module if it’s older than the app
if (verModule < verApp)
{
FreeLibrary(hmodDll);
hmodDll = NULL;
}
}
Conclusion
Adding a version stamp to your modules is easy, and will allow you to perform robust versioning checks, display data about your modules to the user, and keeps the system informed about the sequence of your releases.