2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Inno Setupに新設されたFile Version functions解説

Last updated at Posted at 2021-01-11

File Version functions

Inno SetupのV6.1からFile Version functionsというカテゴリの関数が追加されました。What's Newの文章から引用します。

Added new GetPackedVersion, PackVersionNumbers, PackVersionComponents, ComparePackedVersion, SamePackedVersion, UnpackVersionNumbers, UnpackVersionComponents, and VersionToStr support functions.

ヘルプにある関数のリファレンスを見ると説明がほとんどないので使い方を調べてみました。

ファイルのバージョニング・ルール

マイクロソフトによるファイルのバージョニング・ルールを知っていると、これら関数への理解が早いです。

VERSIONINFO resource
https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource

一般にファイルのバージョンは、a.b.c.dのような4つの数値の並びで示されますが、各数値は16bit整数で表されます。ここから各数値の範囲は0~65535であることが分かります。すなわちバージョンとして表現できるのは、0.0.0.065535.65535.65535.65535の範囲、ということになります。また上記のドキュメントを読むと、32bit整数を2つ使って表記する方式もあることが分かります。これを踏まえて、関数名に使われている単語の意味を定義します。

単語 意味
VersionComponents 16bit整数4つの形式
VersionNumbers 32bit整数2つの形式
PackedVersion 64bit整数1つの形式
Get ファイルからバージョンを取得する
Compare 2つのバージョンを比較して、大小関係を取得する
same 2つのバージョンを比較して、同じかどうかを取得する
Pack PackedVersionに変換する
Unpack PackedVersionから別の形式に変換する

ここまで分かってしまえば、各関数の使い方も分かってきます。また、ファイルバージョンは64bitあれば表現できるのだから、64bit整数に変換してバージョン比較できる、というロジックも見えてきます。

EXE/DLLファイルとMSIファイルのバージョニング・ルールは異なる

誤解してはいけないのは、EXE/DLLファイルとMSIファイルのバージョニング・ルールは異なる、ということです。MSIファイルのバージョニングは、以下に解説があります。

ProductVersion property
https://docs.microsoft.com/en-us/windows/win32/msi/productversion

基本的に3つの数値を「.」で区切って並べますが、4つの数値を使うこともできます。しかし、4番目の数値が付いていても、Windows Installerはこの数値を無視します。使える番号の範囲も異なります。バージョンとして使える範囲は0.0.0255.255.65535となります。

とはいえ、ソースを見れば分かるように、新設された関数は単純に数値演算しているだけなので、MSIファイルのバージョン比較にも使えそうです。

実際に使ってみる

実験用に下記の3つのEXEファイルを作り、インストーラーと同じディレクトリに置きます。

ファイル名 ファイルバージョン
f8_4_2_1.exe 8.4.2.1
f1_1_1_1.exe 1.1.1.1
f65535_65535_65535_65535.exe 65535.65535.65535.65535

各関数の動作を確認します。各関数のエラーをチェックしていないので、実際に使用するときは適宜追加してください。

GetVersionNumbersString()

var
  FilenameA, VersionA: String;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  GetVersionNumbersString(FilenameA, VersionA);
  MsgBox('GetVersionNumbersString(FilenameA, VersionA)'+#13#10+#13#10
    +'VersionA : '+VersionA
    , mbInformation, MB_OK);
end;

GetVersionNumbersString.png

GetVersionComponents()

var
  FilenameA: String;
  MajorA, MinorA, RevisionA, BuildA: Word;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA);
  MsgBox('GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA)'+#13#10+#13#10
    +'MajorA : '+IntToStr(MajorA)+#13#10
    +'MinorA : '+IntToStr(MinorA)+#13#10
    +'RevisionA : '+IntToStr(RevisionA)+#13#10
    +'BuildA : '+IntToStr(BuildA)
    , mbInformation, MB_OK);
end;

GetVersionComponents.png

GetVersionNumbers()

var
  FilenameA: String;
  VersionMSA, VersionLSA: Cardinal;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  GetVersionNumbers(FilenameA, VersionMSA, VersionLSA);
  MsgBox('GetVersionNumbers(FilenameA, VersionMSA, VersionLSA)'+#13#10+#13#10
    +'VersionMSA : '+IntToStr(VersionMSA)+#13#10
    +'VersionLSA : '+IntToStr(VersionLSA)
    , mbInformation, MB_OK);
