programming experience (0分)

  • 主题发起人 主题发起人 mypine
  • 开始时间 开始时间
M

mypine

Unregistered / Unconfirmed
GUEST, unregistred user!
[h1]Adding Version Information To Applications[/h1]
Last month I discussed the concept of a build, and why a standard process to
produce a consistent set of deliverables was a good thing. This month we 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.
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 i
s 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 c
onstructs 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 youdo
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 programmersdo
n’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 (Pointer (VersionInfo), '/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 MaxVersionKeysdo
begin

StandardKeys[ThisInfo] := GetKey (VersionKeyNames[ThisInfo]);

end;
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) > 0do

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
ado
uble 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 beendo
ne the constructor then
makes a call to
the routine that actually returns a version information value for a supplied key
. This routine lives ado
uble 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,do
uble
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)do
es
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 applicationdo
es 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.
Next month 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.
 
那么多阿?好像是程序员的手记?
 
??欧?老大,要考英文了?我可很久没看过这么多字母了!呵呵[:D]
 
时间太久 强制结束
 

Similar threads

A
回复
0
查看
981
Andreas Hausladen
A
A
回复
0
查看
932
Andreas Hausladen
A
A
回复
0
查看
400
Andreas Hausladen
A
A
回复
0
查看
607
Andreas Hausladen
A
A
回复
0
查看
805
Andreas Hausladen
A
后退
顶部