Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2013年04月09日] [更新 2018年09月17日] Delphi サンプルプログラム集
   
005_フォームを実装した実行時パッケージの作成
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
005_FormInPackage.zip [10,684 KB] 2018年09月04日版(EXE同梱)







Delphi の実行時パッケージ


Delphi は,設計時にいろいろなパッケージに格納されてるコード類を読み込んでコンパイルし,実行ファイルを作成します.この時に使用するパッケージを「設計時パッケージ」と言っています.
一方,実行時に,コンパイル済みのパッケージを使用する方法もあります.この動的にロードして使用するパッケージを「実行時パッケージ」と言っています.

本ページのサンプルは,この実行時パッケージのシンプルなテストです.パッケージにフォームを追加して,そのフォームを表示するだけです.

実行時パッケージは,Delphi のバージョンに依存します.実行時パッケージを使用するアプリケーションは,その実行時パッケージと同じバージョンの Delphi で開発する必要があります.異なるバージョンの Delphi で作成した実行時パッケージは,別のバージョンの Delphi では利用できません.





01_パッケージを作成してフォームを追加


設計時パッケージも実行時パッケージも,どちらも同じです.設計時に使用するか,実行に動的にロードして使用するかの違いです.
[ファイル] から [新規作成] で [パッケージ] を選択します.下図のように新しいプロジェクトが作成されます.これを選択してマウスの右ボタンクリックでポップアップメニューを表示します.
[新規作成] で [フォーム] を選択します.これで,このパッケージにフォームが 1 つ追加されたことになります.作成済みのフォームも追加できます.


図1
フォームを追加

フォームを追加したら,次のように設定して,このパッケージのプロジェクトを保存しておくことにします.今回はテストプログラムのフォルダ内に,[Package] というフォルダを作成し,そこに保存しました.
  • (1) 追加したフォームの名前を SubForm とする
  • (2) そのフォームの Visible プロパティの値を False とする
  • (3) ユニットの名前を SubFormUnit で保存
  • (4) プロジェクトの名前を Test_Package として保存
パッケージは DLL と同じように扱えます.したがって,この TSubForm を使用する関数類をこのフォームのユニット内に定義して,それらの関数を外部から使用するようにできますが,ここでは別の方法でこの TSubForm を使用することにします.

追加したフォームのコードを下のコードのようにします.RegisterClass(TSubForm) でこのクラスを登録し,終了後に UnRegisterClass(TSubForm) で登録を解除します.


リスト1
テストに使用したパッケージ内のフォームのコード
今回のテストで使用したパッケージは,このフォームだけ
unit SubFormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TSubForm = class(TForm)
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  SubForm: TSubForm;

implementation

{$R *.dfm}

procedure TSubForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

initialization
  RegisterClass(TSubForm);

finalization
  UnRegisterClass(TSubForm);

end.


コードを追加したら,[再構築] または [ビルド] あるいは [コンパイル] します.
コンパイルは [Ctrl] + [F9] で出来ます.この時,下図のようなダイアログが現れます.[OK] ボタンをクリックします.このダイアログで追加されるのは,実行時に必要なパッケージです.必要なパッケージは,パッケージ内でどのようなコーディングをしているかによって異なります.
この追加パッケージのリストは,パッケージの .dpk ファイル内の requires 部に記述されます.

コンパイルしたら IDE を閉じます.


[備考]
古い Delphi の .dpk ファイルは,そのバージョン専用となっていますが,最近の Delphi では汎用的になっています.そのため,パッケージ形式で提供されているコンポーネント類は,該当バージョンのパッケージがなくても,ソースコードがあれば,再構築することによって,ほとんどの場合インストールが可能となっています.


図2
追加パッケージの設定




02_パッケージを使用するプロジェクトを作成


新規に VCL フォームアプリケーションを作成して,フォームにボタンを 1 つ配置します.


図3
設計時画面