end;

GetVersionNumbers.png

このような数字になるのは、以下の計算がされているからです。

\begin{align}
VersionMSA&=MajorA * 2^{16} + MinorA\\
&=8 * 2^{16} + 4\\
&=524292
\end{align}
\begin{align}
VersionLSA&=RevisionA * 2^{16} + BuildA\\
&=2 * 2^{16} + 1\\
&=131073
\end{align}

検算してみます1

var
  Major32, Minor32, Revision32, Build32: Cardinal;
begin
  Major32 := 8;
  Minor32 := 4;
  Revision32 := 2;
  Build32 := 1;
  MsgBox('Verify VersionNumbers'+#13#10+#13#10
    +'(MajorA shl 16)+MinorA : '+IntToStr((Major32 shl 16)+Minor32)+#13#10
    +'(RevisionA shl 16)+BuildA : '+IntToStr((Revision32 shl 16)+Build32)+#13#10
    , mbInformation, MB_OK);
end;

VerifyVersionNumbers.png

GetPackedVersion()

var
  FilenameA: String;
  VersionA64: Int64;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  GetPackedVersion(FilenameA, VersionA64);
  MsgBox('GetPackedVersion(FilenameA, VersionA64)'+#13#10+#13#10
    +'VersionA64 : '+IntToStr(VersionA64)
    , mbInformation, MB_OK);
end;

GetPackedVersion.png

このような数字になるのは、以下の計算がされているからです。

\begin{align}
VersionA64&=MajorA * 2^{48} + MinorA * 2^{32} + RevisionA * 2^{16} + BuildA\\
&=8 * 2^{48} + 4 * 2^{32} + 2 * 2^{16} + 1\\
&=2251816993685505
\end{align}

検算してみます1

var
  Major64, Minor64, Revision64, Build64: Int64;
begin
  Major64 := 8;
  Minor64 := 4;
  Revision64 := 2;
  Build64 := 1;
  MsgBox('Verify PackedVersion'+#13#10+#13#10
    +'(MajorA shl 48)+(MinorA shl 32)+(RevisionA shl 16)+BuildA : '
    +IntToStr((Major64 shl 48)+(Minor64 shl 32)+(Revision64 shl 16)+Build64)
    , mbInformation, MB_OK);
end;

VerifyPackedVersion.png

ComparePackedVersion()

var
  FilenameA, FilenameB, FilenameC, VersionA, VersionB, VersionC: String;
  VersionA64, VersionB64, VersionC64: Int64;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  FilenameB := ExpandConstant('{src}\f1_1_1_1.exe');
  FilenameC := ExpandConstant('{src}\f65535_65535_65535_65535.exe');
  GetVersionNumbersString(FilenameA, VersionA);
  GetVersionNumbersString(FilenameB, VersionB);
  GetVersionNumbersString(FilenameC, VersionC);
  GetPackedVersion(FilenameA, VersionA64);
  GetPackedVersion(FilenameB, VersionB64);
  GetPackedVersion(FilenameC, VersionC64);
  MsgBox('VersionA : '+VersionA+#13#10
    +'VersionB : '+VersionB+#13#10
    +'VersionC : '+VersionC+#13#10+#13#10
    +'ComparePackedVersion(VersionA64, VersionB64)'+#13#10
    +IntToStr(ComparePackedVersion(VersionA64, VersionB64))+#13#10+#13#10
    +'ComparePackedVersion(VersionA64, VersionC64)'+#13#10
    +IntToStr(ComparePackedVersion(VersionA64, VersionC64))+#13#10+#13#10
    +'ComparePackedVersion(VersionA64, VersionA64)'+#13#10
    +IntToStr(ComparePackedVersion(VersionA64, VersionA64))
    , mbInformation, MB_OK);
end;

ComparePackedVersion.png

SamePackedVersion

