Introduction
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 "version.h"
VS_VERSION_INFO VERSIONINFO
FILEVERSION _FILEVERSION_
PRODUCTVERSION _PRODUCTVERSION_
...
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.
Implementation
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");
DWORD dwHandle,
dwSize;
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)
{
UINT uTemp;
if (VerQueryValue(lpBuffer,
_T("\\VarFileInfo\\Translation"),
(LPVOID *) &lpTranslate,
&uTemp) != FALSE)
{
if (UpdateResource(hResource,
RT_VERSION,
MAKEINTRESOURCE(VS_VERSION_INFO),
lpTranslate->wLanguage,
lpBuffer,
dwSize) != FALSE)
{
EndUpdateResource(hResource, 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.
Updating the StringFileInfo
section
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()
like:
CString strSubBlock;
LPTSTR pValueBuffer;
strSubBlock.Format(_T("\\StringFileInfo\\%04x%04x\\CompanyName"),
lpTranslate->wLanguage,
lpTranslate->wCodePage);
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, BeginUpdateResource()
, UpdateResource()
, and EndUpdateResource()
can be called as shown above.
Notes
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.
References
Thanks goes out to Ted Peck for supplying the two macros I used in this sample.
Enjoy!