Versionsinformationen


Zum Abschluss werfen wir noch einen Blick auf einen Block mit Versionsinformationen. Sie kennen diese Angaben, wenn Sie sich die Eigenschaften einer Datei ansehen. Enthalten ist meist das Copyright, der Firmenname, der interne Name, usw.
Eine Versionsresource trägt den Bezeichner VERSIONINFO und sieht wie folgt aus

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,0
 PRODUCTVERSION 1,0,0,0
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x10004L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040704b0"
        BEGIN
            VALUE "CompanyName", "Win32-API-Tutorials\0"
            VALUE "FileDescription", "Beschreibung\0"
            VALUE "FileVersion", "1.0.0.0\0"
            VALUE "InternalName", "Interner Name\0"
            VALUE "LegalCopyright", "Copyright © 2004 Mathias Simmack.\0"
            VALUE "OriginalFilename", "Originaler Dateiname\0"
            VALUE "ProductName", "Produkt\0"
            VALUE "ProductVersion", "1,0,0,0\0"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x407, 1200
    END
END

Die Angaben für FILEFLAGS, FILEOS, FILETYPE und FILESUBTYPE richten sich nach bestimmten Werten, die Sie in der Headerdatei "WinVer.h" des PSDK finden können.

So sehen Sie bspw., dass die Angabe FILEFLAGS im Normalfall Null ist. Wenn die Compilerdirektive _DEBUG gesetzt wird, dann erhält das Attribut den Wert Eins (VS_FF_DEBUG): das Debug-Flag wird gesetzt. Ähnlich verhält es sich auch mit den anderen Angaben. Der Wert $10004 in FILEOS steht z.B. für eine Win32-Anwendung.

Aufmerksam machen möchte ich Sie auf die beiden Angaben

 FILEVERSION 1,0,0,0
 PRODUCTVERSION 1,0,0,0

Das Komma ist kein Fehler, weil es sich bei diesen beiden Angaben um 32-Bit-Werte handelt. Jede Stelle ist also eins der vier Bytes. Im Gegensatz dazu handelt es sich hier um Strings:

            VALUE "FileVersion", "1.0.0.0\0"
            VALUE "ProductVersion", "1,0,0,0\0"

Es wäre also in dem Fall unerheblich, ob Sie zur Trennung der einzelnen Stellen einen Punkt oder ein Komma verwenden. Nur dürfen Sie bei all diesen Strings (s. auch oben) nicht die abschließende Null vergessen, die ich in dem Auszug fett markiert dargestellt habe.
Eine Regel gibt es für diesen Stringblock in dem Sinn eigentlich nicht. Die oben gezeigten Werte sind üblich, Sie könnten aber auch eigene Strings definieren, die dann natürlich auch angezeigt werden:

            VALUE "Grüße gehen an", "...\0"




Versionsinformationen im Programm auslesen

Zum Auslesen von Versioninformationen benötigen wir ein paar Funktionen aus dem API. Zum einen müssen wir herausfinden, wie groß diese Ressource ist. Dazu benutzen wir "GetFileVersionInfoSize", die den entsprechenden Wert zurückgibt.
Wenn der Wert größer als Null ist, reservieren wir entsprechend Speicher und laden den Versionsblock mit der Funktion "GetFileVersionInfo"

vis           := GetFileVersionInfoSize(pchar(FileName),dummy);
if(vis > 0) then
begin
  GetMem(vi,vis);
  try
    GetFileVersionInfo(pchar(Filename),0,vis,vi);
    if(vi = nil) then exit;

Wie Sie in der o.g. Beispielressource sehen können, befinden sich die gesuchten Strings in einem Block namens "StringFileInfo". Darunter befindet sich ein weiterer Block namens "040704b0", dessen Name allerdings variieren kann. Aus dem Grund muss vorher der zweite Block, "VarFileInfo", ausgelesen werden, dessen "Translation"-Wert mit dem o.g. Block identisch ist. Der "Translation"-Wert ist ein longint, dessen zwei Stellen (word) Sie im Beispiel sehen können.
Um den Wert auszulesen, verwenden wir "VerQueryValue", die zum einen den Zeiger auf den Speicher benötigt, gefolgt von der Angabe des gewünschten Blocks, dem Zielzeiger als dritten Parameter und einer Variablen für die Größe ganz am Schluss

    VerQueryValue(vi,'\\VarFileInfo\\Translation',translation,vis);
    if(translation = nil) then exit;

Der Zeiger "translation" zeigt nun auf den Wert. Um auf den Blocknamen (im Beispiel "040704b0") zu kommen, reicht es aus, den longint hexadezimal formatiert zu verwenden. Der Einfachheit halber kann man dazu "Format" benutzen. Auf die Weise sparen wir auch das Hantieren mit weiteren Variablen, weil wir den gesuchten Stringnamen gleich anhängen:

    VerQueryValue(vi,
      pchar(Format('\\StringFileInfo\\%.4x%.4x\\%s',
        [LOWORD(longint(translation^)),HIWORD(longint(translation^)),
        BlockKey])),ip,vis);
    if(ip = nil) then exit;
SetString(Result,pchar(ip),vis); finally FreeMem(vi); end; end;

Das Ergebnis ist die Funktion "GetFileInfo", die Sie zum Auslesen aller Stringwerte des "StringFileInfo"-Blocks verwenden können; bspw. für die Beschreibung:

SetDlgItemText(hDlg,CID_LABEL1,
  pchar(GetFileInfo(paramstr(0),'FileDescription')));

und die auch im Beispielprogramm benutzt wird.


Der feste Versionsblock

Dabei handelt es sich um die Werte am Anfang,

 FILEVERSION 1,0,0,0
 PRODUCTVERSION 1,0,0,0
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x10004L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L

Diese müssen separat abgefragt werden, was allerdings auch etwas einfacher wird, da es dafür ein spezielles Record mit allen Daten gibt, auf die man einfacher zugreifen kann. Die Grundfunktion sieht auch ähnlich aus, nur dass diesmal die beiden Blöcke keine Rolle spielen, sondern dass wir nach dem Reservieren des Speichers und dem Laden der Versionsressource auf den Hauptblock zugreifen

    VerQueryValue(vi,'\\',pointer(FixBuf),dummy);
    if(FixBuf = nil) then exit;

"FixBuf" ist ein Zeiger auf ein Record vom Typ TVSFixedFileInfo. Dieses Record enthält alle gezeigten Werte. Um bspw. die Versionsnummer als Ergebnis zu erhalten, genügt die folgende Formatierung:

    Result := Format(FormatStr,
     [(FixBuf^.dwFileVersionMS and $FFFF0000) shr 16,
       FixBuf^.dwFileVersionMS and $0000FFFF,
      (FixBuf^.dwFileVersionLS and $FFFF0000) shr 16,
       FixBuf^.dwFileVersionLS and $0000FFFF]);