var
  FilenameA, FilenameB, CompResultA, CompResultB: String;
  VersionA64, VersionB64: Int64;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  FilenameB := ExpandConstant('{src}\f1_1_1_1.exe');
  GetPackedVersion(FilenameA, VersionA64);
  GetPackedVersion(FilenameB, VersionB64);
  if SamePackedVersion(VersionA64, VersionB64) then begin
    CompResultA := 'True';
  end else begin
    CompResultA := 'False';
  end;
  if SamePackedVersion(VersionA64, VersionA64) then begin
    CompResultB := 'True';
  end else begin
    CompResultB := 'False';
  end;
  MsgBox('VersionA : '+VersionA+#13#10
    +'VersionB : '+VersionB+#13#10+#13#10
    +'SamePackedVersion(VersionA64, VersionB64)'+#13#10
    +CompResultA+#13#10+#13#10
    +'SamePackedVersion(VersionA64, VersionA64)'+#13#10
    +CompResultB+#13#10
    , mbInformation, MB_OK);
end;

SamePackedVersion.png

PackVersionComponents()/PackVersionNumbers()

var
  FilenameA: String;
  MajorA, MinorA, RevisionA, BuildA: Word;
  VersionMSA, VersionLSA: Cardinal;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA);
  GetVersionNumbers(FilenameA, VersionMSA, VersionLSA);
  MsgBox('PackVersionComponents(MajorA, MinorA, RevisionA, BuildA)'+#13#10
    +IntToStr(PackVersionComponents(MajorA, MinorA, RevisionA, BuildA))+#13#10+#13#10
    +'PackVersionNumbers(VersionMSA, VersionLSA)'+#13#10
    +IntToStr(PackVersionNumbers(VersionMSA, VersionLSA))
    , mbInformation, MB_OK);
end;

PackVersionNumbers_PackVersionComponents.png

UnpackVersionComponents()/UnpackVersionNumbers()

var
  FilenameA: String;
  MajorB, MinorB, RevisionB, BuildB: Word;
  VersionMSB, VersionLSB: Cardinal;
  VersionA64: Int64;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  GetPackedVersion(FilenameA, VersionA64);
  UnpackVersionComponents(VersionA64, MajorB, MinorB, RevisionB, BuildB);
  UnpackVersionNumbers(VersionA64, VersionMSB, VersionLSB);
  MsgBox('VersionA64 : '+IntToStr(VersionA64)+#13#10+#13#10
    +'UnpackVersionComponents(VersionA64, MajorB, MinorB, RevisionB, BuildB)'+#13#10
    +'MajorB : '+IntToStr(MajorB)+#13#10
    +'MinorB : '+IntToStr(MinorB)+#13#10
    +'RevisionB : '+IntToStr(RevisionB)+#13#10
    +'BuildB : '+IntToStr(BuildB)+#13#10+#13#10
    +'UnpackVersionNumbers(VersionA64, VersionMSB, VersionLSB)'+#13#10
    +'VersionMSB : '+IntToStr(VersionMSB)+#13#10
    +'VersionLSB : '+IntToStr(VersionLSB)
    , mbInformation, MB_OK);
end;

UnpackVersionComponents_UnpackVersionNumbers.png

VersionToStr()

var
  FilenameA: String;
  VersionA64: Int64;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  GetPackedVersion(FilenameA, VersionA64);
  MsgBox('VersionA64 : '+IntToStr(VersionA64)+#13#10+#13#10
    +'VersionToStr(VersionA64)'+#13#10
    +VersionToStr(VersionA64)
    , mbInformation, MB_OK);
end;

VersionToStr.png

実験用にEXEファイルにバージョンを入れる

もちろんVisual Studioを使ったやり方もありますが、私はリソースコンパイラを使った方法がシンプルなのでよく使います。

先ほど紹介したVERSIONINFO resourceの下の方にリソースファイルの例があるので、マネして書いていきます。

f8_4_2_1.rc
#include <winver.h>

#define VER_FILEVERSION             8,4,2,1
#define VER_FILEVERSION_STR         "8.4.2.1\0"
#define VER_PRODUCTVERSION          8,4,2,1
#define VER_PRODUCTVERSION_STR      "8.4.2\0"

