Developer's Blog

実行時型情報を使ったプロパティへのアクセス – Delphi の話

こんにちは。FenrirFS 開発担当の福満です。

フェンリルにはいろいろなエンジニアがいて、お気に入りの言語も様々です。
今日は私の好きな Delphi 言語(Object Pascal)の面白いところを、
ちょっと便利なクラスを作って紹介したいと思います。

* 掲載したコードは現時点での最新のエディション、Delphi XE で動作確認しています。

Delphi に馴染みのない方は是非下記の Wikipedia のページをご一読ください。

Delphi
Object Pascal

●お題

画像などのリソースファイルをバイナリファイルに埋め込んで、プログラムで使用する
ことはよくあると思います。
今日は実行ファイルにリソースとして埋め込まれた任意の画像をロードして使用する
汎用的に使える画像リソース管理クラスを作成したいと思います。

●実行時型情報を使う

リソースに埋め込まれたデータをプログラム内で使う時には、概ね以下のことをするかと思います。

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 に馴染みのないエンジニアの方も多いかと思いますが、面白そうだな!
と思っていただけたなら幸いです。

今後もフェンリルへの応援をよろしくお願いします。

 

Copyright © 2019 Fenrir Inc. All rights reserved.