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 |
||||||||||
|
|
|
|
||||||||
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.
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.
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...
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
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.
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?
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.
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...