VS_VERSION_INFO    VERSIONINFO 
FILEVERSION        VER_FILEVERSION
PRODUCTVERSION     VER_PRODUCTVERSION
FILEFLAGSMASK      VS_FFI_FILEFLAGSMASK 
FILEFLAGS          0x00000000L
FILEOS             VOS__WINDOWS32
FILETYPE           VFT_APP
FILESUBTYPE        VFT2_UNKNOWN
BEGIN
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x0409, 1252
    END

    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904E4"
        BEGIN
            VALUE "CompanyName",      "tohshima"
            VALUE "FileDescription",  "for File Version Test"
            VALUE "FileVersion",      VER_FILEVERSION_STR
            VALUE "InternalName",     "<InternalName>\0"
            VALUE "LegalCopyright",   "Copyright (C) 2021 tohshima.\0"
            VALUE "OriginalFilename", "fx_x_x_x.exe\0"
            VALUE "ProductName",      "fx_x_x_x\0"
            VALUE "ProductVersion",   VER_PRODUCTVERSION_STR
        END 
    END
END

x86 Native Tools Command Promptを起動して、リソースコンパイラで.resファイルに変換します。

rc f8_4_2_1.rc

これで、f8_4_2_1.resが得られます。適当にCのプログラムを作ります。

f8_4_2_1.c
int main(){
	return 0;
}

コンパイルします。

cl f8_4_2_1.c f8_4_2_1.res

これで、バージョン8.4.2.1f8_4_2_1.exeが得られます。

実験用のインストーラーを作る

今回の実験で使用したインストーラーのソースを以下に示します。Uninstallable=noとすることで実行後にテスト環境に何も残さないようにできます。DisableReadyPage=Trueを有効にするために、InitializeWizard()の最後でWizardForm.Show();を実行します。今回は管理者権限で実行する必要がないので、User Account Controlダイアログの表示を抑止するためにPrivilegesRequired=lowestとします。

testFileVersionFunctions.iss
[Setup]
DisableReadyPage=True
DisableReadyMemo=True
DisableFinishedPage=True
AppName=testFileVersionFunctions
CreateAppDir=False
Uninstallable=no
AppVersion=1.0.0
PrivilegesRequired=lowest
OutputDir=.

[Code]
procedure InitializeWizard();
var
  FilenameA, FilenameB, FilenameC, VersionA, VersionB, VersionC, CompResultA, CompResultB: String;
  MajorA, MinorA, RevisionA, BuildA, MajorB, MinorB, RevisionB, BuildB: Word;
  VersionMSA, VersionLSA, VersionMSB, VersionLSB, Major32, Minor32, Revision32, Build32: Cardinal;
  VersionA64, VersionB64, VersionC64, Major64, Minor64, Revision64, Build64: Int64;
