ユニット
ユニットとはVBにおける標準モジュールのようなものです。 ただ、標準モジュールとは異なり、Object Pascalではインターフェイス部と呼ばれる外部に公開するプロシージャ・関数・定数・型などを記述する部分があります。 これはC/C++でいうプロトタイプ宣言によく似たものです。 また、ユニットはメインプログラムのあるファイルとは別のファイルに記述されます。 簡単なユニットとそれを用いたサンプルを次に示します。
{$APPTYPE CONSOLE}
program LearningDelphi;
uses
SysUtils,
SampleUnit in 'SampleUnit.pas';
begin
SampleProcedure;
Writeln( SampleFunction );
// 終了メッセージを表示
Write( 'Press any key to continue' );
Readln;
end.
unit SampleUnit;
interface
uses
SysUtils;
procedure SampleProcedure;
function SampleFunction : string;
implementation
var
sampleString: string;
procedure SampleProcedure;
begin
Writeln( sampleString );
end;
function SampleFunction : string;
begin
result := sampleString;
end;
initialization
begin
Writeln( '初期化処理がなされます' );
sampleString := 'SampleUnit';
end;
finalization
begin
// メインプログラム終了後に呼び出されます
end;
end.
初期化処理がなされます SampleUnit SampleUnit
まず、ユニットを利用する側ではuses文で使用するユニットとそれが記述されているファイルを指定します。 それ以外に特別な記述は必要ありません。 後はユニットにあるプロシージャや型などを利用するだけです。 そして、それらの要素を実際に記述するのがユニットの方です。 ユニットは次のような構造になっています。

