Date: | December 20, 2006 / year-entry #420 |
Tags: | history |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20061220-15/?p=28653 |
Comments: | 14 |
Summary: | I return to the extremely sporadic series on resources with a description of the version resource. You don't need to know how version resources are formatted internally; you should just use the version resource manipulation functions GetFileVersionInfo, VerQueryValue, and their friends. I'm providing this information merely for its historical significance. Version resources can be viewed... |
I return to the extremely sporadic series on resources
with a description of the version resource.
You don't need to know how version resources are
formatted internally;
you should just use the version resource manipulation functions
Version resources can be viewed as a serialized tree structure.
Each node of the tree has a name and associated data
(either binary or text),
and each node can have zero or more child nodes.
The root node is always named
The child nodes can appear in any order, and the strings
like
If you've used How does this tree get stored into a resource? It's actually quite simple. Each node is stored in a structure which takes the following form (in pseudo-C): struct VERSIONNODE { WORD cbNode; WORD cbData; CHAR szName[]; BYTE rgbPadding1[]; // DWORD alignment BYTE rgbData[cbData]; BYTE rgbPadding2[]; // DWORD alignment VERSIONNODE rgvnChildren[]; };
In words, each version node begins with a 16-bit value
describing the size of the nodes in bytes (including its children),
followed by a 16-bit value that specifies how many bytes
of data (either binary or text) are associated with the node.
(If the node contains text data, the count includes the null terminator.)
Next comes the null-terminated name of the node
and padding bytes to bring us back into
Since each of the children might themselves have children,
you can see how the tree structure "flattens" into this
serialized format.
To move from one node to its next sibling, you skip ahead
by
Let's take a look at the resources for the 16-bit
0000 E4 01 34 00 56 53 5F 56-45 52 53 49 4F 4E 5F 49 ..4.VS_VERSION_I 0010 4E 46 4F 00 BD 04 EF FE-00 00 01 00 0A 00 03 00 NFO............. 0020 67 00 00 00 0A 00 03 00-67 00 00 00 3F 00 00 00 g.......g...?... 0030 0A 00 00 00 01 00 01 00-02 00 00 00 00 00 00 00 ................ 0040 00 00 00 00 00 00 00 00-78 01 00 00 53 74 72 69 ........x...Stri 0050 6E 67 46 69 6C 65 49 6E-66 6F 00 00 64 01 00 00 ngFileInfo..d... 0060 30 34 30 39 30 34 45 34-00 00 00 00 27 00 17 00 040904E4....'... 0070 43 6F 6D 70 61 6E 79 4E-61 6D 65 00 4D 69 63 72 CompanyName.Micr 0080 6F 73 6F 66 74 20 43 6F-72 70 6F 72 61 74 69 6F osoft Corporatio 0090 6E 00 00 00 2A 00 16 00-46 69 6C 65 44 65 73 63 n...*...FileDesc 00A0 72 69 70 74 69 6F 6E 00-57 69 6E 64 6F 77 73 20 ription.Windows 00B0 53 68 65 6C 6C 20 6C 69-62 72 61 72 79 00 00 00 Shell library... 00C0 16 00 06 00 46 69 6C 65-56 65 72 73 69 6F 6E 00 ....FileVersion. 00D0 33 2E 31 30 00 00 00 00-1A 00 06 00 49 6E 74 65 3.10........Inte 00E0 72 6E 61 6C 4E 61 6D 65-00 00 00 00 53 48 45 4C rnalName....SHEL 00F0 4C 00 00 00 3B 00 27 00-4C 65 67 61 6C 43 6F 70 L...;.'.LegalCop 0100 79 72 69 67 68 74 00 00-43 6F 70 79 72 69 67 68 yright..Copyrigh 0110 74 20 A9 20 4D 69 63 72-6F 73 6F 66 74 20 43 6F t . Microsoft Co 0120 72 70 2E 20 31 39 38 31-2D 31 39 39 36 00 00 00 rp. 1981-1996... 0130 22 00 0A 00 4F 72 69 67-69 6E 61 6C 46 69 6C 65 "...OriginalFile 0140 6E 61 6D 65 00 00 00 00-53 48 45 4C 4C 2E 44 4C name....SHELL.DL 0150 4C 00 00 00 39 00 29 00-50 72 6F 64 75 63 74 4E L...9.).ProductN 0160 61 6D 65 00 4D 69 63 72-6F 73 6F 66 74 AE 20 57 ame.Microsoft. W 0170 69 6E 64 6F 77 73 28 54-4D 29 20 4F 70 65 72 61 indows(TM) Opera 0180 74 69 6E 67 20 53 79 73-74 65 6D 00 00 00 00 00 ting System..... 0190 1A 00 06 00 50 72 6F 64-75 63 74 56 65 72 73 69 ....ProductVersi 01A0 6F 6E 00 00 33 2E 31 30-00 00 00 00 14 00 04 00 on..3.10........ 01B0 57 4F 57 20 56 65 72 73-69 6F 6E 00 34 2E 30 00 WOW Version.4.0. 01C0 24 00 00 00 56 61 72 46-69 6C 65 49 6E 66 6F 00 $...VarFileInfo. 01D0 14 00 04 00 54 72 61 6E-73 6C 61 74 69 6F 6E 00 ....Translation. 01E0 09 04 E4 04 .... We start with the root node. 0000 E4 01 // cbNode (node ends at 0x0000 + 0x01E4 = 0x01E4) 0002 34 00 // cbData = sizeof(VS_FIXEDFILEINFO) 0004 56 53 5F 56 45 52 53 49 4F 4E 5F 49 4E 46 4F 00 // "VS_VERSION_INFO" + null terminator Notice that the size of the root node equals the size of the entire version resource. This is to be expected, of course, because the version resource is merely a serialization of the resource tree diagram. Since the string name (plus null terminator) happens to come out
to an exact multiple of four bytes, there is no need for padding
between the name and the binary data, which takes the form of a
0014 BD 04 EF FE // dwSignature 0018 00 00 01 00 // dwStrucVersion 001C 0A 00 03 00 // dwFileVersionMS = 3.10 0020 67 00 00 00 // dwFileVersionLS = 0.103 0024 0A 00 03 00 // dwProductVersionMS = 3.10 0028 67 00 00 00 // dwProductVersionLS = 0.103 002C 3F 00 00 00 // dwFileFlagsMask 0030 0A 00 00 00 // dwFileFlags 0034 01 00 01 00 // dwFileOS = VOS_DOS_WINDOWS16 0038 02 00 00 00 // dwFileType = VFT_DLL 003C 00 00 00 00 // dwFileSubtype 0040 00 00 00 00 // dwFileDateMS 0044 00 00 00 00 // dwFileDateLS The structure is also a multiple of 4 bytes in length, so no padding is necessary between the data and the child nodes. 0048 78 01 // cbNode (node ends at 0x0048 + 0x0178 = 0x01C0) 004A 00 00 // cbData (no data) 004C 53 74 72 69 6E 67 46 69 6C 65 49 6E 66 6F 00 // "StringFileInfo" + null 005B 00 // padding to restore alignment 005C // no data
The first child is the 005C 64 01 // cbNode (node ends at 0x005C + 0x0164 = 0x01C0) 005E 00 00 // cbData (no data) 0060 30 34 30 39 30 34 45 34 00 // "040904E4" + null terminator 0069 00 00 00 // padding to restore alignment 006C // no data The children of the language node are the strings. This is where all the goodies can be found. 006C 27 00 // cbNode (node ends at 0x006C + 0x0027 = 0x0093) 006E 17 00 // cbData 0070 43 6F 6D 70 61 6E 79 4E 61 6D 65 00 // "CompanyName" + null terminator 007C // no padding needed 007C 4D 69 63 72 6F 73 6F 66 74 20 43 6F 72 70 6F 72 61 74 69 6F 6E 00 // "Microsoft Corporation" + null terminator 0091 00 00 00 // padding to restore alignment
Notice that the padding bytes
are not counted in the 0094 2A 00 // cbNode (node ends at 0x0094 + 0x002A = 0x00BE) 0096 16 00 // cbData 0098 46 69 6C 65 44 65 73 63 72 69 70 74 69 6F 6E 00 // "FileDescription" + null terminator 00A8 // no padding needed 00A8 57 69 6E 64 6F 77 73 20 53 68 65 6C 6C 20 6C 69 62 72 61 72 79 00 // "Windows Shell library" + null terminator 00BE 00 00 // padding to restore alignment
All of these nodes have no children since we run out of
bytes in 00C0 16 00 // cbNode (node ends at 0x00C0 + 0x0016 = 0x00D6) 00C2 06 00 // cbData 00C4 46 69 6C 65 56 65 72 73 69 6F 6E 00 // "FileVersion" + null terminator 00D0 33 2E 31 30 00 // "3.10" 00D5 00 00 00 // padding to restore alignment 00D8 1A 00 // cbNode (node ends at 0x00D8 + 0x001A = 0x00F2) 00DA 06 00 // cbData 00DC 49 6E 74 65 72 6E 61 6C 4E 61 6D 65 00 // "InternalName" + null terminator 00E9 00 00 00 // padding to restore alignment 00EC 53 48 45 4C 4C 00 // "SHELL" + null terminator 00F2 00 00 // padding to restore alignment 00F4 3B 00 // cbNode (node ends at 0x00F4 + 0x003B = 0x12E) 00F6 27 00 00F8 4C 65 67 61 6C 43 6F 70 79 72 69 67 68 74 00 // "LegalCopyright" + null terminator 0107 00 // padding to restore alignment 0108 43 6F 70 79 72 69 67 68 74 20 A9 20 4D 69 63 72 6F 73 6F 66 74 20 43 6F 72 70 2E 20 31 39 38 31 2D 31 39 39 36 00 // "Copyright © Microsoft Corp. 1981-1996" // + null terminator + another null terminator? 012F 00 // padding to restore alignment Wait a second, what's that "another null terminator"?
if you count the bytes, you'll see that the VALUE "LegalCopyright", "Copyright\251 Microsoft corp. 1981-1996\0" For whatever reason, there's an extra null in there. 0130 22 00 // cbNode (node ends at 0x0130 + 0x0022 = 0x0152) 0132 0A 00 // cbData 0134 4F 72 69 67 69 6E 61 6C 46 69 6C 65 6E 61 6D 65 00 // "OriginalFilename" + null terminator 0145 00 00 00 // padding to restore alignment 0148 53 48 45 4C 4C 2E 44 4C 4C 00 // "SHELL.DLL" + null terminator 0152 00 00 // padding to restore alignment 0154 39 00 // cbNode (node ends at 0x0154 + 0x0039 = 0x018D) 0156 29 00 // cbData 0158 50 72 6F 64 75 63 74 4E 61 6D 65 00 // "ProductName" + null terminator 0164 4D 69 63 72 6F 73 6F 66 74 AE 20 57 69 6E 64 6F 77 73 28 54 4D 29 20 4F 70 65 72 61 74 69 6E 67 20 53 79 73 74 65 6D 00 00 // "Microsoft® Windows(TM) " // "Operating System" + null terminator // + another null terminator? 018D 00 00 00 // padding to restore alignment There's another of those extra null terminators. Go figure. 0190 1A 00 // cbNode (node ends at 0x0190 + 0x001A = 0x01AA) 0192 06 00 // cbData 0194 50 72 6F 64 75 63 74 56 65 72 73 69 6F 6E 00 // "ProductVersion" + null terminator 01A3 00 // padding to restore alignment 01A4 33 2E 31 30 00 00 // "3.10" + null terminator // + another null terminator? 01AA 00 00 // padding to restore alignment 01AC 14 00 // cbNode (node ends at 0x01AC + 0x0014 = 0x01C0) 01AE 04 00 // cbData 01B0 57 4F 57 20 56 65 72 73 69 6F 6E 00 // "WOW Version" 01BC // no padding needed 01BC 34 2E 30 00 // "4.0" + null terminator 01C0 // no padding needed
Once we reach offset 01C0 24 00 // cbNode (node ends at 0x01C0 + 0x0024 = 0x01E4) 01C2 00 00 // cbData (no data) 01C4 56 61 72 46 69 6C 65 49 6E 66 6F 00 // "VarFileInfo" + null terminator 01D0 // no padding needed 01D0 // no data
Since we have not reached the end of the 01D0 14 00 // cbNode (noed ends at 0x01D0 + 0x0014 = 0x01E4) 01D4 04 00 // cbData 01D6 54 72 61 6E 73 6C 61 74 69 6F 6E 00 // "Translation" + null terminator 01E0 09 04 E4 04 // 0x0409 = US English // 0x04E4 = 1252 = Western European 01E4 // no padding needed
And once we've reached offset Thus, we have reconstructed the original version resource: FILEVERSION 3,10,0,103 PRODUCTVERSION 3,10,0,103 FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS VS_FF_PRERELEASE | VS_FF_PRIVATEBUILD FILEOS VOS_DOS_WINDOWS16 FILETYPE VFT_DLL FILESUBTYPE VFT_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "CompanyName", "Microsoft Corporation" VALUE "FileDescription", "Windows Shell library" VALUE "FileVersion", "3.10" VALUE "InternalName", "SHELL" VALUE "LegalCopyright", "Copyright\251 Microsoft corp. 1981-1996\0" VALUE "OriginalFilename", "SHELL.DLL" VALUE "ProductName", "Microsoft\256 Windows(TM) Operating System\0" VALUE "ProductVersion", "3.10\0" VALUE "WOW Version", "4.0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x0409, 0x04E4 END END Next time, we'll look at how version resources are represented in 32-bit resources. |
Comments (14)
So, given that the resource system has a perfectly workable system for i18n, why are all the different locales packed into one resource block for version resources? And following on from that, what is the recomended ID and Locale to use for the version resources? (Both of these questions apply to 32bit as well as 16bit) Actually there are distinct entities here. NUL is the name for ASCII 0. NULL is a name for a constant which converts trivially to a null pointer in C. The word "null" has some definition which I won’t bother quoting; I’m assuming we’re happy with its usage in context. Null pointers are synonymous with NULL but that doesn’t "make" them NULL; NULL is either a builtin compiler concept or a macro which expands to a compiler concept (usually just "0" but that’s a whole additional legacy). "Strings" in C/C++ are required to terminate with a null character which is by definition 0. (Even with wchar_t being a distinct type, it’s still an integral type and must store and preserve the value "0". Whereas pointers are not integral types and assignment of "0" to them may involve a nontrivial conversion.) I personally try to avoid using the term NUL ever since it assumes too much about the character set encoding. But I’m a weirdo like that. Thursday, December 21, 2006 10:21 AM by Dean Earley
If you’re talking about Win16 then the base note here makes me think that it did not have an i18n system. Strings were encoded in ANSI and there was no way to designate code pages other than the code page in use in the end user’s Windows system. If you’re talking about Win32 then the resource system does appear to be designed to support i18n but it doesn’t work. If you try to load resources for a language other than the "first" language in the .exe’s resources, it won’t work. You have to put each other language in its own .dll.
Maybe so that the list of languages can be retrieved? There’s one known place to retrieve it from.
Ahhh, good old backwards compatability then :)
It works for me when i specify the LocaleID… If you don’t, it gets the first suitable. Friday, December 22, 2006 7:33 AM by Dean Earley [Norman Diamond:]
>
I think I mixed up two things, sorry. (1) If you try to load "ordinary" resources (strings, dialog boxes, etc.) with a LocaleID that doesn’t match the "first" set of resources that were compiled into the .exe then it doesn’t work. This is why separate resource-only .dll files are needed. (2) For version resources, which are the subject of this thread, I didn’t try it. No that’s not right, because I did try it, I just didn’t know I was trying it. No that’s not quite right either. For a product that is designed primarily to be exported, I set "ordinary" resources to English (UK) in the .exe and various other languages in .dll’s. I defined version resources in the .exe but didn’t bother (yet) in the .dll’s. Both Visual Studio 2005 and eMbedded Visual C++4 put version resources in a resource section for Japanese instead of English (UK). The .exe produced by eVC++4 has version resources visible despite the mismatch. The .exe produced by VS2005 ordinarily doesn’t have version resources visible, and still doesn’t even if I hand-edit the .rc file to put them in the English (UK) section — but Microsoft suggested a workaround in editing the .rc file, so that now the version resources are visible even though they’re in the default Japanese section. So I think that I18N might really be working (mostly), for version resources though not for other resources. Why are 16-bit resources 32-bit aligned? Previous blogs in this series: 0: A long journey begins with the zeroeth step One of the first things Comments are closed. |
I’m not really that surprised at the “another NUL terminator”.
There have been times where the resource compiler (or maybe
Visual Studio, I haven’t really looked) does not properly terminate the
strings in the version resource. In fact, I had to use the
“extra” NULs on my last project because the product version string had
weird characters on the end due to the File Properties dialog printing
out the size of the subsequent version information record as a string.
By the way. I do hope that the use of “null” rather than NUL
was a typo. It would be damaging to my idealized version of
Raymond Chen to see that he didn’t know his ASCII character codes.
That puts a serious hurt on your geek cred. :)
same way that BEL is the name for the bell character, but if I want to
talk about a bell, I’ll just call it bell and not BEL. -Raymond]
Visual Studio’s resource editor includes a as part of the value strings, but not the name strings, in the version resource, at least in VS2003.
<<Beyond that, you can call your nodes anything you want and give them any kind of data you want. But if you want other people to understand your version information, you’d be best off following the conventions I describe below.>>
In fact, even the resource editor in VS has problems with that, and will "merge" resources from other blocks into the StringFileInfo block.
(VS 2005 on Win XP, editing version info in DLL)
Raymond is right. NUL is the name of the character, not NULL.
There is an unfortunate complication for the string nodes; according to the <A HREF=”Phil Rodgers says:
Apologies – an honest mistake!