2011年11月14日月曜日

DELPHIでInterfaceを使ってみる(1)

(※以下の話はDELPHI2010を用いていることが前提になっています)
DELPHIIntefaceを実装するための簡単なメモを書いていきたいと思います。InterfaceはもともとCOM(Common Object Model)に対応するためにある機能です。DELPHIではIntefaceオブジェクトが無効(?)になるとき自動的に'_Release'が呼ばれ、コピーされるときに'_AddRef'が呼ばれます。この挙動は非常に便利で、いわゆる参照カウンターを持つオブジェクトとして扱うことができます。私は解放のタイミングが把握しにくいオブジェクトを扱う時などに使います。Interfaceを持つオーバーヘッドを考慮しても、メリットの方が大きいと考えます。

まずは以下のようにIntefaceを書いてみます。
  IType = interface
    procedure DoTest() ;
  end;

  TTestType = class(IType)
  public
    procedure DoTest() ;
  end;
DELPHIではクラスを継承するようにInterfaceを追加できます。Interfaceはあくまでインターフェイス(部品と部品を繋ぐ接点)なので、扱うにはどこかに実体が無いといけません。通常、実体はクラスの方で実装します。
実際に上記の例をビルドすると、QueryInterface,_AddRef,_Releaseが無いとエラーが出ます。DELPHIでは、どのInterfaceIInterfaceを継承しています。そのIInterfaceではQueryInterface,_AddRef,_Releaseが宣言されているため、クラスにこの3つのメソッドが実装されていないとコンパイルエラーがでます。
DELPHIには便利なTInterfacedObjectというクラスが用意されています。これは、QueryInterface,_AddRef,_Releaseがすでに実装されているクラスです。
次のようにTTestTypeを書き換えます。
  TTestType = class(TInterfacedObject,IType)
  public
    procedure DoTest() ;
  end;
使用する際は以下のようにします。
var
  intf : IType ;
  entity : TTestType ;
begin
  entity := TTestType.Create() ;
  intf := entity ;
  intf.DoTest() ;  // <- run
end ;
見て分るように単純にITypeの変数にTTestTypeのオブジェクトをコピーするだけで使えるようになります。
ここでオブジェクトの破棄について考えます。
1)上記のような参照カウンターを持つInterfaceを使用する際は、実体側のオブジェクト(上の例だと、entity)でオブジェクトの解放(free())やコピー・複製はしてはいけません。Intefaceの参照カウンターでオブジェクトのライフタイム制御しているので、別の次元でライフタイム操作を行うのは賢明とは思えません。
2)1)を踏まえて上記の例を考えます。Inteface変数は破棄される際に_Releaseが呼ばれ、参照カウンターが減りますので、intf変数は関数を抜ける際に参照カウンターはゼロとなり、実体であるentityオブジェクトは破棄されます。entity.free()など呼ぶと、実行時エラーが起きます。

class functionを使って以下のように書くと、うまく実体が隠せます。(ただし、これだけでは完全には隠せません・・・何か良い方法があるのでしょうか?コンストラクタを隠せばいいように感じますが・・・)
  TTestType = class(TInterfacedObject,IType)
  public
    procedure DoTest() ;
  public class
    function GetIntrf() : IType ;
  end;

...
 
class function TTestType.GetIntrf: IType;
begin
  exit(TTestType.Create()) ;
end;
次はもう少しだけDELPHIInterfaceを使ってみます。

ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。

0 件のコメント:

コメントを投稿