[プロジェクト] [オプション] の設定ダイアログで [パッケージ] を選択します.
[実行時パッケージを使って構築] にチェックを入れます.この実行時パッケージ名の欄に,上で作成したパッケージの名前,Test_Package.bpl を入力します.今回はこの 1 つだけを使用します.複数のパッケージを使用する場合は,; で区切って指定します.

今回はカスタムパッケージを使用するわけですが,カスタムパッケージを使用しないで,実行時パッケージを使用したアプリを開発する場合は,vcl と rtl を指定しておきます.


図4
実行時パッケージの設定
  • 今回は Test_Package だけ指定
  • vcl, rtl も必要なので追加してもよい
  • 空欄にすると,実行時時パッケージと同じのを全て指定したことになる

パッケージに登録したコンポーネントを実行時に (動的に生成して) 使用する時は,そのコンポーネントのユニット名を usse 部に追加して利用しますが,今回のテストでは,実行時に LoadPackage でパッケージをロードします.
実行時にパッケージをロードすることにより,EXE とパッケージの機能を分離でき,パッケージを利用するプロジェクトを再コンパイルしなくてもパッケージでの変更が反映できます.

uses 部にパッケージ内のユニット名を追加しませんから,そのユニット内で定義しているクラス型の変数は定義できません.したがって,パッケージ内のクラスで追加した変数やメソッド類にはアクセスできません.今回の実行時パッケージの利用方法は,そのパッケージ内で処理を完了させる場合の処理に向いていると言えます.

パッケージからのメッセージの受信には,ローカルフックが使えます.グローバルフックを使う必要はありません.TApplication.OnMessage では受信できません.

リスト2
パッケージを使用するテスト用プロジェクトのコード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    HandleModule : HModule;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  Form1のOnCreateイベント処理
//  パッケージをロードする
//-----------------------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
  HandleModule := LoadPackage('Package\Test_Package.bpl');
end;

//-----------------------------------------------------------------------------
//  Form1のOnDestroyイベント処理
//  パッケージをアンロードする
//-----------------------------------------------------------------------------
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if HandleModule <> 0 then UnloadPackage(HandleModule);
end;

//-----------------------------------------------------------------------------
//  パッケージ内のフォームをモーダル表示する
//
//  [注意]
//  パッケージ内のユニットをusesして使用しないこと
//  静的ロードとなってしまい,パッケージを更新してもEXE実行時に反映されない
//  通常のユニットの利用と同じになってしまう
//-----------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
  AClass : TPersistentClass;
  pForm  : TForm;
  AForm  : TForm;
begin
  if HandleModule = 0 then exit;

  AClass := GetClass('TSubForm');
  if AClass <> nil then begin
    //パッケージ内のTSubFormのポインタとしてpFormを取得
    pForm := TForm(TPersistentClass(AClass).Create);

    //pFormクラスのフォームを生成
    //パッケージ内のSubFormのVisibleプロパティの値をFalseにしておく
    AForm := pForm.Create(Application);
    try
      AForm.Top  := Self.Top  + 70;
      AForm.Left := Self.Left + 50;
      AForm.Caption := 'テストフォーム';
      AForm.ShowModal;
    finally
      AForm.Free;
    end;
  end;
end;

end.




03_アプリの配布に必要な実行時パッケージと実行結果


これまでの作業で,パッケージを使用するテストプログラムを作成して,Delphi がインストールされていない環境で実行すると,
  • コンピュータに vcl150.bpl がないため,プログラムを開始できません
  • コンピュータに rtl150.bpl がないため,プログラムを開始できません
というエラーが発生してプログラムを実行できません.このエラーは Delphi XE の場合です.他のバージョンの場合は,数字の箇所が違います.実行時パッケージを使用する場合は,そのアプリ専用のコンパイル済みパッケージ以外にも,そのパッケージで必要な他のパッケージ類も必要です.
実行時パッケージは,ランタイムパッケージとも言います.


図5
パッケージを検出できない場合

