January 1999

A component for reading version information from a program file

by John M. Miano

You can download sample files from our Web site as part of the file jan99.zip. Visit www.zdjournals.com/cpb and click on the Source Code hyperlink.

Almost every program that goes into production will eventually exist in multiple versions. And, unless the application resides on a single computer, multiple versions will exist at the same time. Multiple versions of the same application create support problems--not the least of which is simply determining which version of the application is running on a particular computer. Suppose you're supporting insurance systems, and the users already have an auto-insurance application. Now you're rolling out a home-insurance application that shares some elements with the auto-insurance program. For example, the original system configuration program that shipped with the auto-insurance application has been upgraded so that it can configure both the auto- and home-insurance applications.

If someone re-installs the auto-insurance application, you don't want the old configuration program to be installed on top of the new one. (You'd be getting support calls about the home-insurance program not working.)

Windows lets you store program version information in an application's resource data. An installation procedure can call Windows API functions to extract the version of an existing file and compare this with the version of the new file.

Another version issue occurs when a user calls for support. The first thing a support person needs to know is the version of the application the caller is using. Traditionally, the application version is displayed on an About dialog box accessible through the Help menu.

If you're building a C++Builder application, you don't want to store the current version in both the application's resource data and in a label control on the About dialog box. If you have to keep two copies of the same information they'll invariably get out of synchronization. The solution for this problem is a VCL custom control that automatically updates a label control with information from the application's version resources. In this article, we'll show you how to create such a component. (For some background on accessing version information, see "Working with version information".)

 

The TVersionInfo component

Our version-information component has two properties. FileVersion is a runtime, read-only property that returns the file version string; FileVersionLabel is a read/write property that specifies a TLabel control, which is automatically updated with the file version.

Creating the component

To create the component, start a new project and select Component | New Component from the C++Builder main menu. Fill in the resulting New Component dialog box, as shown in Figure A; then click OK. Because this will be a non-visual component, you use TComponent as the ancestor class.

Figure A: Create the TVersionInfo class.

The component class definition

The TVersionInfo class definition, containing two property definitions, is shown in Listing A. The FileVersion property is read-only and the value never changes while a program runs, so it simply references a member variable. FileVersionLabel is a design-time property that lets the user assign a label to automatically display the file-version string. This property uses the SetFileVersionLabel function to write its value.

Listing A: The TVersionInfo class definition, versioninfo.h

#ifndef VersionInfoH
#define VersionInfoH
#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include <StdCtrls.hpp>
//--------------------------------------------------
class PACKAGE TVersionInfo : public TComponent
{
private:
  String file_version ;
  TLabel *file_version_label ;

void __fastcall TVersionInfo::SetFileVersionLabel (
                                    TLabel *label)
  {
    if (ComponentState.Contains (csDesigning))
      label->Caption = "#File Version#" ;
    else
     label->Caption = file_version ;
    file_version_label = label ;
    return ;
  }
protected:
  virtual void __fastcall Notification(
                              TComponent *AComponent,
                              TOperation Operation) ;
  virtual void __fastcall Loaded () ;
public:
  __fastcall TVersionInfo(TComponent* Owner);

 __property String FileVersion = { read = file_version } ;
__published:
  __property TLabel *FileVersionLabel = 
                            { 
                              read = file_version_label,
                              write = SetFileVersionLabel
                            } ;
};
//-------------------------------------------------------
#endif

The only reason for using a function to write to the FileVersion property is that the label's caption will be updated when it's linked to the TVersionInfo control. At design time, the label is updated with the string #File Version#; at runtime it gets the actual version stored in the file. Using a constant string at design time avoids confusing the user. At design time, the control reads the version information for the C++Builder IDE rather than for the application.

 

The component implementation

The TVersionInfo class's CPP implementation file appears in Listing B. Because the file-version information doesn't change during the execution of a program, and because it's available throughout the program's execution, the TVersionInfo class extracts the version information in the class constructor. The class constructor uses the process described earlier to store the file-version string in the file_version member variable.

Listing B: The TVersionInfo class implementation, versioninfo.cpp

//----------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "VersionInfo.h"
#pragma package(smart_init)
//-----------------------------------------------------
// ValidCtrCheck is used to assure that the components 
// created do not have any pure virtual functions.
//

