What can I do if I don’t want my file version number to be a sequence of four integers?

Raymond Chen

In the Windows file version resource, the file version number is given as a sequence of four 16-bit integers. Here’s the prototype file version resource given on docs.microsoft.com:

#define VER_FILEVERSION             3,10,349,0
#define VER_FILEVERSION_STR         "3.10.349.0\0"

#define VER_PRODUCTVERSION          3,10,0,0
#define VER_PRODUCTVERSION_STR      "3.10\0"

#ifndef DEBUG
#define VER_DEBUG                   0
#else
#define VER_DEBUG                   VS_FF_DEBUG
#endif

VS_VERSION_INFO VERSIONINFO
FILEVERSION     VER_FILEVERSION
PRODUCTVERSION  VER_PRODUCTVERSION
FILEFLAGSMASK   VS_FFI_FILEFLAGSMASK
FILEFLAGS       (VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG)
FILEOS          VOS__WINDOWS32
FILETYPE        VFT_DLL
FILESUBTYPE     VFT2_UNKNOWN
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904E4"
        BEGIN
            VALUE "CompanyName",      VER_COMPANYNAME_STR
            VALUE "FileDescription",  VER_FILEDESCRIPTION_STR
            VALUE "FileVersion",      VER_FILEVERSION_STR
            VALUE "InternalName",     VER_INTERNALNAME_STR
            VALUE "LegalCopyright",   VER_LEGALCOPYRIGHT_STR
            VALUE "LegalTrademarks1", VER_LEGALTRADEMARKS1_STR
            VALUE "LegalTrademarks2", VER_LEGALTRADEMARKS2_STR
            VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR
            VALUE "ProductName",      VER_PRODUCTNAME_STR
            VALUE "ProductVersion",   VER_PRODUCTVERSION_STR
        END
    END

    BLOCK "VarFileInfo"
    BEGIN
        /* The following line should only be modified for localized versions.     */
        /* It consists of any number of WORD,WORD pairs, with each pair           */
        /* describing a language,codepage combination supported by the file.      */
        /*                                                                        */
        /* For example, a file might have values "0x409,1252" indicating that it  */
        /* supports English language (0x409) in the Windows ANSI codepage (1252). */

        VALUE "Translation", 0x409, 1252

    END
END

But what if your file versioning scheme isn’t major.minor.build.revision? For example, maybe your file versioning sceheme is major.minor.patch? Can you just leave out the fourth value?

// Does this work?
#define VER_FILEVERSION             3,10,349
#define VER_FILEVERSION_STR         "3.10.349\0"

If you try this, you’ll find that many tools show the version as 3.1.349.0. Where is the extra .0 coming from?

The file version information is recorded in two formats in the version resource. There is a binary machine-parseable version, and there is a string human-readable version. The tools are probably getting the machine-parseable version, which takes the form of a VS_FIXED­FILE­INFO structure.

typedef struct tagVS_FIXEDFILEINFO {
  DWORD dwSignature;
  DWORD dwStrucVersion;
  DWORD dwFileVersionMS;
  DWORD dwFileVersionLS;
  DWORD dwProductVersionMS;
  DWORD dwProductVersionLS;
  DWORD dwFileFlagsMask;
  DWORD dwFileFlags;
  DWORD dwFileOS;
  DWORD dwFileType;
  DWORD dwFileSubtype;
  DWORD dwFileDateMS;
  DWORD dwFileDateLS;
} VS_FIXEDFILEINFO;

The file version number is represented in the form of two 32-bit integers dwFileVersionMS and dwFileVersionLS, and the traditional interpretation of these values is to break it down as follows:

dwFileVersionMS dwFileVersionLS
3
1
1
6
1
5
0
3
1
1
6
1
5
0
major minor build revision

Technically, it’s just a 64-bit number, and you could try to get the word out to the entire industry that, “For my app, please don’t break it down into four 16-bit integers. Instead, break it down into three 16-bit integers, and ignore the last one. Thanks. Love ya!”

You may even succeed at convincing some tools vendors to go along with your special rule. But you’re unlikely to convince all of them.

