Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / MFC
Article

Updating version information at run-time

Rate me:
Please Sign up or sign in to vote.
4.63/5 (14 votes)
14 Jul 20056 min read 232.8K   3.1K   45   89
Updating version information at run-time rather than at design-time.

Sample Image - UpdateVersion.jpg

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;

// determine the size of the resource information
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.

Sample screenshot

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:

// 'point to' the start of the version information block
VS_VERSIONINFO *pVerInfo = (VS_VERSIONINFO *) lpBuffer;

// the fixed section starts right after the 'VS_VERSION_INFO' string
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;

    // get the language information
    if (VerQueryValue(lpBuffer,
                      _T("\\VarFileInfo\\Translation"),
                      (LPVOID *) &lpTranslate,
                      &uTemp) != FALSE)
    {
        // could probably just use LANG_NEUTRAL/SUBLANG_NEUTRAL
        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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) Pinnacle Business Systems
United States United States

The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.

HTTP 404 - File not found
Internet Information Services

Comments and Discussions

 
QuestionUsage of the code Pin
ayeshakdhar3-Mar-21 23:24
ayeshakdhar3-Mar-21 23:24 
Question&lpTranslate => is not defined Pin
sati9925-Feb-16 5:45
sati9925-Feb-16 5:45 
AnswerRe: &lpTranslate => is not defined Pin
David Crow25-Feb-16 5:50
David Crow25-Feb-16 5:50 
Download the source code and you'll see the definition of lpTranslate.

"One man's wage rise is another man's price increase." - Harold Wilson

"Fireproof doesn't mean the fire will never come. It means when the fire comes that you will be able to withstand it." - Michael Simmons

"You can easily judge the character of a man by how he treats those who can do nothing for him." - James D. Miles


GeneralRe: &lpTranslate => is not defined Pin
sati9925-Feb-16 20:15
sati9925-Feb-16 20:15 
AnswerRe: &lpTranslate => is not defined Pin
Member 1409910024-Dec-18 1:04
Member 1409910024-Dec-18 1:04 
GeneralUpdateresource makes the DLL,Exe unuseable Pin
fr220117-May-10 20:57
fr220117-May-10 20:57 
Generalwhy not writing a new exe Pin
ziopino705-Apr-10 16:07
ziopino705-Apr-10 16:07 
QuestionHow I can convert this project to Multibyte? Pin
M8R-3d3rjt127-Dec-09 14:03
M8R-3d3rjt127-Dec-09 14:03 
QuestionHi, why didn't U using following code to get pFixedInfo? Pin
Icepool23-Sep-09 16:44
Icepool23-Sep-09 16:44 
AnswerRe: Hi, why didn't U using following code to get pFixedInfo? Pin
David Crow24-Sep-09 2:51
David Crow24-Sep-09 2:51 
GeneralRe: Hi, why didn't U using following code to get pFixedInfo? Pin
Icepool24-Sep-09 15:04
Icepool24-Sep-09 15:04 
QuestionRe: Hi, why didn't U using following code to get pFixedInfo? Pin
David Crow24-Sep-09 15:40
David Crow24-Sep-09 15:40 
GeneralRe: Hi, why didn't U using following code to get pFixedInfo? Pin
Icepool24-Sep-09 16:29
Icepool24-Sep-09 16:29 
GeneralResourceLib: same functionality that supports adding/removing/editing version and string resources Pin
dB.8-Oct-08 12:22
dB.8-Oct-08 12:22 
General64 bit version Pin
umeca7426-Sep-08 21:16
umeca7426-Sep-08 21:16 
QuestionIt doesn't work when the file is ocx or arx format? Pin
malj9-Sep-08 22:03
malj9-Sep-08 22:03 
AnswerRe: It doesn't work when the file is ocx or arx format? Pin
David Crow10-Sep-08 4:19
David Crow10-Sep-08 4:19 
AnswerRe: It doesn't work when the file is ocx or arx format? Pin
malj10-Sep-08 23:29
malj10-Sep-08 23:29 
QuestionUpdate of StringFileInfo entry doesn't work. Can you help ? Pin
cherbuliez21-Sep-07 6:27
cherbuliez21-Sep-07 6:27 
AnswerRe: Update of StringFileInfo entry doesn't work. Can you help ? Pin
David Crow21-Sep-07 7:30
David Crow21-Sep-07 7:30 
QuestionRe: Update of StringFileInfo entry doesn't work. Can you help ? Pin
cherbuliez23-Sep-07 23:57
cherbuliez23-Sep-07 23:57 
QuestionRe: Update of StringFileInfo entry doesn't work. Can you help ? Pin
David Crow24-Sep-07 2:57
David Crow24-Sep-07 2:57 
AnswerRe: Update of StringFileInfo entry doesn't work. Can you help ? Pin
BhawnaA26-Sep-07 1:58
BhawnaA26-Sep-07 1:58 
GeneralRe: Update of StringFileInfo entry doesn't work. Can you help ? Pin
cherbuliez26-Sep-07 5:26
cherbuliez26-Sep-07 5:26 
QuestionIs it possible to have more information Blocks (Not language) ? Pin
zmau19-Jun-07 9:14
zmau19-Jun-07 9:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.