プログラムとユニット(Delphi)
プログラムとユニット:インデックス への移動
このトピックでは、program ヘッダー、unit 宣言構文、uses 句という、Delphi アプリケーションの全体的な構造について説明します。
- 大規模なプログラムをモジュールに分割し、それぞれを個別に編集できる。
- ライブラリを作成し、複数のプログラムで共有できる。
- ソース コードを公開せずに、他の開発者にライブラリを配布できる。
目次
プログラムの構造と構文
実行可能な完全な Delphi アプリケーションは複数のユニット モジュールで構成され、それらはすべて、プロジェクト ファイルと呼ばれる単一のソース コード モジュールによって互いに結び付けられています。 従来の Pascal プログラミングでは、メイン プログラムを含むすべてのソース コードが .pas ファイルに記述されていました。 Embarcadero 製ツールでは、ファイル拡張子 .dpr を使用して、メイン プログラムのソース モジュールを示す一方、他のソース コードの大半は従来の .pas 拡張子を持つユニット ファイルに記述されます。 コンパイラでプロジェクトをビルドするには、プロジェクト ソース ファイルのほか、ユニットごとにソース ファイルかコンパイル済みユニット ファイルのどちらかが必要です。
メモ: 厳密に言えば、プロジェクトでユニットを明示的に使用する必要はなく、System ユニットと SysInit ユニットはすべてのプログラムで自動的に使用されます。
実行可能な Delphi アプリケーションのソース コード ファイルには、以下のものが含まれています。
- program ヘッダー
- uses 句(省略可能)
- 宣言および実行可能文のブロック
コンパイラ(したがって IDE)では、これら 3 つの要素が単一のプロジェクト ファイル(.dpr ファイル)にあることを想定しています。
program ヘッダー
program ヘッダーは実行可能プログラムの名前を指定します。 予約語 program の後に有効な識別子が続き、最後にセミコロンが付きます。 Embarcadero 製ツールを使って開発されたアプリケーションの場合は、この識別子とプロジェクト ソース ファイル名が一致する必要があります。
以下の例は、Editor という名前のプログラムのプロジェクト ソース ファイルを示しています。 プログラム名が Editor なので、このプロジェクト ファイルの名前は Editor.dpr になります。
program Editor; uses Forms, REAbout, // An "About" box REMain; // Main form {$R *.res} begin Application.Title := 'Text Editor'; Application.CreateForm(TMainForm, MainForm); Application.Run; end.
1 行目に program ヘッダーがあります。 この例の場合、uses 句は、3 つの追加ユニット Forms、REAbout、REMain を利用することを指定します。 $R コンパイラ指令は、プロジェクトのリソース ファイルをプログラムにリンクします。 最後に、begin キーワードと end キーワードの間にあるステートメント ブロックがプログラムの動作時に実行されます。 すべての Delphi ソース ファイルと同様に、プロジェクト ファイルは、セミコロンではなくピリオドで終わります。
プログラムのロジックのほとんどはユニット ファイルに記述されているため、Delphi プロジェクト ファイルは短いのが普通です。 通常、Delphi プロジェクト ファイルには、アプリケーションのメイン ウィンドウを起動したり、イベント処理ループを開始するのに十分なコードのみ記述されています。 プロジェクト ファイルは、IDE によって自動的に生成および維持管理されるため、手動で編集する必要はほとんどありません。
標準 Pascal では、以下のように、プログラム ヘッダーのプログラム名の後にパラメータを指定できます。
program Calc(input, output);
Embarcadero Delphi では、これらのパラメータを無視します。
RAD Studio では、program ヘッダーによって独自の名前空間が導入されます。これは、プロジェクトのデフォルト名前空間と呼ばれます。
プログラムの uses 句
uses 句では、プログラムの一部として組み込まれるユニットを列挙します。 同様に、これらのユニットにもそれぞれ独自の uses 句が含まれている場合があります。 ユニットのソース ファイル内の uses 句の詳細は、以下の「ユニット参照と uses 句」を参照してください。
uses 句では、キーワード uses の後に、プロジェクト ファイルで直接使用されるユニットのコンマ区切りリストが続きます。
ブロック
ブロックの中には、プログラムの動作時に実行される単純文または構造化文が含まれています。 ほとんどの program ファイルでは、ブロックは予約語 begin と end で囲まれた複合文であり、プロジェクトの Application オブジェクトに対するメソッド呼び出しで構成されます。 ほとんどのプロジェクトには、グローバル変数 Application があり、その中にたとえば、Vcl.Forms.TApplication、Web.WebBroker.TWebApplication、または Vcl.SvcMgr.TServiceApplication などのインスタンスが格納されています。 ブロックには、定数、型、変数、手続き、関数の宣言を含めることもできますが、これらの宣言は、ブロック内のステートメントより前に記述する必要があります。 プログラム ソースの終わりを表す end の後には、以下のようにピリオド(.)が必要であることに注意してください。
begin . . . end.
ユニットの構造と構文
ユニットは、型(クラスを含む)、定数、変数、ルーチン(関数および手続き)で構成されます。 各ユニットは、それぞれ独自のソース(.pas)ファイルで定義されます。
ユニット ファイルは、unit ヘッダーで始まり、その後に interface キーワードが続きます。 interface キーワードの後に、利用するユニットのリストを uses 句で指定します。 次に implementation セクションがあり、その後に任意指定の initialization セクションと finalization セクションが続きます。 ユニット ソース ファイルの概略は次のとおりです。
unit Unit1; interface uses // List of unit dependencies goes here... // Interface section goes here implementation uses // List of unit dependencies goes here... // Implementation of class methods, procedures, and functions goes here... initialization // Unit initialization code goes here... finalization // Unit finalization code goes here... end.
ユニットは、ピリオドを後ろに伴う予約語 end でもって完了してなければなりません。
unit ヘッダー
unit ヘッダーはユニットの名前を指定します。 予約語 unit の後に有効な識別子が続き、最後にセミコロンが付きます。 Embarcadero 製ツールを使って開発されたアプリケーションの場合は、この識別子とユニット ファイル名が一致する必要があります。 したがって、たとえば次のような unit ヘッダーの場合、
unit MainForm;
このユニットのソース ファイルは MainForm.pas という名前になり、コンパイル済みユニットが格納されるファイルの名前は MainForm.dcu になります。 ユニット名はプロジェクト内で一意でなければなりません。 たとえユニット ファイルが別々のディレクトリにあっても、同じ名前の 2 つのユニットを 1 つのプログラムで使用することはできません。
interface セクション
ユニットの interface セクションは、予約語 interface で始まり、implementation セクションの直前で終わります。 interface セクションでは、クライアントで使用可能な定数、型、変数、手続き、関数を宣言します。 クライアントとは、このユニットに定義されている要素を使用する他のユニットまたはプログラムのことです。 これらはパブリックなエンティティと呼ばれます。他のユニットのコードから、あたかもそのユニット自身に宣言されているかのように、それらのエンティティにアクセスできるからです。
手続きや関数の interface 宣言には、そのルーチンのシグネチャのみ含まれます。 つまり、ルーチンの名前、パラメータ、戻り値の型(関数の場合)のみ含まれます。 手続きや関数の実行可能コード ブロックは implementation セクションに記述されます。 したがって、interface セクションでの手続きや関数の宣言は、前方宣言のような働きをします。
クラスの interface 宣言には、クラスのすべてのメンバ(フィールド、プロパティ、手続き、関数)の宣言が含まれていなければなりません。
interface セクションには独自の uses 句を付けることができますが、その場合、uses 句は予約語 interface の直後に記載されなければなりません。
implementation セクション
ユニットの implementation セクションは、予約語 implementation で始まり、initialization セクションの直前で終わるか、initialization セクションがない場合はユニットの終わりまで続きます。 implementation セクションでは、interface セクションで宣言された手続きおよび関数を定義します。 implementation セクション内では、これらの手続きや関数はどのような順序で定義および呼び出してもかまいません。 implementation セクションでパブリックな手続きや関数を定義する場合は、ヘッダーのパラメータ リストを省略できますが、省略しない場合は、interface セクションでの宣言とパラメータ リストが正確に一致する必要があります。
implementation セクションでは、パブリックな手続きや関数の定義だけでなく、ユニットのプライベートな定数、型(クラスを含む)、変数、手続き、関数を宣言することもできます。 つまり、interface セクションとは異なり、implementation セクションで宣言されたエンティティには、他のユニットからアクセスできません。
implementation セクションには独自の uses 句を付けることができますが、その場合、uses 句は予約語 implementation の直後に記載されなければなりません。 implementation セクションで指定されたユニット内で宣言された識別子は、その implementation セクション内でのみ使用することができます。 そうした識別子を interface セクションで参照することはできません。
initialization セクション
initialization セクションは省略可能です。 このセクションは、予約語 initialization で始まり、finalization セクションの直前で終わるか、finalization セクションがない場合はユニットの終わりまで続きます。 initialization セクションには、プログラムの起動時に実行されるステートメントが記述されます。これらのステートメントは記述された順に実行されます。 そのため、たとえば、初期化の必要なデータ構造を定義した場合は、initialization セクションで初期化することができます。
interface セクションの uses 句に列挙されているユニットについては、クライアントで使用されるユニットの initialization セクションは、ユニットがクライアントの uses 句に出現する順に実行されます。
従来の "begin ... end." 構文はまだ機能します。 基本的には、initialization の代わりに予約語 "begin" を使用することができ、その後にゼロ個以上の実行文が続きます。 従来の "begin ... end." 構文を用いたコードでは、finalization セクションを指定できません。 その場合、終了処理は、ExitProc 変数に手続きを設定することで遂行されます。 この方法は、これから作成するコードにはお勧めしませんが、以前のソース コードでは使用されている場合があります。
finalization セクション
finalization セクションは省略可能で、initialization セクションのあるユニットにのみ含めることができます。 finalization セクションは、予約語 finalization で始まり、ユニットの終わりまで続きます。 このセクションには、メイン プログラムの終了時に実行されるステートメントが記述されます(Halt 手続きを使用してプログラムを終了させる場合を除く)。 initialization セクションで割り当てたリソースを解放するには、finalization セクションを使用します。
finalization セクションは、initialization セクションとは逆順に実行されます。 たとえば、アプリケーションでユニット A、B、C をこの順に初期化した場合は、C、B、A の順に終了処理が実行されます。
いったんユニットの initialization コードが実行を開始すると、それに対応する finalization セクションがアプリケーションの停止時に必ず実行されます。 そのため、finalization セクションでは、不完全に初期化されたデータを処理できなければなりません。実行時エラーが発生した場合には、initialization コードが完全には実行されないおそれがあるからです。
ユニット参照と uses 句
uses 句では、その句が出現するプログラム、ライブラリ、またはユニットで使用されるユニットを列挙します。 uses 句を記述できる場所は以下のいずれかです。
- program または library のプロジェクト ファイル
- ユニットの interface セクション
- ユニットの implementation セクション
大半のプロジェクト ファイルには uses 句が含まれており、大半のユニットの interface セクションにも含まれています。 ユニットの implementation セクションには、独自の uses 句を付けることもできます。
System ユニットと SysInit ユニットは、どのアプリケーションでも自動的に使用され、uses 句で明示的に指定することはできません。 (System には、ファイル入出力、文字列処理、浮動小数点演算、動的メモリ割り当てなどのルーチンが実装されています)。 SysUtils といったその他の標準ライブラリ ユニットは、uses 句で明示的に指定する必要があります。 ほとんどの場合、プロジェクトに対してユニットを追加または削除すると、必要なユニットはすべて自動的に uses 句に指定されます。
大文字と小文字の区別: unit 宣言と uses 句では、ユニット名とファイル名の大文字/小文字の区別が一致していなければなりません。 それ以外の状況(たとえば、限定識別子など)では、ユニット名は大文字と小文字が区別されません。 ユニット参照に関する問題を避けるには、以下のように、ユニット ソース ファイルを明示的に参照するようにします。
uses MyUnit in "myunit.pas";
プロジェクト ファイルにこのような明示的な参照がある場合、他のソース ファイルでは、以下のように、大文字と小文字の区別が一致しなくてもよい単純な uses 句でユニットを参照できます。
uses Myunit;
uses 句の構文
uses 句は、予約語 uses で始まり、その次に、コンマで区切られたユニット名が 1 つ以上指定された後、セミコロンで終わります。 例:
uses Forms, Main; uses Forms, Main; uses Windows, Messages, SysUtils, Strings, Classes, Unit2, MyUnit;
program または library の uses 句では、ユニット名の後に予約語 in とソース ファイル名を指定できます。ソース ファイル名は単一引用符で囲みます。ディレクトリ パスは付ける場合も付けない場合もあり、絶対パスでも相対パスでもかまいません。 例:
uses Windows, Messages, SysUtils, Strings in 'C:\Classes\Strings.pas', Classes;
ユニットのソース ファイルを指定する必要がある場合は、キーワード in をユニット名の後に付けます。 IDE ではユニット名とユニットが定義されているソース ファイルの名前が一致することを想定しているため、通常はそのようなことをする必要はありません。 ソース ファイルの場所がはっきりしない場合のみ、in を使用する必要があります。たとえば、次のような場合です。
- 使用したソース ファイルがプロジェクト ファイルとは異なるディレクトリにあり、そのディレクトリがコンパイラの検索パスに含まれていない場合
- コンパイラの検索パスに含まれている異なるディレクトリに同じ名前のユニットが存在する場合
- コマンド ラインからコンソール アプリケーションをコンパイルしようとしていて、ユニット名とソース ファイル名が一致しない場合
コンパイラはまた、in ... 構文に基づいて、どのユニットがプロジェクトに含まれているかを判定します。 プロジェクト(.dpr)ファイルの in およびファイル名で限定された uses 句に指定されているユニットのみプロジェクトの一部と見なされ、uses 句内の他のユニットはプロジェクトの外部エンティティとして使用されます。 この区別はコンパイルには影響はありませんが、[プロジェクト マネージャ]などの IDE ツールには影響を与えます。
ユニットの uses 句では、in を使用して、コンパイラにソース ファイルの場所を指示することはできません。 どのユニットもコンパイラの検索パスに含まれていなければなりません。 さらに、ユニット名とそのソース ファイル名が一致していなければなりません。
ユニットの多重参照と間接参照
uses 句におけるユニットの指定順序によって、ユニットの初期化順序が決まり、コンパイラによる識別子の特定方法に影響が及びます。 同じ名前の変数、定数、型、手続き、関数が 2 つのユニットで宣言されている場合、コンパイラは uses 句で後に指定されたユニットにある方を使用します。 (もう一方のユニットにある識別子にアクセスするには、UnitName.Identifier のように限定子を追加しなければなりません)。
uses 句で指定する必要があるのは、その句が出現するプログラムまたはユニットで直接使用されるユニットのみです。 つまり、ユニット B で宣言されている定数、型、変数、手続き、関数をユニット A で参照する場合、ユニット A はユニット B を明示的に使用する必要があります。 今度はユニット C で宣言されている識別子をユニット B で参照する場合、ユニット A はユニット C に間接的に依存します。この場合、ユニット A の uses 句でユニット C を指定する必要はありませんが、それでも、ユニット A を処理するには、コンパイラはユニット B とユニット C を両方とも見つけることができなければなりません。
以下の例は、このような間接的な依存関係を示しています。
program Prog; uses Unit2; const a = b; // ... unit Unit2; interface uses Unit1; const b = c; // ... unit Unit1; interface const c = 1; // ...
この例では、Prog は Unit2 に直接依存しており、Unit2 は Unit1 に直接依存しています。 したがって、Prog は Unit1 に間接的に依存しています。 Unit1 は Prog の uses 句で指定されていないため、Unit1 で宣言されている識別子に Prog からアクセスすることはできません。
クライアント モジュールをコンパイルするには、クライアントが直接または間接的に依存するユニットをコンパイラがすべて見つける必要があります。 ただし、これらユニットのソース コードが変更されていない場合は、ソース ファイル(.pas ファイル)ではなく、.dcu ファイルのみコンパイラには必要です。
ユニットの interface セクションに変更を加えた場合は、その変更内容に依存する他のユニットを再コンパイルする必要があります。 しかし、ユニットの implementation などのセクションにのみ変更が加えられた場合は、依存するユニットを再コンパイルする必要はありません。 コンパイラはこれらの依存関係を自動的に追跡し、必要な場合にのみユニットを再コンパイルします。
ユニットの循環参照
ユニットどうしが直接または間接に相互参照する場合、それらのユニットは相互依存していると言います。 一方の interface セクションの uses 句ともう一方の interface セクションの uses 句の間に循環参照が存在しない限り、相互依存は許されます。 つまり、あるユニットの interface セクションを出発点として、他のユニットの interface セクションによる参照をたどっていった結果、元のユニットに戻ることがあってはいけません。 相互依存のパターンが妥当であるためには、それぞれの循環参照パスが少なくとも 1 つの implementation セクションの uses 句を経由する必要があります。
2 つのユニットが相互に依存する最も単純な場合では、これらのユニットの interface セクションにある uses 句で互いに相手を指定することはできないということです。 そのため、以下の例ではコンパイル エラーが発生します。
unit Unit1; interface uses Unit2; // ... unit Unit2; interface uses Unit1; // ...
ただし、どちらかの参照を implementation セクションに移動すれば、これら 2 つのユニットは問題なく相互参照できます。
unit Unit1; interface uses Unit2; // ... unit Unit2; interface //... implementation uses Unit1; // ...
循環参照が発生する可能性を少なくするには、可能な限り implementation セクションの uses 句でユニットを指定することをお勧めします。 別のユニットに定義されている識別子が interface セクションで使用される場合のみ、そのユニットを interface セクションの uses 句で指定する必要があります。