How to cope with version info?
By , journalist and programmer

For you as a programmer it is essential to add useful product and version information to your applications and DLL's. This is really not difficult, but there are certain rules to follow in order to make the info accessible for your fellow programmers. Or for yourself, if you want to use portions of it in your application's About Box. Those rules were set by Microsoft Corporation, I assume. After all 'they' were the inventors of the Windows API. In this perspective it is much interesting to see that Microsoft-programmers sometimes don't obey their own rules.

In this article I'm going to discuss 2 aspects of the version info issue:
   how to store it
   how to retrieve it

These points apply, as you will understand, to our programming skills. To see which information I'm talking about, simply right-click on an application's name in any folder of Windows Explorer, select Properties and open the Version-tab (if present). This is - approximately - what you'll get:

As you may have noticed, this screenshot shows Microsoft Word's Version-tab. Why this one? It proves that within my copy of Word97 relevant version information surely has been included. Nevertheless I cannot retrieve this same info using the relevant Windows API-calls. Following the rules as explained in a Microsoft-support article my reward was just an empty Message Box.
Resource definition file
Before I come to explain how Microsoft coders put this kind of 'bugs' in some of their applications, I'm going to discuss how to write version info in a Resource Definition File. This is the file that programmers always use to include icons and so on within their applications and that has to be compiled using Microsoft's RC.EXE and Power Basic's PBRES.EXE. This is an example:

#include "D:\PBWIN70\WINAPI\RESOURCE.H"   
VS_VERSION_INFO VERSIONINFO               // numeric or .... 
FILEVERSION 1, 1, 0, 0                    // binary version .... 
PRODUCTVERSION 1, 0, 0, 0                 // and product info
FILEOS VOS_WINDOWS32                      // platform = 32 bits Windows
FILETYPE VFT_APP                          // application (use VFT_DLL for DLL)

BEGIN                                     
  BLOCK "StringFileInfo"                  // this is the info we're talking about
  BEGIN
    BLOCK "041304E4"                      // &H0413 = LANG_ID Dutch, &H04E4 = charset ANSI
    BEGIN
      VALUE "CompanyName",      "Egbert Zijlema\0"
      VALUE "FileDescription",  "Application to test version resource file\0"
      VALUE "FileVersion",      "1.01\0"
      VALUE "InternalName",     "GetVer for Windows\0"
      VALUE "LegalCopyright",   "Copyright \251 2000: Egbert Zijlema\0"
      VALUE "LegalTradeMarks",  "Don't have such, legal nor illegal\0"
      VALUE "OriginalFilename", "GETVER.EXE\0"      
      VALUE "ProductName",      "GetVer\0"
      VALUE "ProductVersion",   "1\0"
    END
  END
  BLOCK "VarFileInfo"
  BEGIN
    VALUE "Translation", 0x413, 1252      // tell application it's Dutch + ANSI
  END
END
The part discussed in this article is StringFileInfo. There you can store pre-defined items, such as "CompanyName", "FileVersion" etc. According to Microsoft's WIN32.HLP there are 8 pre-defined items:
  1. CompanyName
  2. FileDescription
  3. FileVersion
  4. InternalName
  5. LegalCopyright
  6. OriginalFilename
  7. ProductName
  8. ProductVersion
But "LegalTrademarks", although not mentioned in this help file, also works fine. The item "Language", however, is a different story. You must include it by means of a language-charset identifier, using "VarFileInfo\Translation", but you cannot retrieve it in the 'normal' way. Fortunately, the Windows API can convert a language identifier to a language name (API-call VerLanguageName).
Note that some applications have a Comment item included in the list. Not very smart, because Windows is sorting the items alphabetically. So 'comment' will come first and, as a result, will be visible when the user opens the Version-tab of your application. Let "CompanyName" be the first item, until you believe that your comments are more important than your business.
VarFileInfo performs two tasks:
1. it adds the language item to the list under Version-tab
2. it tells your application how to identify the information in order to retrieve it properly
Possibly there is a third task? Anyhow, something does not happen when you don't include "VarFileInfo\Translation" in your resource definition file: the first item of the list ("CompanyName") will not automatically be selected and opened when clicking the Version-tab.
Don't miss this: the 2 values in "VarFileInfo\Translation" must be exactly the same as the hexadecimal string in "StringFileInfo". This 8-digit string is actually a combination of two 4-digit strings, representing a language identifier and a character set. It is at least somewhat confusing that those values are written so differently. 0413 is a normal hexadecimal string, 0x413 is the way to write an hexadecimal value in a resource definition file. 04E4 is the hexadecimal representation of 1252, which appears to be a decimal number. In this example 0413 and 0x413 are the language identifier for Dutch, 04E4 and 1252 both represent the ANSI character set. Valid language and charset identifiers can be found in the WIN32.HLP file. Which language identifier you want to use is up to you. But it is a bit odd to present Hebrew or Russian under the Properties Version-tab while your application's screen output is Aussie-English, don't you agree?

How to retrieve it
I'm not going to discuss my source code here line by line. Only the important issues. To begin with, a FUNCTION that has to catch file version information from applications uses 3 (indeed, three!) different API-calls, GetFileVersionInfoSize, GetFileVersionInfo and VerQueryValue:


FUNCTION FileVersion(app$) AS STRING
  LOCAL nSize AS LONG, ret AS LONG, dummy AS LONG
  LOCAL buff AS STRING, LangID AS STRING, CharsetID AS STRING, temp AS STRING
  LOCAL szPathName AS ASCIIZ * %MAX_PATH, svPtr AS ASCIIZ PTR, LangPtr AS LONG PTR

  ' skipped
  ' skipped
  ' skipped

  ' get language information and convert it (via two 4-digit hex strings) to a string
  ' example: "040904E4"
  ret = VerQueryValue(BYVAL STRPTR(buff), "\VarFileInfo\Translation", LangPtr, nSize)
  LangID    = HEX$(LOWRD(@LangPtr), 4) 
  CharsetID = HEX$(HIWRD(@LangPtr), 4)
  block$ = "\StringFileInfo\" + LangID + CharsetID

  ' finally get the information about the product
  VerQueryValue BYVAL STRPTR(buff), block$ + "\InternalName", svPtr, nSize
  temp = temp + @scPtr + $CRLF         ' store result in a temp-var
  ' repeat VerQueryValue for additional string values
  FUNCTION = temp                     
END FUNCTION
VerQueryValue is the most important of the 3 API-calls involved. It tries to get the language and charset information first. The values found should be converted to their 4-digit hex-representations and then combined to one 8-character hex string. Then the same FUNCTION must be called as often as the number of pre-defined strings your application should find. During these repetetive calls it uses the found hex string as 'location'.

It's clear what will happen when the converted hex string does not match the one in StringFileInfo: no information can be obtained. And this is what Microsoft coders obviously did wrong in WINWORD.EXE and other applications. They passed language identifier 0x000 (= Neutral Language) + charset 1252 (= ANSI) through VarFileInfo\Translation, but used lang-charset identifier "040904E4" (= American English + ANSI) in StringFileInfo.


Where to download?
   Click here to download the necessary sources, including a resource definition file.
   VersionInfo.zip can also be downloaded from Power Basic.
   Back to homepage

Copyright © 2000-2002: Egbert Zijlema
Failed to execute script '/cgi-bin/hidden.exe?version': Win32 Error Code = 2