static inline void ValidCtrCheck(TVersionInfo *)
{
    new TVersionInfo(NULL);
}
//------------------------------------------------------
//
//  Description:
//
//    Class Constructor
//
__fastcall TVersionInfo::TVersionInfo(TComponent* Owner)
    : TComponent(Owner)
{
   file_version_label = NULL ;

   DWORD handle ; // Dummy, Windows does not use 
                  // this parameter.
   DWORD size = GetFileVersionInfoSize (
                   Application->ExeName.c_str (),
                   &handle) ;
   if (size == 0)
     return ; // No file information

   char *buffer = new char [size] ;

   bool status = GetFileVersionInfo (
                     Application->ExeName.c_str (),
                     0, // Unused Parameter
                     size,
                     buffer) ;
   if (! status)
   {
     delete [] buffer ;
     return ;
   }

   // Extract the language ID
   UINT datasize ;
   struct
   {
     unsigned short language ;
     unsigned short character_set ;
   } *translation ;
   status = VerQueryValue(
                       buffer,
                       "\\VarFileInfo\\Translation",
                       (void **) &translation,
                       &datasize) ;

  String key 
    = "\\StringFileInfo\\"
    + String::IntToHex (translation [0].language, 4)
    + String::IntToHex (translation [0].character_set, 4)
    + "\\FileVersion" ;
  char *data ;
  status = VerQueryValue(
                       buffer,
                       key.c_str (),
                       (void **) &data,
                       &datasize) ;
  if (status)
    file_version = data ;
  else
    file_version = "" ;
  delete [] buffer ;
  return ;
}

void __fastcall TVersionInfo::Notification(
                                 TComponent *AComponent,
                                 TOperation Operation)
{
  // We don't care about controls being added.
  if (Operation != opRemove)
    return ;

  if (AComponent == file_version_label)
    file_version_label = NULL ;
  return ;
}

void __fastcall TVersionInfo::Loaded ()
{
  if (file_version_label != NULL)
    file_version_label->Caption = file_version ;
  return ;
}

//----------------------------------------------------
namespace Versioninfo
{
    void __fastcall PACKAGE Register()
    {
        TComponentClass classes[1] = 
          {__classid(TVersionInfo)};
        RegisterComponents(
          "Whatever You Want", classes, 0);
    }
}

Loaded() is a virtual function that VCL calls after all the components have been loaded. You use this function to update the TLabel control referenced by the FileVersionLabel property with the file version string, because you know that when Loaded() is called, the label will be ready for updating.

The other virtual function used in this component is Notification(). VCL calls this function when a control is added to or removed from a form. Suppose you add a label control to a form and then assign the label a TVersionInfo control's FileVersionLabel property. If you then delete the label, the FileVersionLabel property would contain an invalid value. The Notification() function lets the component determine whether a component it's referencing is becoming invalid. If the TVersionInfo control references a TLabel control that's no longer valid, the Notification() function deletes the reference.

 

Installing the component

Having created the TVersionInfo component, you're ready to install it on the component palette in the C++Builder IDE. Select File | New from the main menu and then double-click on the Package icon. In the New Package dialog box, enter the name and description of the package, as shown in Figure B.

Figure B: Create a new package using the New Package dialog box.

Next, you need to add the component to the package. Select Project | Add To Project and fill in the Add dialog box, as shown in Figure C.

Figure C: Add the component to the package.

Now, rebuild your package. Then, select Component | Install Packages from the main menu and add your package to the list of installed packages. When you're finished, the TVersionInfo control should appear on the component palette, as shown in Figure D.

Figure D: The TVersionInfo component appears on the component palette.

Try creating a new project. Add a TVersionInfo control and a TLabel control to the main form. Go to the object inspector and assign the label control to the FileVersionLabel property and then run the program. The results appear in Figure E. Go back and select Project | Options and update the version information; then run the program again and see what happens.

Figure E: The project displays version information like this.

Final thoughts

In order to avoid duplication, this component implements properties for reading only one of the possible version-information values. If you decide to use the component in your projects, you'll probably want to expand it to read some of the other pieces of version information from the image file. Other keywords are read in exactly the same manner as FileVersion. Another possible extension is to support the use of multiple languages--you could add a property that would allow the user to select the language to display.