今回は,rtl150.bpl と vcl150.bpl が必要です.これらのコンパイル済のパッケージが,実行環境のコンピュータのシステム的にパスの通ったフォルダに必要です.
今回は,開発環境のシステムフォルダ内にあるこれらのファイルを,テストプログラムのフォルダにコピーしました.筆者の開発環境は,Windows 7 の 64 ビット版ですので,Windows の SysWOW64 からコピーしました.32 ビット版の Windows の場合は System32 フォルダ内にあります.


図6
必要なパッケージはシステムフォルダにある

  • 今回のテストに必要なのは,
    rtl150.bpl と vcl150.bpl
  • 多くの場合,この 2 つは必要
  • これらは,コンパイル済のパッケージ

実行結果です.パッケージ内のフォームをモーダル表示するだけです.


図7
実行結果

次に,パッケージ内の動作が独立しているかを確認してみます.実行時パッケージを使用するアプリを終了しておきます.
  • (1) Test_Package.dpk または Test_Package.dproj を IDE で開く
  • (2) SubFormUnit のフォームを表示し,オブジェクトインスペクタで Color の色を変更
  • (3) [Ctrl] + [F9] でコンパイルする
  • (4) Project1.exe を起動し,Button1 をクリック
実行結果の例です.
実行時パッケージを使用する側のアプリのプロジェクトを再コンパイルしなくても,実行時パッケージの変更が反映されます.実行時パッケージをアドオンのように使えることを意味しています.

拡張子が .bpl のファイル,つまり,コンパイルス済みのパッケージは,.dcu ファイルと同じで,Delphi のバージョンに依存するファイルです.他のバージョンの Delphi で作成した EXE からは利用できません.


図8
実行時パッケージの変更結果




04_実行形式ファイル ( EXE ) のサイズと実行時パッケージ


実行時パッケージを使用したアプリの実行形式ファイルのサイズは,非常に小さくなります.
上のサンプルの場合は以下の値となりました.
作成環境は,Windows 7 U64(SP1) + Delphi XE です.

EXE のサイズは小さくなりますが,作成したアプリの配布には実行時パッケージが必要です.したがって,結果的に配布ファイル全体のサイズは EXE 単体よりも大きくなります.



実行時パッケージを使用した上のサンプルの場合    107 KB
通常の,フォームが 2 つだけのプロジェクトの場合    905 KB


参考のため,Halbow 資料館のサンプル [23−5 モーダルダイアログクラス] をコンパイルしてみました.コードは,Delphi XE でもコンパイルできるように修正しています.


Windows 7 U64(SP1) + Delphi 7 Pro でコンパイル     31 KB
Windows 7 U64(SP1) + Delphi XE Pro でコンパイル    153 KB


下の表は,VCL フォームアプリケーションを新規に作成し,ボタンを 1 つ配置しただけのプロジェクトを作成し,EXE のサイズを比較したものです.下図がその実行画面です.実行時パッケージを使用した EXE のサイズとはかなり違います.
また,Delphi のバージョンが上がるほど大きくなっていくのが分かります.VCL 類の内容は,Delphi のバージョンが上がる度に機能が増えています.そのために,コンパイルした結果の EXE のサイズも増大していくことになります.
ポインタのサイズだけを考えても 2 倍となりますから,64 ビット版の EXE 方が 32 ビット版よりも当然大きくなります.

機能の増加と共にサイズが増えるは,Delphi だけの現象ではありません.
Windows 95 のシステム要件は 75 MB でしたが,Windows 7 のシステム要件は 16 GB です.64 ビット版の Windows 7 では 20 GBです.Delphi で作成した EXE のサイズに驚く方をネットで見かけます.そのような方は,是非,OS のサイズの大きさにも驚いてください.


図9
EXE のサイズ比較用アプリ
  • 下表の exe サイズは,プロジェクトのビルド構成が Release 相当の時の値

