The question "How can I update the version information in my file?" usually yields a handful of solutions. Everyone has their favorite. Some are generic, while others are specific to a particular environment. This type of problem is one of the reasons that the "There's more than one way to skin a cat" idiom exists.
Back when I was still using the 16-bit compiler (v1.52c), the solution I put in place required the version information structure in the .rc file to be modified to look something like:
#include <span class="code-string">"version.h"
The version.h file contained a whole slew of
#define directives, a pair for each module. I then put together a little utility that ran during the automated build process that would open the version.h file, find the right
#define directives, and increment them accordingly. This solution worked well, with little to no user interaction. For some reason, when our development team moved to the 32-bit compiler, version numbers, or at least the auto-incrementing of them, moved to the back burner. It wasn't that they were any less important, but was because we only had a small handful of 32-bit modules and it was more efficient to change the version numbers manually than to re-tool the utility to work with the 32-bit compiler's way of processing the .rc file. Maybe the .rc2 file could have been used. I give VB a thumbs-up for having this functionality in its IDE.
When the question is coupled with "at runtime rather than compile time", the solution becomes a tad more complicated. Such a question came across my viewport the other day.
You've no doubt used the suite of version-related functions to retrieve file version, description, copyright, platform, etc. from a file. Most have even seen the handy class,
CModuleVersion, put together by Paul DiLascia back in 1998 to read from both new and old files. Knowing that the
VERSIONINFO resource was stored in a file in an easy-to-retrieve location (we can use
GetFileVersionInfo() for this or go Matt Pietrek's way (an up-to-date article is here) and navigate the PE file structure manually!), it only makes sense that it should be easy to update. The remainder of this article is my attempt at just that.
The first thing we need to do is get a pointer to the start of the version information block. This is done with:
LPCTSTR lpszFilename = _T("c:\\winnt\\system32\\atl.dll");
dwSize = GetFileVersionInfoSize(lpszFile, &dwHandle);
if (0 < dwSize)
LPBYTE lpBuffer = new BYTE[dwSize];
GetFileVersionInfo(lpszFile, 0, dwSize, lpBuffer);
delete  lpBuffer;
At this point,
lpBuffer now points to the start of the version information block, a
VS_VERSIONINFO structure actually. In my example, that address is 0x007b25a8. From
dwSize, I know that the block is 1700 bytes in size. From my tests, it seems that the compiler allocates more room for the version information block than is actually consumed by the data. Maybe, this is for performance reasons, or to ensure that boundaries are aligned following the version information block. It may also be that
GetFileVersionInfoSize() is just overly cautious!
This image shows some of the version information block. What's outlined in red is the data that will be updated.
Next, we get to do a little pointer arithmetic. You'll note that the
VS_VERSIONINFO structure contains a
VS_FIXEDFILEINFO member structure. No big deal except that it follows a variable-length member and some padding bytes. The number of characters in the
szKey member of
VS_VERSIONINFO rounded to the nearest 4-byte boundary is where the
VS_FIXEDFILEINFO member actually starts. So, if the
szKey member contained 15 characters (usually "
VS_VERSION_INFO"), plus 1 for the
NULL character, the number of bytes offset from the beginning of the version information block would be calculated as:
VS_VERSIONINFO *pVerInfo = (VS_VERSIONINFO *) lpBuffer;
LPBYTE pOffsetBytes = (BYTE *) &pVerInfo->szKey[_tcslen(pVerInfo->szKey) + 1];
VS_FIXEDFILEINFO *pFixedInfo =
(VS_FIXEDFILEINFO *) roundpos(pVerInfo, pOffsetBytes, 4);
After rounding to the nearest 4-byte boundary, we get 28. So,
pFixedInfo starts 28 bytes after the beginning of the version information block, or address 0x007b25d0.
roundpos() is a macro that handles the rounding. In this case, it uses a 4-byte boundary. Consult the source code for the actual implementation.
We're now ready to increment the version-related members of the
VS_FIXEDFILEINFO structure. Just for demonstration purposes, I incremented each word by one using:
pFixedInfo->dwFileVersionMS = pFixedInfo->dwFileVersionMS + 0x00010001;
pFixedInfo->dwFileVersionLS = pFixedInfo->dwFileVersionLS + 0x00010001;
pFixedInfo->dwProductVersionMS = pFixedInfo->dwProductVersionMS + 0x00010001;
pFixedInfo->dwProductVersionLS = pFixedInfo->dwProductVersionLS + 0x00010001;
This was so I could tell that everything was working when I viewed the "before" and "after" files (see the pictures at the top of this article). At this point, we have an in-memory snapshot of an updated version information block. It's time to commit it to disk. Using the resource-related functions, we have:
HANDLE hResource = BeginUpdateResource(lpszFile, FALSE);
if (NULL != hResource)
(LPVOID *) &lpTranslate,
&uTemp) != FALSE)
dwSize) != FALSE)
Note the comment about using
LANG_NEUTRAL/SUBLANG_NEUTRAL as the fourth parameter to
UpdateResource(). The section of the version information block that we updated was independent of language and code page, so we could probably get away without having to call
VerQueryValue() to get the translation array.
At this point, the file's version information has been updated. However, there remains but one problem. Can you spot it? The corresponding version numbers in the file's .rc file remains unchanged. The next time the file is built, the previous numbers will be used. I guess that's one reason for updating the .rc file rather than the compiled file.
The above code snippets focused on the
VS_FIXEDFILEINFO section of the file. This is somewhat easier to do because the information contained within it is, well, fixed. The information contained in the
StringFileInfo section is not necessarily more difficult to change, but it does have the constraint that the new information be the same size or smaller than the old information. In other words, if I wanted to change the string "Copyright 2004" with an updated string of "Copyright 2004-05" the results would not be pretty since the string is three characters longer. Those extra characters would inevitably write on something important.
The first thing we need to do is get a pointer to one of the sub-blocks in the
StringFileInfo section. We do this using
VerQueryValue(lpBuffer, (LPTSTR) ((LPCTSTR) strSubBlock),
(LPVOID *) &pValueBuffer, &uTemp);
At this point, the variable
pValueBuffer is pointing to the CompanyName key. Whatever change I make to this value must be the same length or smaller and end with a '
\0' character. So you can either right-pad the new string with '
\0' characters or zero the whole string out and then update it. For the latter, I used:
ZeroMemory(pValueBuffer, _tcslen(pValueBuffer) * sizeof(TCHAR));
_tcscpy(pValueBuffer, _T("My Company, Inc."));
You can watch this transition if you have the Memory window open in the IDE. At this point,
EndUpdateResource() can be called as shown above.
Two of the language-specific strings that relates to this are FileVersion and ProductVersion. They're contained in the
Children member of the
StringTable structure and are dependent on language and code page. The updating of those is left as an exercise for the reader, or the author if time and interest permits.
Thanks goes out to Ted Peck for supplying the two macros I used in this sample.