開発ブログ - TInterfacedObject ではまる
知ってはいたんですよ。
TInterfacedObject の自動破棄については…
いえ、知っているつもりだったんです。
TInterfacedObject を継承して作ったクラスを使うときには Free をしちゃだめ!
という知識はありました。
ただ、それは局所変数で処理しているから、局所変数がなくなったときに自動破棄されるものだと思っていたのです。
知っていたサンプル
たとえば下に示すようなクラスを作ったとします。
単に Test というプロシジャだけを持つ interface と、それを実装した TInterfacedObject を継承したクラスです。
interface
uses
System.Classes;
type
ITestInterface = interface
['{7723B047-28A4-4EF1-8836-0BE06092F2F5}']
procedure Test;
end;
TTestClass = class(TInterfacedObject, ITestInterface)
public
constructor Create;
destructor Destroy; override;
procedure Test;
end;
implementation
uses
Vcl.Dialogs;
{ TTestClass }
constructor TTestClass.Create;
begin
inherited;
ShowMessage('Create!');
end;
destructor TTestClass.Destroy;
begin
ShowMessage('Destroy!!');
inherited;
end;
procedure TTestClass.Test;
begin
ShowMessage('Test');
end;
end.
問題が起きるのは次のような書き方をした場合だと思っていました。
procedure Test;
var
T: TTestClass;
begin
T := TTestClass.Create;
(T as ITestInterface).Test;
T.Free;
end;
大丈夫だと思っていたサンプル
フォームからこのクラスを使おうとして、フォーム生成時にクラスを作成し、ボタンが押されたら Test メソッドを呼び出す、というコードを書いてみました。
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
FTest: TTestClass;
public
{ Public 宣言 }
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
FTest := TTestClass.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FTest);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
(FTest as ITestInterface).Test;
end;
このコードを実行すると、2回目にボタンを押すと例外が発生してしまいます。
(FTest as ITestInterface).Test;
を実行すると、Button1Click を抜けるタイミングで FTest の実体が解放されてしまうためです。
えー!そんなぁ!!!
このサンプルくらいなら FTest を ITestInterface で宣言するという手が使えますが、複数の Interface を持つクラスを保持するためにその数分の変数を用意するのはないなぁ…と思うのです。
やはり TInterfacedObject を Java の Interface 的に使うのは、どんなときでもだめ!ということですね。
今回、かなり複雑な仕組みのプロジェクトに、TInterfaceObject と Interface の機構を後から組み込んでしまい、わけのわからないバグに悩まされてしまいました。
もう絶対 TInterfacedObject は使わないことを心に誓いましたよ。
解決策は?
参考サイトの情報を参照してください〜
[参考サイト]
Delphi/interfaceを使おう/何が問題になるか?
TInterfacedObject で自動破棄....の前に