まず、ユニットヘッダではユニットの名前を指定します。 Delphiではここで指定した名前とファイル名が一致している必要があります。 ユニット本体の記述では、インターフェイス部と実現部の二つの部分を記述します。 実現部は実装部と言い換えてもいいかもしれません。 インターフェイス部でユニット外に公開するプロシージャ・関数の宣言部だけを記述し、実現部でその実装を行います。
また、実装部にはinitialization文とfinalization文をオプションで記述することができます。 ユニットをインスタンスかできないクラスと考えれば、この部分はコンストラクタ・デストラクタに相当するといえます。 この例ではusesをインターフェイス部で使用しましたが、ユニット同士の循環参照が起こる場合にはいずれかのusesを実現部に移すことで解決できます。 また、usesはできるだけ実現部で使用するべきとされています。
実行結果を検証してみると、メインプログラムから呼び出されたユニットのプロシージャ・関数は適切に動作しています。 また、すべてのプロシージャ・関数などが使用される前にユニットの初期化部が動作しています。 この例の終了処理部にWritelnなどを記述しても、アプリケーション終了直前に呼び出されるため、実際には表示された結果を見ることはできません。
クラス 基本編
Object Pascalにおけるクラスの概念もC/C++のクラスと大域的には同じといえます。 また、記述方法もC/C++と非常によく似ています。
unit SampleUnit;
// インターフェイス部
interface
// 型宣言
type
TMusume = class
// プライベートスコープ
private
// フィールド
m_Age: integer;
m_BirthDate: TDateTime;
// メソッド
function CalcAge: integer;
// プロパティプロシージャ
function getAge: integer;
function getBirthDate: TDateTime;
procedure setBirthDate( const birthDate: TDateTime );
// パブリックスコープ
public
// フィールド
Name: string;
// プロパティ
property Age: integer read getAge;
property BirthDate: TDateTime read getBirthDate write setBirthDate;
// メソッド
procedure Introduce;
// コンストラクタ
constructor Create( nam: String );
// デストラクタ
//destructor Destroy;
end;
// 実装部
implementation
uses
SysUtils;
// コンストラクタ
constructor TMusume.Create( nam: String );
begin
Name := nam;
end;
// プロパティプロシージャ
function TMusume.getAge: integer;
begin
result := m_Age;
end;
procedure TMusume.setBirthDate( const birthDate: TDateTime );
begin
m_BirthDate := birthDate;
m_Age := CalcAge;
end;
function TMusume.getBirthDate: TDateTime;
begin
result := m_BirthDate;
end;
// メソッド
procedure TMusume.Introduce;
var
y, m ,d: Word;
begin
DecodeDate( m_BirthDate, y, m, d );
Write( Name, ' ' );
Write( y, '年', m, '月', d, '日生まれ ' );
Writeln( m_Age, '歳' );
end;
function TMusume.CalcAge: integer;
var
age: Word;
by, bm, bd: Word;
ny, nm, nd: Word;
begin
DecodeDate( m_BirthDate, by, bm, bd );
DecodeDate( Now, ny, nm, nd );
age := ny - by;
if ( bm > nm ) or ( ( bm = nm ) and ( bd > nd ) ) then
age := age - 1;
result := age;
end;
end.
まず、type文で「識別子 = class」としてクラス型を宣言します。 さらに、そのクラスのフィールド・インターフェイスを定義します。 各メンバはスコープを持つことができ、基本的にはC/C++と同じ「private, proteced, public」の三つです。 これ以外にもいくつか特殊なものがありますがここでは省略します。 プロシージャ・関数・フィールドのインターフェイスは今までどおりです。 また、Object Pascalのクラスはプロパティを持つことができます。 プロパティとなるフィールドはpropertyで宣言し、別途プロパティプロシージャを用意してある必要があります。
プロパティプロシージャの名前は任意ですが、書き込み用プロシージャは値をひとつだけ取り戻り値なし、読み取り用関数は引数なしで戻り値ありのものを用意します。 これらのプロパティプロシージャはプロパティ宣言部より前で宣言されている必要があります。 また、プロパティは読み取り専用にも書き込み専用にもすることができ、そうする場合はreadないしwriteを省略します。 この例ではプロパティプロシージャはprivateスコープですが、publicにすることで普通のプロシージャ同様に呼び出すことも可能です。 また、コンストラクタおよびデストラクタを実装することもできます。 その場合は「constructor 識別子(引数リスト);」ないし「destructor 識別子;」などとします。
インターフェイスの宣言が完了したら、実現部でその実装を行います。 これはC/C++のクラスにおけるメンバ関数の外部定義と同様の方法で行います。 つまり、「procedure クラス名.プロシージャ名」、「function クラス名.関数名」のように、関数・プロシージャの名前の前にクラスの名前を指定します。 また、これらのプロシージャの中はクラスの中と同様なので、クラスのメンバにクラス名を付加する必要はありません。 こうしてできたクラスは次のようにして使用します。
{$APPTYPE CONSOLE}
program LearningDelphi;
uses
SysUtils,
SampleUnit in 'SampleUnit.pas';
var
// クラス型変数を宣言
aibon: TMusume;
begin
// インスタンスを生成
aibon := TMusume.Create( '加護亜依' );
// プロパティ値を設定
aibon.BirthDate := EncodeDate( 1988, 2, 7 );
// メソッドを呼び出し
aibon.Introduce;
// インスタンスを破棄
aibon.Free;
// 終了メッセージを表示
Write( 'Press any key to continue' );
Readln;
end.
まず、クラス型変数の宣言は普通の変数宣言と変わりありません。 インスタンスを生成するには「クラス型.コンストラクタ(引数)」とします。 このときコンストラクタに渡すべきパラメータがないときは省略できますし、クラスにコンストラクタ宣言がないときは暗黙の基底クラスであるTObjectから継承したCreateメソッドを利用することができます。 また、デストラクタも同様で、特にクラスで宣言されていない場合はFreeまたはDestroyメソッドを使用することができます。 その他メソッドの呼び出しやフィールド・プロパティへのアクセスはC/C++やVBと変わりありません。 このコードを実行すると次のようになります。
加護亜依 1988年2月7日生まれ 15歳 Press any key to continue
参考までに、これと同様の動作をするクラスとそれを利用するコードを、VB.NETで組んだものを例示しておきます。 見た目もできる限り似せています。
Imports System
Class TMusume
Private m_Age As Integer
Private m_BirthDate As DateTime
Public Name As String
' コンストラクタ
Private Sub New(ByVal name As String)
Me.Name = name
End Sub
Public Shared Function Create(ByVal name As String) As TMusume
Return New TMusume(name)
End Function
' プロパティ
Public ReadOnly Property Age() As Integer
Get
Return m_Age
End Get
End Property
Public Property BirthDate() As DateTime
Get
Return m_BirthDate
End Get
Set(ByVal Value As DateTime)
m_BirthDate = Value
m_Age = CalcAge()
End Set
End Property
' メソッド
Public Sub Introduce()
Dim y, m, d As Integer
y = m_BirthDate.Year : m = m_BirthDate.Month : d = m_BirthDate.Day
Console.Write(Name + " ")
Console.Write("{0}年{1}月{2}日生まれ ", y, m, d)
Console.WriteLine("{0}歳", m_Age)
End Sub
Private Function CalcAge()
Dim age As Integer
Dim by, bm, bd As Integer
Dim ny, nm, nd As Integer
by = m_BirthDate.Year : bm = m_BirthDate.Month : bd = m_BirthDate.Day
ny = Now.Year : nm = Now.Month : nd = Now.Day
age = ny - by
If (bm > nm) OrElse ((bm = nm) AndAlso (bd > nd)) Then age -= 1
CalcAge = age
End Function
End Class
Class SampleClass
Shared aibon As TMusume
Public Shared Function Main(ByVal args() As String) As Integer
aibon = TMusume.Create("加護亜依")
aibon.BirthDate = New DateTime(1988, 2, 7)
aibon.Introduce()
' 解放処理は無用
Return 0
End Function
End Class
クラス 継承編
Object Pascalのクラスでは、当然継承もサポートされます。
unit SampleUnit;
// インターフェイス部
interface
// 型宣言
type
TMusume = class
// プライベートスコープ
private
// フィールド
m_Age: integer;
m_BirthDate: TDateTime;
// メソッド
function CalcAge: integer;
// プロパティプロシージャ
function getAge: integer;
function getBirthDate: TDateTime;
procedure setBirthDate( const birthDate: TDateTime );
// パブリックスコープ
public
// フィールド
Name: string;
// プロパティ
property Age: integer read getAge;
property BirthDate: TDateTime read getBirthDate write setBirthDate;
// メソッド
procedure Introduce; virtual;
// コンストラクタ
constructor Create( nam: string );
end;
// TMusumeの派生クラス
TDerived = class(TMusume)
public
// フィールド
BloodType: string;
// メソッド(オーバーライド)
procedure Introduce; override;
// コンストラクタ
constructor Create( nam: string; bldType: string );
end;
// 実装部
implementation
uses
SysUtils;
// コンストラクタ
constructor TMusume.Create( nam: string );
begin
Name := nam;
end;
// プロパティプロシージャ
function TMusume.getAge: integer;
begin
result := m_Age;
end;
procedure TMusume.setBirthDate( const birthDate: TDateTime );
begin
m_BirthDate := birthDate;
m_Age := CalcAge;
end;
function TMusume.getBirthDate: TDateTime;
begin
result := m_BirthDate;
end;
// メソッド
procedure TMusume.Introduce;
var
y, m ,d: Word;
begin
DecodeDate( m_BirthDate, y, m, d );
Write( Name, ' ' );
Write( y, '年', m, '月', d, '日生まれ ' );
Writeln( m_Age, '歳' );
end;
function TMusume.CalcAge: integer;
var
age: Word;
by, bm, bd: Word;
ny, nm, nd: Word;
begin
DecodeDate( m_BirthDate, by, bm, bd );
DecodeDate( Now, ny, nm, nd );
age := ny - by;
if ( bm > nm ) or ( ( bm = nm ) and ( bd > nd ) ) then
age := age - 1;
result := age;
end;
// 派生クラスでのコンストラクタ
constructor TDerived.Create( nam: string; bldType: string );
begin
Name := nam;
BloodType := bldType;
end;
// オーバーライドされたメソッド
procedure TDerived.Introduce;
var
y, m ,d: Word;
begin
DecodeDate( m_BirthDate, y, m, d );
Write( Name, ' ' );
Write( y, '年', m, '月', d, '日生まれ ' );
Write( m_Age, '歳 ' );
Writeln( BloodType, '型' );
end;
end.
{$APPTYPE CONSOLE}
program LearningDelphi;
uses
SysUtils,
SampleUnit in 'SampleUnit.pas';
var
// クラス型変数を宣言
aibon: TDerived;
musume: TMusume;
begin
// インスタンスを生成
aibon := TDerived.Create( '加護亜依', 'AB' );
// プロパティ値を設定
aibon.BirthDate := EncodeDate( 1988, 2, 7 );
// メソッドを呼び出し
aibon.Introduce;
// 基底クラス型へキャスト
musume := aibon;
// メソッドを呼び出し
musume.Introduce;
// インスタンスを破棄
aibon.Free;
// 終了メッセージを表示
Write( 'Press any key to continue' );
Readln;
end.
加護亜依 1988年2月7日生まれ 15歳 AB型 加護亜依 1988年2月7日生まれ 15歳 AB型 Press any key to continue
あるクラスを基底クラスとしてクラスを派生するにはtype文で「派生クラス = class(基底クラス)」とします。 Delphiにおける継承は、.NET FrameworkのCLSにおける継承と同じく、クラスの多重継承は認められていないものの、インターフェイスは複数実装することができます。 この例ではTMusumeを継承してTDerivedという派生クラスを作成しています。 まず、Introduceメソッドをオーバーライドするために、 TDerivedでのIntroduceメソッドの宣言の最後にoverrideを付加します。 当時に、オーバーライドされる基底クラスの Introduceメソッドにもvirtualを付加します。 後は各メンバの実装を行うだけです。
ちなみに、実行結果を見ると、TDerived型のインスタンスをTMusume型に代入しても、当然TDerived型の Introduceメソッドが呼び出されます。 しかし、派生クラスでのメソッド宣言でoverrideを取り去ると、派生クラスのメソッドは基底クラスのメソッドをシャドウしたかのような動作になります。
{ 途中省略 }
// TMusumeの派生クラス
TDerived = class(TMusume)
public
// フィールド
BloodType: string;
// メソッド(シャドウ)
procedure Introduce;
// コンストラクタ
constructor Create( nam: string; bldType: string );
end;
加護亜依 1988年2月7日生まれ 15歳 AB型 加護亜依 1988年2月7日生まれ 15歳 Press any key to continue
ただし、このままだと「'Introduce'メソッドが基本型'TMusume'の仮想メソッドを隠しました」という警告が発生します。 これを解決するにはさっき消し去ったoverrideの変わりにreintroduceを付け替えます。
クラス TObject編
Object Pascalのクラスは、暗黙的にTObjectを継承します。
{$APPTYPE CONSOLE}
program LearningDelphi;
uses
SysUtils,
SampleUnit in 'SampleUnit.pas';
var
// クラス型変数を宣言
aibon: TDerived;
begin
// インスタンスを生成
aibon := TDerived.Create( '加護亜依', 'AB' );
// クラス名
Writeln( aibon.ClassName );
// 基底クラス名
Writeln( aibon.ClassParent.ClassName );
// インスタンスのサイズ
Writeln( aibon.InstanceSize );
// インスタンスを破棄
aibon.Free;
// 終了メッセージを表示
Write( 'Press any key to continue' );
Readln;
end.
TDerived TMusume 28 Press any key to continue
ここで呼び出したメソッドは全てTObjectから継承したメソッドです。 このほかにもいくつかメソッドが存在しますが、その多くは特別な場合を除いてあまり使用しないでしょう。
Windows アプリケーション
Delphiが賞賛される要因、そしてVisual Basicと比較される要因の一つが、RADにあると思います。 コンソールアプリで遊ぶのをそろそろ切り上げて、簡単なWindowsアプリケーションを作ってみます。 ところで一般的にはよくRAD環境と言いますけど、RADとはRapid Application Developmentの略なので「RAD環境」はRapid Application Development Developmentと冗長な表現になっています。 SerializableAttribute属性とか言っているのと同じ気がするのですがいいのでしょうか。 (確かに日本語の語感からするとRAD環境の方がしっくりくるのですが・・・)
よけいな話しになりましたが、まずはじめは生成されるテンプレートについて見てみることにします。
生成されたテンプレート
program FirstWindowsApplication;
uses
Forms,
MainForm in 'MainForm.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
unit MainForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
end.
生成されたフォーム

メインプログラムの方は特に問題ありません。 アプリケーションを初期化して、フォームを作成して、実行してメッセージループに入るだけです。 ただ、 MainFormユニットの方はフォームの実装を記述する部分ですが、VBとかなり異なります。 まず注目すべきは、フォームはTForm型から継承したクラス型であることです。 VBでもフォームは一種のクラスでしたが、Delphiでのフォームは継承が可能です。
それでは早速ログインダイアログのようなものを作ってみます。 このアプリケーションはエディットボックスに入力された文字をメッセージボックスを利用して表示するだけの単純なアプリケーションです。

unit MainForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure Edit1KeyPress(Sender: TObject; var Key: Char);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
// ボタンのクリック
procedure TForm1.Button1Click(Sender: TObject);
begin
MessageDlg( 'Hello, ' + Edit1.Text + '!', mtInformation, [mbOK], 0 );
end;
// Enterキーによる入力
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then Button1Click( nil );
end;
end.

灰色の部分はデザイン時に自動的に追加された部分と、イベントハンドラとして作成された部分です。 青いところが実際に記述したコードです。 これを実行するとこんな感じになります。