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







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 です.


実行時パッケージを使用した上のサンプルの場合    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 のサイズ比較用アプリ

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 というコンポーネント群を使用すると,非常に小さな実行ファイルのアプリケーションを作成することができます.ただし,KOL/MCX は Delphi が実装している VCL の全ての機能を提供しているわけではありません.




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


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

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


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