begin
  FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
  FilenameB := ExpandConstant('{src}\f1_1_1_1.exe');
  FilenameC := ExpandConstant('{src}\f65535_65535_65535_65535.exe');
  //
  GetVersionNumbersString(FilenameA, VersionA);
  MsgBox('GetVersionNumbersString(FilenameA, VersionA)'+#13#10+#13#10
    +'VersionA : '+VersionA
    , mbInformation, MB_OK);
  //
  GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA);
  MsgBox('GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA)'+#13#10+#13#10
    +'MajorA : '+IntToStr(MajorA)+#13#10
    +'MinorA : '+IntToStr(MinorA)+#13#10
    +'RevisionA : '+IntToStr(RevisionA)+#13#10
    +'BuildA : '+IntToStr(BuildA)
    , mbInformation, MB_OK);
  //
  GetVersionNumbers(FilenameA, VersionMSA, VersionLSA);
  MsgBox('GetVersionNumbers(FilenameA, VersionMSA, VersionLSA)'+#13#10+#13#10
    +'VersionMSA : '+IntToStr(VersionMSA)+#13#10
    +'VersionLSA : '+IntToStr(VersionLSA)
    , mbInformation, MB_OK);
  //
  Major32 := MajorA;
  Minor32 := MinorA;
  Revision32 := RevisionA;
  Build32 := BuildA;
  MsgBox('Verify VersionNumbers'+#13#10+#13#10
    +'(MajorA shl 16)+MinorA : '+IntToStr((Major32 shl 16)+Minor32)+#13#10
    +'(RevisionA shl 16)+BuildA : '+IntToStr((Revision32 shl 16)+Build32)+#13#10
    , mbInformation, MB_OK);
  //
  GetPackedVersion(FilenameA, VersionA64);
  MsgBox('GetPackedVersion(FilenameA, VersionA64)'+#13#10+#13#10
    +'VersionA64 : '+IntToStr(VersionA64)
    , mbInformation, MB_OK);
  //
  Major64 := MajorA;
  Minor64 := MinorA;
  Revision64 := RevisionA;
  Build64 := BuildA;
  MsgBox('Verify PackedVersion'+#13#10+#13#10
    +'(MajorA shl 48)+(MinorA shl 32)+(RevisionA shl 16)+BuildA : '
    +IntToStr((Major64 shl 48)+(Minor64 shl 32)+(Revision64 shl 16)+Build64)
    , mbInformation, MB_OK);
  //
  GetVersionNumbersString(FilenameB, VersionB);
  GetVersionNumbersString(FilenameC, VersionC);
  GetPackedVersion(FilenameB, VersionB64);
  GetPackedVersion(FilenameC, VersionC64);
  MsgBox('VersionA : '+VersionA+#13#10
    +'VersionB : '+VersionB+#13#10
    +'VersionC : '+VersionC+#13#10+#13#10
    +'ComparePackedVersion(VersionA64, VersionB64)'+#13#10
    +IntToStr(ComparePackedVersion(VersionA64, VersionB64))+#13#10+#13#10
    +'ComparePackedVersion(VersionA64, VersionC64)'+#13#10
    +IntToStr(ComparePackedVersion(VersionA64, VersionC64))+#13#10+#13#10
    +'ComparePackedVersion(VersionA64, VersionA64)'+#13#10
    +IntToStr(ComparePackedVersion(VersionA64, VersionA64))
    , mbInformation, MB_OK);
  //
  if SamePackedVersion(VersionA64, VersionB64) then begin
    CompResultA := 'True';
  end else begin
    CompResultA := 'False';
  end;
  if SamePackedVersion(VersionA64, VersionA64) then begin
    CompResultB := 'True';
  end else begin
    CompResultB := 'False';
  end;
  MsgBox('VersionA : '+VersionA+#13#10
    +'VersionB : '+VersionB+#13#10+#13#10
    +'SamePackedVersion(VersionA64, VersionB64)'+#13#10
    +CompResultA+#13#10+#13#10
    +'SamePackedVersion(VersionA64, VersionA64)'+#13#10
    +CompResultB+#13#10
    , mbInformation, MB_OK);
  //
  MsgBox('PackVersionComponents(MajorA, MinorA, RevisionA, BuildA)'+#13#10
    +IntToStr(PackVersionComponents(MajorA, MinorA, RevisionA, BuildA))+#13#10+#13#10
    +'PackVersionNumbers(VersionMSA, VersionLSA)'+#13#10
    +IntToStr(PackVersionNumbers(VersionMSA, VersionLSA))
    , mbInformation, MB_OK);
  //
  UnpackVersionComponents(VersionA64, MajorB, MinorB, RevisionB, BuildB);
  UnpackVersionNumbers(VersionA64, VersionMSB, VersionLSB);
  MsgBox('VersionA64 : '+IntToStr(VersionA64)+#13#10+#13#10
    +'UnpackVersionComponents(VersionA64, MajorB, MinorB, RevisionB, BuildB)'+#13#10
    +'MajorB : '+IntToStr(MajorB)+#13#10
    +'MinorB : '+IntToStr(MinorB)+#13#10
    +'RevisionB : '+IntToStr(RevisionB)+#13#10
    +'BuildB : '+IntToStr(BuildB)+#13#10+#13#10
    +'UnpackVersionNumbers(VersionA64, VersionMSB, VersionLSB)'+#13#10
    +'VersionMSB : '+IntToStr(VersionMSB)+#13#10
    +'VersionLSB : '+IntToStr(VersionLSB)
    , mbInformation, MB_OK);
  //
  MsgBox('VersionA64 : '+IntToStr(VersionA64)+#13#10+#13#10
    +'VersionToStr(VersionA64)'+#13#10
    +VersionToStr(VersionA64)
    , mbInformation, MB_OK);
  //
  WizardForm.Show();  // wpReady画面を出さない
end;
  1. 数式上は2のべき乗を掛け合わせていますが、Pascalで計算する際にはshl演算子(shift left)を使って計算しています。 2

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?