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.
Back to the home page