Delphi 4 Pro    279 KB
Delphi 5 Pro    291 KB
Delphi 7 Pro    363 KB
Delphi 2007-R2 Pro    402 KB
Delphi 2009(UP3) Pro    497 KB
Delphi 2010(UP4) Pro    809 KB
Delphi XE Pro    905 KB
Delphi XE2(UP4) Pro VCL-32  1,559 KB
Delphi XE2(UP4) Pro VCL-64  1,981 KB
Delphi XE3(UP1) Pro VCL-32  2,226 KB
Delphi XE3(UP1) Pro VCL-64  3,051 KB
Delphi XE5(UP2) Pro VCL-32  2,051 KB
Delphi XE5(UP2) Pro VCL-64  3,137 KB
Delphi XE7 Pro VCL-32  2,221 KB
Delphi XE7 Pro VCL-64  3,523 KB


Visual Basic で開発したアプリケーションの実行形式ファイルのサイズも,.NET Framework アプリケーションの実行形式ファイルのサイズも,Delphi で開発した実行形式ファイルのサイズに比べると,非常に小さくなります.

しかし,Visual Basic で作成したアプリは,EXE 単体では動作しません.
実行時にランタイムライブラリが必要です.このラインタイムライブラリは,Visual Basic のバージョンによって異なります.

.NET Framework アプリも EXE 単体では動作しません.
システムに .NET Framewor がインストールされていなければなりません.Windows Vista では標準実装となりましたが,より新しいバージョンで開発したアプリは,古いバージョンの .NET Framework では動作しない場合があります.また,Windows のバージョンによっては,動作しない場合もあります.


Delphi の実行時パッケージは,Visual Basic で言えば,ランタイムライブラリに相当することになります.また,.NET Framework のアプリが,実行時に DLL をロードして動作するのにも似ています.
つまり,必要な機能をパッケージとし,それを実行時に読み込むことにより,EXE のサイズが小さくなるわけです.
実行時パッケージを使用しない EXE は単体で動作しますが,必要な機能も EXE の中に含まれます.そのため EXE のサイズは必然的に大きくなります.

EXE のサイズが大きいからと言って,必ずしも起動時間遅くなるわけではありません.むしろ,起動時に多くの DLL をロードする .NET Framework アプリの方が起動に時間がかかります.コーディングによる影響の方が大きいでしょう.

Delphi の pas ファイルをコンパイルすると,dcu というコンパイル済みのファイルができます.これは Delphi のバージョンに依存します.同じように,コンパイルしたパッケージも Delphi のバージョンに依存します.Visual Basic のランタイムライブラリ,.NET Framework のバージョン依存関係と似ています.


[備考 1]
この文章中の Visial Basic は,.NET 版でないバージョンを指しています.Visial Basic には .NET 版もあります.

[備考 2]
Delphi は,Windows が提供している機能を,より汎用的にして,より高速にアプリの開発ができるように機能を拡張しています.したがって,コンポーネント類を配置して開発できるという便利さと,VCL の豊富な機能を利用することを放棄して,Halbow 資料館のサンプルのように,Windows API を駆使して作成すれば,実行ファイルのサイズを小さくすることができます.
また,KOL/MCK (Key Objects Library / Mirror Classes Kit) というコンポーネント群を使用すると,非常に小さな実行ファイルのアプリケーションを作成することができます.ただし,KOL/MCX は Delphi が実装している VCL の全ての機能を提供しているわけではありません.




05_実行形式ファイル ( EXE ) のサイズとビルド構成


前項では,Delphi のバージョンの違いによる EXE のサイズの違いを確認しました.

説明するまでもありませんが,Delphi のバージョンが同じでも,EXE ファイルのサイズはそのプログラムによって違います.Delphi で作成するプログラムはプロジェクトの構成と内容で違います.
フォームやユニット,コンポーネント (コントロール) やリソース等,ビルド構成,ターゲットプラットフォームもプロジェクトの構成と内容の一部です.したがって,使用するユニット類とそれらのユニットファイルの内容によっても違ってくることになります.あるいは [オプション] の [コンパイラ],[リンク] の設定等によっても変わることになります.

実行時パッケージを使用するのも,プロジェクトの構成方法の 1 つですが,ここでは実行時パッケージを使用しない簡単なプログラムで EXE のサイズの違いを調べてみます.



