Version resources, part 2: Getting version information from an application


Introduction

There's not a lot of point adding version information to an executable if you can't read it.

To read version information from a file (whether it be EXE, DLL, VXD or whatever) you really only need to make use of 3 different API calls.

API Call Function
VerQueryValue Retrieves specified version information from the specified version information resource
GetFileVersionInfo Retrieves version information for the specified file
GetFileVersionInfoSize Returns the size, in bytes, of any available version information

Step 1 - Find out if the file contains version information, and how big it is

To work out if the file contains version information you call the GetFileVersionInfoSize() function. It returns, in bytes, how large the version information is, or 0 if there isn't any present.

DWORD
GetFileVersionInfoSizeA(
        LPSTR lptstrFilename, /* Filename of version stamped file */
        LPDWORD lpdwHandle
        );                      /* Information for use by GetFileVersionInfo */

The important part to note of it's C prototype is the first parameter, the address of the CString containing the filename, and the return value, which contains the size of the version information. In Clarion I use the following prototype:

GetFileVersionInfoSize(Long,Long),Long,Raw,Pascal,Name('GetFileVersionInfoSizeA')

The 2nd parameter is no longer used in 32bit code. In 16bit code it contains the address of a DWord that is filled in with a handle to the version information.  Any subsequent versioning API calls (such as GetFileVersionInfo() ) would then use this handle to access the version information. 

To call the function, simply populate a CString with the name of a file and look at the return value.

filename_string CString(200)
buffersize      long
unusedvar       long
Code 
 filename_string = 'C:\WINNT\SYSTEM32\NTDLL.DLL'
 bufferSize = GetFileVersionInfoSize( Address(filename_string),Address(unusedvar ))
 if buffersize = 0
  ! no version information, or file does not exist
 else
  ! version information present
 end

Step 2 - Get the version information from the file

Once you have worked out if the file contains version information or not you then need to read the version information into memory.  This is accomplished by using the GetFileVersionInfo() call.

BOOL
GetFileVersionInfoA(
        LPSTR lptstrFilename,   /* Filename of version stamped file */
        DWORD dwHandle,         /* Information from GetFileVersionSize */
        DWORD dwLen,            /* Length of buffer for info */
        LPVOID lpData
        );                      /* Buffer to place the data structure */

GetFileVersionInfo() takes the name of the file, the unused 16bit handle, and the length of the version information, all information that was obtained in the earlier call to GetFileVersionInfoSize().  The 4th parameter is the address of some memory to hold the version information, and the return value indicates whether the call was successful or not.  The Clarion prototype would be something similar to:

GetFileVersionInfo(Long,Long,Long,Long),Bool,Raw,Pascal,Name('GetFileVersionInfoA')

So, to continue with our example code from above, we should now have something that looks like this:

filename_string         CString(200)
buffersize              long
unusedvar               long
rawversioninfobuffer    byte,dim(1024)
Code 
 filename_string = 'C:\WINNT\SYSTEM32\NTDLL.DLL'
 bufferSize = GetFileVersionInfoSize( Address(filename_string),Address(unusedvar ))
 if buffersize = 0
  ! no version information, or file does not exist
 else
  if GetFileVersionInfo( Address(filename_string), unusedvar, buffersize, Address(rawversioninfobuffer) ) = 0 
   ! did not read version information
  else
   ! read version information
  end
 end

Step 3 - Getting version numbers from the version resource

Once you have read the version information into memory with GetFileVersionInfo() you then have to get at the actual version numbers within the version information.  There are basically 2 ways you can do this 

1) By calling VerQueryValue() to get the VS_FIXEDFILEINFO record from the version information.  The VS_FIXEDFILEINFO is a group that contains the file version and product version numbers, the file flags field, the file type field etc etc - basically all the static information defined at the top of the version resource in the .RES file.

2) By calling VerQueryValue() to find what language IDs the version information supports, then getting a language-specific string ( FileDescription, FileVersion, InternalName etc ) directly from the version information.