You can put whatever 64-bit value you like, but you have to accept that tools are going to parse it as if it were four 16-bit integers. You can tell your customers, “If you try to view the file version information, many tools will show it in four parts rather than three. You can ignore the last part, the one with the trailing .0.”

There’s a bit of an escape hatch: The VER_FILEVERSION_STR. You can set this string to anything you like. By convention, it’s a human-readable version of the binary file information, but there is no enforcement.

// Don't show the final .0 to the user. We don't use it.
#define VER_FILEVERSION_STR         "3.10.349\0"

You can even put extra bonus information in there if you like.

#define VER_FILEVERSION_STR         "3.10.349 (prerelease)\0"

Most tools will also show this string to the user, although it carries no formal meaning.

7 comments

Comments are closed. Login to edit/delete your existing comments

  • Jan Ringoš 2

    As Windows have gone wild with increasing build number in the past years, I’ve been curious (as Windows binaries themselves use the very same format): We are already at 25352 (publicly), maximum being 65535… are there any thoughts on what then?

    Is everyone betting on virtualization solving that somehow? Virtualizing new OS that won’t employ today’s EXEs/DLLs directly? Or just that it won’t be their problem?

    • Evgeny Vrublevsky (VEG) 1

      Yeah, I also have the same question in mind. It’s not just version info in files, there is also USHORT OSBuildNumber field in the PEB structure. A compatibility issue is emerging.

      • Jan Ringoš 0

        I’m not that worried about these ones. It will likely be solved by setting it to fixed value of 0xFFFF meaning “read actual value from some other DWORD.” Also OSVERSIONINFO, OSVERSIONINFOEX and RtlGetNtVersionNumbers all return DWORDs already.

        But the binary format of version info resources would need to change very soon, so that third-party tools, that are used regularly, can adapt before build numbers start reaching that value. Still, many of those tools are no longer maintained, so that’ll be interesting.

  • Kuanlan Marswer 2

    In my opinion, Microsoft has been handling version numbers quite terrible. For example, Windows version number has become increasingly confusing. Nowadays, Windows systems no longer use the major and minor parts (frozen at 10.0), and consumers/developers/business need to remember and recognize COMPLEX build and revision numbers. Another example is C++/WinRT version 2.0.230225.1, I don’t understand how its build part is stored in 16bits.
    Microsoft should take version numbers seriously, I recommend Semantic Versioning 2.0.0

    • Me Gusta 0

      Well, as far as how the C++/WinRT build number is stored in 16 bits, to put it simply, it isn’t. If you look at the resource version for cppwinrt.exe, it is 2.0.0.0. But since this is only talking about the executable version resource, aka the file version, this isn’t talking about the version in general.

  • Greg Lolo 2

    What will happen if I don’t end strings with \0? I haven’t found any documentation which says that strings in StringFileInfo need to be explicitly NUL-terminated, only examples that do so. On the other hand, the STRINGTABLE examples do not end strings with \0.

    • Kevin Puetz 3

      As far as I can tell, VerQueryValue will just return whatever’s there in the compiled resource (or unicode/ANSI conversion). Which is fine, VerQueryValue never said it would be null-terminated, and instead includes an `[out] PUINT puLen` returning the length to the caller.

      As far as I’ve ever been able to determine, rc seems to truncate the string itself at the first \0 in your .rc source file, and always writes exactly one (unicode) NULL to the actual compiled resource regardless. I get a byte-for-byte identical .res file whether the .rc source code has VALUE “Name”, “…” or “…\0” or “…\0\0\0\0\0” – all give a .res with one null terminator. It’s like this at least as far back as rc 5.00.1641.1 – Build 1641 (VC98/Visual Studio 6 SP6), 5.2.3690.0 (Visual Studio 2005), and 5.2.3790.0 (Windows SDK Update Feb 2003), which are the only ones I still had old VMs handy to check with.

      The ATL project wizard in VC++6.0 did include a trailing “…\0” on each entry in StringFileInfo, but removing it doesn’t change the generated .res file at all. VS2005’s wizard no longer included the \0, it was just VALUE “Name”, “Value”. So whatever this old pattern is scar tissue from (presumably at some point rc did *not* terminate the strings?), it seems to be at least 20 years past being necessary…

Feedback usabilla icon