[備考 1]
本項のテストは Windows 7 U64(SP1) + Delphi XE5(UP2) Pro で行っています.
記事中の VCL-32 は,VCL フォームの 32 ビットアプリケーションという意味です.

[備考 2]
本項のテスト結果として表示しているのはファイルのサイズです,単位はバイトです.
フォームが 1 つの,ビルド構成が Debug の時の EXE のサイズは,エクスプローラでは1 10,543 KB ですが,本項のサンプルでは 10,796,995 バイト です.エクスプローラでのサイズの表示は KB 単位です.1 KB は 1024 バイトですから,10796995 / 1024 = 10543 (小数点以下切り捨て) という計算で,エクスプローラでの表示値に換算できます.


まず最初に,フォームを使用しないプログラムの EXE のサイズを調べてみることにします.
以下はフォームを使用しない簡単なアプリケーションのプロジェクトの例です.使用しているユニットファイルは Winapi.Windows だけです.EXE ファイルのサイズは 29.2 KB しかありません.



リスト3
フォームを使用しない簡単なプログラム
program Project_01; 

uses 
  Winapi.Windows;

begin
  MessageBox(0, 'Exit', 'Exit', MB_ICONINFORMATION);
end.


図10
フォームを使用しない簡単なアプリの EXE のサイズ
  • 29.2 KB しかない
  • ターゲットプラットフォームは 32 ビット Windows
  • ビルド構成は Release 相当

上のプログラムに,以下のように Vcl.Forms ユニットを追加して EXE を作成すると,2.04 MB もの大きさになります.
このことから,使用しないユニットは uses には追加しない,プロジェクトで使用しないユニットがあればプロジェクトから削除すれば,EXE のサイズを小さくできる可能性があると言えます.

このプロジェクトの [オプション] の設定を,以下の記事の表の [Debug 構成のデフォルト] の値にしてコンパイルすると,EXE ファイルのサイズは約 10.7 MB になります.この値は次のフォーム 1 だけのアプリの EXE ファイルのサイズとほとんど同じです.



リスト4
上のプログラムに Vcl.Forms ユニットだけを追加したプログラム
program Project_02; 

uses 
  Winapi.Windows, 
  Vcl.Forms; 

begin 
  MessageBox(0, 'Exit', 'Exit', MB_ICONINFORMATION); 
end. 


図11
フォームを使用しない簡単なアプリの EXE のサイズ
  • uses に Vcl.Forms だけを追加したプログラム
  • 2.04 MB になっている
  • ターゲットプラットフォームは 32 ビット Windows
  • ビルド構成は Release 相当

今度は新規に VCL フォームアプリケーションを作成します.下図のようにフォームが 1 つだけのアプリケーションです.
新規に VCL フォームアプリケーションを作成した時のビルド構成のデフォルトは Debug です


図12
フォームが 1 つだけのアプリ

作成された EXE ファイルのサイズは約 10.8 MB となっています.


図13
フォームが 1 だけのプログラム
  • ビルド構成が Debug の時
  • EXE のサイズは約 10.8 MB

同じプロジェクトのビルド構成を Release にすると下図のように,EXE のサイズが 2.10 MB と小さい値となります.Release 構成ではデバッガ関係の処理機能をリンクしないため,EXE にデバッガ関係の機能や情報が格納されません.その分 EXE のサイズが小さくなります.前項の表に掲載している EXE の値は,この Release 構成での値です.

ビルド構成の Release はリリース,つまり配布用です.一般的には,配布用の EXE,つまり,配布用のアプリケーションは Release 構成の EXE とします.



[備考 3]
ビルド構成等,コンパイルの動作仕様を変更するオプションを変更した場合,一般的にはプロジェクトのビルド(再構築) が必要です.[Shift] + [F9] でビルド (再構築) できます.


図14
フォームが 1 つだけのプログラム
  • ビルド構成が Release の時
  • EXE のサイズは約 2.10 MB

上のプログラムのプロジェクトに TButton コンポーネントを 1,000 個配置して EXE のサイズを調べてみました.上のフォームが 1 つだけプログラムの EXE よりも 120,832 バイト増加して,2,220,032 バイトとなりました.


