こんにちは。FenrirFS 開発担当の福満です。
フェンリルにはいろいろなエンジニアがいて、お気に入りの言語も様々です。
今日は私の好きな Delphi 言語(Object Pascal)の面白いところを、
ちょっと便利なクラスを作って紹介したいと思います。
* 掲載したコードは現時点での最新のエディション、Delphi XE で動作確認しています。
Delphi に馴染みのない方は是非下記の Wikipedia のページをご一読ください。
●お題
画像などのリソースファイルをバイナリファイルに埋め込んで、プログラムで使用する
ことはよくあると思います。
今日は実行ファイルにリソースとして埋め込まれた任意の画像をロードして使用する
汎用的に使える画像リソース管理クラスを作成したいと思います。
●実行時型情報を使う
リソースに埋め込まれたデータをプログラム内で使う時には、概ね以下のことをするかと思います。
1、データを保持するクラスのインスタンスを生成。
2、インスタンスにデータを読み込む。
3、必要なくなったらインスタンスを解放。
上記の3つの処理を個々のプロパティに対して記述すると冗長なコードになってしまいますが、今回は、基本クラスで実行時型情報(RTTI)からプロパティの一覧を取得して、プロパティ名と同名のリソースをロードするようにし、サブクラスでプロパティ宣言とプロパティのインスタンス生成に使用する実際のクラスを記述するようにします。以下は基本クラスのコードです。
unit ImageRes; interface uses Windows, Classes, Graphics; type {$M+} // 実行時型情報を有効にするコンパイラスイッチ TImageRes = class private procedure AllocateProps; procedure DeAllocateProps; protected // TGraphic 継承クラスを返す function GetGraphicClass: TGraphicClass; virtual; abstract; public constructor Create; destructor Destroy; override; end; {$M-} implementation uses TypInfo; { TImageRes } procedure TImageRes.AllocateProps; var cls : TGraphicClass; Count : Integer; i : Integer; PropList : PPropList; PropInfo: PPropInfo; g : TGraphic; stream : TResourceStream; begin // インスタンス生成のためのクラスを取得 cls := GetGraphicClass; // プロパティ情報の一覧を取得 Count := GetPropList( Self.ClassInfo, PropList); if ( Count > 0) then begin try for i := 0 to Count - 1 do begin PropInfo := PropList^[i]; g := cls.Create; stream := TResourceStream.Create( HInstance, string( PropInfo.Name), RT_RCDATA); try g.LoadFromStream( stream ); finally stream.Free; end; // プロパティに値を設定 SetObjectProp( Self, PropInfo, g ); end; finally FreeMemory( PropList ); end; end; end; constructor TImageRes.Create; begin AllocateProps; end; procedure TImageRes.DeAllocateProps; var PropList : PPropList; Count : Integer; i : Integer; PropInfo: PPropInfo; o : TObject; begin Count := GetPropList( Self.ClassInfo, PropList); if ( Count > 0) then begin try for i := 0 to Count - 1 do begin PropInfo := PropList^[i]; o := GetObjectProp( Self, PropInfo); o.Free; end; finally FreeMemory( PropList ); end; end; end; destructor TImageRes.Destroy; begin DeAllocateProps; inherited; end; end.
コンストラクタ / デストラクタで 呼び出される AllocateProps / DeAllocateProps は実行時型情報からプロパティの一覧を取得してインスタンス生成 / リソースのロード /インスタンス破棄を行います。
● サブクラスの作成
サブクラスの作成方法は簡単です。 GetGraphicClass をオーバーライドして TGraphic の継承クラスを返し、個々のリソースの名前と同名のプロパティを宣言するだけです。
リソースが下記の内容だとすると、
Star RCDATA "Star.png" Label_Red RCDATA "Label_Red.png" Label_Blue RCDATA "Label_Blue.png"
サブクラスはこのようになります。
unit PngImageRes; interface uses Graphics, ImageRes, PngImage; type TPngImageRes = class( TImageRes ) private FStar: TPngImage; FLabel_Red: TPngImage; FLabel_Blue: TPngImage; protected function GetGraphicClass: TGraphicClass; override; published property Star: TPngImage read FStar write FStar; property Label_Red: TPngImage read FLabel_Red write FLabel_Red; property Label_Blue: TPngImage read FLabel_Blue write FLabel_Blue; end; implementation { TPngImageRes } function TPngImageRes.GetGraphicClass: TGraphicClass; begin Result := TPngImage; end; end.
● 作成したサブクラスを使う
プログラム内で使用するときは例えば以下のようにします。
procedure TFormMain.Button1Click(Sender: TObject); begin Image1.Picture.Graphic := FPngImageRes.Star; Image2.Picture.Graphic := FPngImageRes.Label_Red; Image3.Picture.Graphic := FPngImageRes.Label_Blue; end; procedure TFormMain.FormCreate(Sender: TObject); begin FPngImageRes := TPngImageRes.Create; end; procedure TFormMain.FormDestroy(Sender: TObject); begin FPngImageRes.Free; end;
このような手法を用いると、個々のプロパティのためのコードはプロパティ宣言だけに
なりますので、状況によっては大幅に記述するコード量を削減することができます。
基本クラスを宣言したユニットをライブラリパスが通った場所に置いておけば、
いろいろな場面で手軽に使うことが出来るのでおすすめです。
● まとめ
このように Delphi では実行時型情報を手軽且つ便利に使うことが出来ます。
今回ご紹介させていただいた手法は、キーとプロパティを関連付けたい場面では
応用することが可能です。
Delphi に馴染みのないエンジニアの方も多いかと思いますが、面白そうだな!
と思っていただけたなら幸いです。
今後もフェンリルへの応援をよろしくお願いします。