The first method is certainly easier, because you don't have to enumerate the support languages, or worry about converting the language ID and code page ID to hex.  But, as I mentioned in part 1, in order for it to work correctly you must remember to change the FileVersion and ProductVersion numbers in addition to the FileVersion string.

You get the actual version information out of the version resource by calling the VerQueryValue() procedure.

BOOL
VerQueryValueA(
        const LPVOID pBlock,
        LPSTR lpSubBlock,
        LPVOID * lplpBuffer,
        PUINT puLen
        );

The first parameter is the address of the memory that holds the version information - this is the same as the 4th parameter to GetFileVersionInfo().

The 2nd parameter is a pointer to a CString that holds the value to query.  To get the VS_FIXEDFILEINFO record you use a string of '\'.  To get a language specific string ( FileVersion, for example ) you use '\StringFileInfo\LLLLCCCC\FileVersion' where LLLL and CCCC are the identifiers (in hex) of the language ID and codepage ID in the version information.

The 3rd parameter is a pointer to a variable that's a pointer to the memory that will actually receive the version information, and the 4th parameter is a pointer to a variable that receives the size of the version information.   To use it in Clarion you'd use a prototype similar to this:

VerQueryValue(Long,Long,Long,Long),Bool,Raw,Pascal,Name('VerQueryValueA')

If we take the easy route for a moment and decide to just get the entire VS_FIXEDFILEINFO record then we obviously need to know what the format is.

VS_FixedFileInfo    Group,Type              
dwSignature             Long ! fixed to 0xFEEFO4BD
dwStructVersion         Long
dwFileVersionMS         Long
dwFileVersionLS         Long
dwProductVersionMS      Long
dwProductVersionLS      Long
dwFileFlagsMask         Long
dwFileFlags             Long
dwFileOS                Long
dwFileType              Long
dwFileSubtype           Long
dwFileDateMS            Long
dwFileDateLS            Long
                    End

It's basically a simple group structure that contains the version numbers and other fields from the version resource we saw earlier in the .RES file.  Continuing on from our example code earlier, we would now have something similar to this: 

filename_string         cstring(200)
buffersize              long
unusedvar               long
rawversioninfobuffer    byte,dim(1024)
rawqueryinfobuffer      byte,dim(1024)
subblock_string			cstring(20)
temp_ptr                long
version_info            like(VS_FixedFileInfo)
Code 
 filename_string = 'C:\WINNT\SYSTEM32\NTDLL.DLL'
 bufferSize = GetFileVersionInfoSize( address(filename_string),address(unusedvar ))
 if buffersize = 0
  ! no version information, or file does not exist
 else
  if GetFileVersionInfo( address(filename_string), unusedvar, buffersize, address(rawversioninfobuffer) ) = 0 
   ! did not read version information
  else
   ! read OK
   !
   ! now set the string to look for the (VS_FixedFileInfo information
   subblock_string = '\'
   ! set up our temporary pointer
   temp_ptr = address(rawqueryinfobuffer)
   if VerQueryValue( address(RawVersionInfoBuffer), |
                     address(subblock_string), |
                     address(temp_ptr), |
                     address(buffersize) ) = 0 
    ! error - the original string in subblock_string was probably wrong
   else
    memcpy( Address(version_info), temp_ptr , buffersize)
    ! got version information successfully
   end                      
  end
 end

The only slight problem with this is that when VerQueryValue() returns with the correct information it actually returns us a pointer to the structure, not the actual VS_FixedFileInfo record directly.  Because Clarion doesn't support the use of pointers you have to use the C runtime function MemCpy() to dereference the pointer and copy the required number of bytes directly from memory into a local variable.

To implement the second, more difficult way of doing it you have to call VerQueryValue() a number of times.  Calling it with a string of '\VarFileInfo\Translation' returns a pointer to an array of language and codepage pairs, with each field being 2 bytes ( 4 bytes total for one language/codepage pair ).  You can then calculate how many languages the version resource supports ( buffersize / 4 ), and work your way through the version information, converting each language and codepage ID to hexadecimal, before calling VerQueryValue() to get the string you are interested in.  The example ( below ) contains code that does just this.

CI_VER2.ZIP (187K)


Back to the home page