DELPHIのIntefaceを実装するための簡単なメモを書いていきたいと思います。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では、どのInterfaceもIInterfaceを継承しています。その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;次はもう少しだけDELPHIのInterfaceを使ってみます。
ソースコードは自由にご使用ください。ただし問題が起きても責任はとれません。
0 件のコメント:
コメントを投稿