Mega Code Archive

 
Categories / Delphi / Examples
 

Adding version information to applications

This article I will investigate the means by which version information can be made available within an application, writing a class which can be incorporated into any new development. Adding Version Information To Applications Last article I discussed the concept of a build, and why a standard process to produce a consistent set of deliverables was a good thing. Since the earliest computer programs were deployed, developers wanted to have some way of identifying which version a customer was running; after all, it’s obviously worthwhile to know if a bug they’re reporting has already been fixed. Fairly quickly (and obviously) a numerical system evolved, simply incrementing the version number each time a release was made. As the frequency of releases increased, developers wanted to have some way of distinguishing a major new release compared with an interim one: the concept of major and minor versions of a program was introduced - going from V1.12 to V2.0 conveyed a lot more (even to lay users) than going from V12 to V13. For many years the major and minor version was adequate and commonplace, but as programs grew larger and complex requiring more frequent bug-fixes a tertiary number was tacked on to the end of the version to indicate a “release”. All releases with the same major and minor version numbers should have an identical feature list, but should include cumulative bug fixes and other enhancements without adding new features as such. Latterly, it has become good practice to append the build number to the release resulting in a complete version number with four components, X.Y.Z.B, where X represents the major version, Y the minor version, Z the release number and B the build number. Note that although the minor and release versions can be reset to zero (with major and minor releases), the build number is never reset and therefore always increases with each new release (although not necessarily strictly incremental; it is very possible to have internal builds within a company that are not released to clients). Listing 1 summarises when each version number component should be incremented. Component Describes When To Increment X Major After significant changes have been made to the application Y Minor After new features have been added to the application Z Release Every time a minor release is made available with bug fixes B Build Every time the build process completes Listing 1 – Components of version number X.Y.Z.B Although marketing departments hijacked the concept of version numbers for their own ends once it was realised that large numbers of people would invest in upgrades when the major version number changed or leapfrogged the competition, it is still very useful to have some kind of a formal version number in your applications – it is much easier to confirm that your client is running Build 134 or later, rather than “SuperApp 97 SR-2a Patch 13 (Y2K)”. This became particularly important with the advent of shared DLL libraries within Windows that could be replaced by a freshly installed application. Although not terribly good at using the numbering scheme consistently, virtually all Microsoft application files (EXE’s and DLL’s) now contain some kind of a 4-digit version number embedded within them. Indeed, Windows 2000 makes extensive use of this information in an attempt to prevent important system files being overwritten by earlier, or sometimes later, versions. Installation procedures also make use of this information by warning when a file would overwrite a similarly named but later version of itself. Embedded Version Information If you right click on an EXE or DLL file in Explorer the pop-up menu that appears has an entry at the bottom called “Properties”. Selecting this shows some details about the file. If available, the second tab (called Version) shows the information embedded within the file about the version number, product name, copyright details and so on. Any file that can have a resource segment can have this information embedded within it, and this includes applications compiled with Delphi. Microsoft has published the information and format that must be provided in the resource section for this information to be displayed. In Delphi 2, the only way by which this could be achieved was by creating a resource file with exactly the right strings within it, compiling it separately with the resource compiler and then linking it into the application (this forming part of the main build process). Although not difficult, these steps required a level of technical knowledge and proficiency such that few application developers bothered to do it. Within it’s ethos of making Windows development more productive, Delphi 3 introduced a special dialog whereby this information could be easily attached to the application. Figure 1 shows this dialog, available from the Delphi Project menu, Options item. By ticking the checkbox at the top of the tab to show that version number information should be included it is possible to add details about not only the version number, but also flags about whether the build is intended for public release, and at the bottom it is possible to add details to a whole list of predefined categories, such as a description of the file and copyright. Delphi automatically changes the FileVersion category as the module version number details above are updated. When this dialog is confirmed, Delphi constructs the necessary resource file (which also contains such details as the application icon) in a transparent step, which will be automatically linked into the final executable. If you do this on even the simplest project you will see that you can now right-click on the executable and obtain version information, just like all well-behaved Windows applications. What Version Am I? Now that we know how to place version information within the application, it would be useful to actually access the information. After all, if you are trying to establish over the phone what version a user is running it would be much easier to describe how to display the About dialog rather than finding the application executable in Explorer, then right clicking, choosing Properties and selecting the Version tab. As the version information is just stored as strings in a specific format in the resource section of the application, it would be possible to use standard Win32 resource file commands to extract the relevant information, decoding the structures. There are, however, some specific Win32 API commands available that do just this in a much more convenient form. These are GetFileVersionInfoSize (which returns details about the space required to store the version information), GetFileVersionInfo (which extracts the details into a pre-supplied buffer of the correct size), and VerQueryValue (which extracts a named piece of version information from the buffer, such as “LegalCopyright”). As in usual when interacting with the Win32 API, these commands must be called in the correct sequence, preserving certain internal values returned from previous commands through var parameters. It is a very good idea to encapsulate any kind of interaction with the Windows API with a more user and Delphi-friendly interface. Typical Delphi programmers don’t want to deal with allocating memory blocks and Win32-specific types such as DWORD and UINT, and neither should they. Far better to design a nice class that, in the best traditions of OO, hides the raw access to version information and presents a far more useable interface. This has the added advantage that should the storage of this version information ever change, the same class can encapsulate the system dependencies whilst maintaining the same public interface. There are a few things to consider when designing this class. Firstly, it should be able to be used with any application file, including that from which it is executing. Secondly, it should provide convenient access to the standard and most commonly used version information keys (FileVersion, ProductName etc.). Lastly, as it is possible for the user to provide additional custom version keys and values within their structure, the class should expose these in a natural way. Listing 2 shows the public interface for this class. TVersionInfo = class private function GetVersionInfo (Index: Integer): String; public constructor Create (ThisSourceFile: String); destructor Destroy; override; // Arbitrary key information property Key[KeyName: String]: String read GetKey; // Standard key information property CompanyName: String index 0 read GetVersionInfo write SetVersionInfo; property FileDescription: String index 1 read GetVersionInfo; property FileVersion: String index 2 read GetVersionInfo; property InternalName: String index 3 read GetVersionInfo; property Copyright: String index 4 read GetVersionInfo; property TradeMarks: String index 5 read GetVersionInfo; property OriginalFileName: String index 6 read GetVersionInfo; property ProductName: String index 7 read GetVersionInfo write SetVersionInfo; property ProductVersion: String index 8 read GetVersionInfo write SetVersionInfo; property Comments: String index 9 read GetVersionInfo; property BuildNumber: String read GetBuildNumber; end; Listing 2 – Public interface of TVersionInfo As can be seen from the class, all of the standard key names are exposed as named properties, while the Key property provides access to custom additional information by name. The class is constructed by passing in a fully-qualified path and file name from which version information should be extracted. There is one very interesting aspect about this class that demonstrates a under-utilised aspect of Delphi class design: using the index identifier to map a number of different properties onto the same accessor function. As can be seen from the private implementation of GetVersionInfo used to read the properties, this index value is passed in depending on the property accessed, allowing the function to determine which value to return. As we shall see in the implementation, this often facilitates very concise coding. As previously mentioned, the GetFileVersionInfo command extracts the details from the resource section and stores them in the buffer passed as a parameter to the API call. It therefore makes sense to perform this as a one-off operation in the constructor. Once this information has been extracted, we can interrogate it for known key names. For convenient coding and an increase in performance, we will extract key values for all of the standard key names and store these values in an array with the same ordinal value as each property index. These means that the implementation of the GetVersionInfo property accessor function can be transparently simple and simply returns the array value at the supplied index. For the custom keys that might additionally be provided we will simply call the API command to extract the details from the version information buffer. Although this will be slightly slower than accessing the details direct from a pre-calculated array, it is not anticipated that these properties will be frequently accessed and this is therefore an acceptable design decision. Listing 3 shows the implementation of the class. constructor TVersionInfo.Create (ThisSourceFile: String); const VersionKeyNames: array [0..MaxVersionKeys] of String = ('CompanyName', 'FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'LegalTrademarks', 'OriginalFilename', 'ProductName', 'ProductVersion', 'Comments'); var ThisInfo: Integer; InfoLength: UINT; Len: DWORD; Handle: DWORD; PCharset: PLongInt; begin inherited Create; // Get size of version info Len := GetFileVersionInfoSize (PChar (ThisSourceFile), Handle); // Allocate VersionInfo buffer size SetLength (VersionInfo, Len + 1); // Get version info if GetFileVersionInfo (PChar(ThisSourceFile), Handle, Len, Pointer(VersionInfo)) then begin // Get translation info for Language / CharSet IDs if VerQueryValue(PointerVersionInfo), '\VarFileInfo\Translation', Pointer(PCharset), InfoLength) then begin LangCharset := Format ('%.4x%.4x', [LoWord (PCharset^), HiWord (PCharset^)]); InfoAvailable := True; // Get standard version information for ThisInfo := 0 to MaxVersionKeys do StandardKeys[ThisInfo] := GetKey(VersionKeyNames[ThisInfo]); end; end; end; function TVersionInfo.GetKey (ThisKeyName: string): string; var InfoLength: UINT; begin if InfoAvailable then begin SetLength (Result, 255); if VerQueryValue (Pointer(VersionInfo), PChar(Format('\StringFileInfo\%s\%s', [LangCharset, ThisKeyName])), Pointer(Result), InfoLength) then begin SetString(Result, PChar(Result), InfoLength - 1); end else begin Result := ''; end; end else begin Result := 'N/A'; end; end; function TVersionInfo.GetVersionInfo(Index: Integer): string; begin Result := StandardKeys[Index]; end; procedure TVersionInfo.SetVersionInfo(Index: Integer; Value: string); begin StandardKeys[Index] := Value; end; function TVersionInfo.GetBuildNumber: string; begin // Strip the last element from the file version Result := FileVersion; while Pos ('.', Result) > 0 do begin Result := Copy (Result, Pos ('.', Result) + 1, Length(Result)); end; end; One of the nuances of the way that version information is stored within the application resource section is that it is possible to define which language character set has been used to create the version information (and the application or DLL itself). This is defined within the version information as a double word, or 32-bit unsigned integer, and must be present as part of the version information string (in hexadecimal format) for each key that is extracted. One of the jobs of the constructor, as well as extracting the version information into a string buffer (a convenient way of storing static data returned from the API calls) is to extract the language and character set information and to build up a correct hexadecimal string that will be used in future calls to VerQueryValue. Once this has been done the constructor then makes a call to the routine that actually returns a version information value for a supplied key. This routine lives a double life, additionally masquerading as the accessor function for the Key property. Again, an interesting aspect of this process is that the constructor obtains a list of the standard key names from a private constant fixed string array that is declared and defined in a single statement. This too is a little-used, but very handy, technique that is also allowable for global variables as well as constants. If you refer back to Figure 1 you can see some extra versioning information in the group called “Module attributes”, mainly a set of flags describing the status of the application, whether it is a debug or private build and so on. This information is also available within the resource file, and can be accessed using the VerQueryValue API command, but with a second parameter of just “\”, rather than “\VarFileInfo\Translation”. In this instance what is returned is a pointer to a structure that contains details about file type and content, double word pairs that can be combined to represent a strict 64-bit version number used for strict numerical comparisons (by installation programs) as well as the Module attributes definable within Delphi. It would be a simple task to extend the TVersionInfo constructor to extract this information and expose it via simple Boolean properties and, in Delphi 5, Int64 property types for the strict version numbers. This ability to encapsulate access to complex data structures via a convenient interface is one of the beauties of Object Orientation, and one to which we will be continually returning. The class defined here has already exposed the build number as a separate property, itself derived from the last element of FileVersion to provide users of the class with appropriate data in a convenient manner. For example, an appropriate use of this would be on the About box for an application, displaying not only the application name and copyright details, but also the program version and build number for easy reference. To complete our examination of a build process it would be extremely nice if, having successfully completed, it could increment the build number (and possibly release number) within the version information for all deliverables. Delphi appears to store this version information within the .DOF file that accompanies the project; this is in INI file format and contains most of the details exposed by the Project | Options dialog. However, programmatically making changes to this file (there are entries in the [Version Info] section for things like MajorVer, Release and so on) does not actually result in any changes to the compiled application. This is because Delphi generates the compiled resource file (Project.RES) from this information only when it is changed interactively from within Delphi itself. The .RES compiled resource file is simply linked to the application at compile time, so making changes to the .DOF file and then recompiling the application does not cause the resource file to be regenerated and the out of date one is used. It is good practice therefore to update the build and release numbers of each Delphi project by hand at the end of each successful build. In the next article we will begin to design the classes that we will need to produce a truly object-oriented application, encapsulating business objects and database access in a highly productive manner. This will be a major odyssey covering almost every aspect of Windows application development.