図15
フォームに TButton コンポーネントを 1,000 個配置
  • [Shift] + [クリック] でパレットのコンポーネントを選択
  • クリックの連続でフォームに連続配置
図16
TButton を 1,000 個配置したプログラムの EXE のサイズ
  • 上のプログラムの EXE よりも 約 120 KB バイト増加

ここまでのテスト結果は以下のようになっています.
この表は,Windows 7 U64(SP1) + Delphi XE5 (UP2) Pro で作成した 32 ビットの EXE の値です.

フォームなしアプリ (Release 構成相当).uses は Winapi.Windows のみ 29,184 バイト
フォームなしアプリ (Release 構成相当).uses は Winapi.Windows, Vcl.Forms 2,035,200 バイト
フォームなしアプリ (Debug 構成相当).uses は Winapi.Windows, Vcl.Forms 10,732,413 バイト
新規 VCL フォームアプリ (Debug 構成) 10,796,995 バイト
新規 VCL フォームアプリ (Release 構成) 2,099,200 バイト
新規 VCL フォームアプリ (Release 構成) に TButton を 1000 個配置 2,220,032 バイト


下表は,TButton をフォームに配置した時の EXE のサイズの増加を調べた結果です.
フォームに配置する TButton コンポーネントの数を増やすと徐々に EXE のサイズも増加していくことが確認できます.
この表は,Windows 7 U64(SP1) + Delphi XE5 (UP2) Pro における値です.

新規 VCL フォームアプリ (32 ビット, Release 構成)    2,099,200 バイト
TButton を 1 個配置    2,099,200 バイト
TButton を 10 個配置    2,100,236 バイト
TButton を 20 個配置    2,102,272 バイト
TButton を 100 個配置    2,113,536 バイト


次の表は,コンポーネントパレットの [dbGO] タブにある TADOQuery コンポーネントをフォームに配置した時の EXE のサイズの増加を調べた結果です.
TADOQuery コンポーネントをフォームに配置すると,uses に Data.DB, Data.Win.ADODB が追加されるので EXE サイズが約 500 KB 以上大きくなります.その後は TButoon の時と同じように徐々に増えていくことが確認できます.
この表も Windows 7 U64(SP1) + Delphi XE5 (UP2) Pro における値です.

新規 VCL フォームアプリ (32 ビット, Release 構成)    2,099,200 バイト
TADOQuery を 1 個配置    2,642,432 バイト
TADOQuery を 10 個配置    2,643,456 バイト
TADOQuery を 20 個配置    2,646,480 バイト




06_実行形式ファイル ( EXE ) のサイズと EXE の起動時間


作成した実行ファイル,つまり EXE ファイルのサイズが大きいからと言って,必ずしもその EXE の起動に時間がかかるわけではありません.添付の Project1.exe は約 50 MB ありますが,数メガバイトの EXE と起動時間に大差ないことが確認できます.

EXE の起動に時間がかかる要因はいろいろ考えられますが,起動時の初期化処理に時間がかかると,起動に時間がかかることは容易に想像できます.体感的な起動時間を早くするためには,初期化の処理を見直す必要があるかも知れません.
ユーザインターフェイスとしては,メインフォームを早い段階で表示する工夫が必要と思われます.そうすれば,ユーザは,確かにそのアプリが起動したことを確認できます.残念ながら,アプリケーションが起動時にどのような処理を行う必要があるかは,開発するアプリケーションによります.ここで一般的な方法を提案することはできません.
本体の EXE の起動待ちの間,スプラッシュウィンドウを表示することもあるようです.


[備考]
ネット上に,Delphi の欠点は EXE のサイズが大きいことであるという記事があります.
「欠点」は,それによって何かしらの問題が発生する場合に使用する用語です.筆者の場合,EXE のサイズによる問題が発生したことはありません.
プログラム言語を比較する際,.NET Framework 用と Delphi では,動作の仕組みが違います.EXE のサイズは「優劣」の比較の対象にはなりません.