インターフェイスの混乱を解決する方法

私は、無関係の異なるクラスに共通の機能を与える方法として、常にインターフェイスについて考えていました。しかし、「RefCOuntがゼロになったときにオブジェクトを解放する」というインターフェースの性質は、私が望むように働くことを許しません。

例:私は、TMyObjectとTMyDifferentObjectという2つの異なるクラスがあると仮定します。両方ともこのインタフェースをサポートしています:

const
  IID_MyInterface: TGUID = '{4D91C27F-510D-4673-8773-5D0569DFD168}';

type
 IMyInterface = Interface(IInterface)
  ['{4D91C27F-510D-4673-8773-5D0569DFD168}']
  function GetID : Integer;
 end;

type
  TMyObject = class(TInterfacedObject, IMyInterface)
    function GetID: Integer;
  end;

function TMyObject.GetID: Integer;
begin
  Result := 1;
end;


type
  TMyDifferentObject = class(TInterfacedObject, IMyInterface)
    function GetID: Integer;
  end;

function TMyDifferentObject.GetID: Integer;
begin
  Result := 2;
end;

今、私のプログラムでこのクラスのインスタンスを作成し、そのインスタンスをこのメソッドに渡したいと思います。

procedure ShowObjectID(AObject: TObject);
var
  MyInterface: IMyInterface;
begin
  if Supports(AObject, IID_MyInterface, MyInterface) then
  begin
    ShowMessage(IntToStr(MyInterface.GetID));
  end;
end;  //Interface goes out of scope and AObject is freed but I still want to work with that object!

これは一例です。一般的に私はオブジェクトのインスタンスをいくつかのプロシージャに渡し、このオブジェクトがインタフェースをサポートしているかどうかを確認したい場合は、このインタフェースのメソッドを実行します。しかし、私はインターフェイスが範囲外になると、そのオブジェクトで作業を終了したくありません。これを行う方法?

よろしく。

2
参照カウントを無効にすることができます。 IInterfaceがTComponentでどのように実装されているかを見てください。
追加された 著者 David Heffernan,

3 答え

あなたの問題はおそらく、オブジェクト参照を使用してオブジェクトを作成したことに起因します。

var
  MyObject: TObject;
begin
  MyObject := TMyObject.Create;
  ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  ShowObjectID(MyObject);
  ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;

これは、作成後のRefCountがゼロであることを意味します。必要に応じてオブジェクトをインタフェース参照に割り当てるか、

var
  MyObject: TMyObject;
  MyIntf: IMyInterface;
begin
  MyObject := TMyObject.Create;
  MyIntf := MyObject;
  ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  ShowObjectID(MyObject);
  ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  MyIntf := nil;
  ShowMessage('After nilling the interface MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;

Davidがコメントで示唆したようにrefcountingを無効にするか、無効にします。基本的には、独自の「TInterfacedObject」を宣言し、3つのIInterfaceメソッドを実装することです。

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;

本質は、_AddRefと_Releaseの両方で -1 を返すことです。 Davidが言ったように:TComponentがどのようにそれを行うのか見てみましょう。そして、FVCLComObjectがnilのときに行っていることを実行します。

8
追加された
本質は参照を数えないことです。 -1を返すことは、参照カウントを無効にするものではありません。
追加された 著者 Rob Kennedy,
@Wodzuは、解放されたオブジェクトのフィールドにアクセスしても、必ずしもプログラムがクラッシュするとは限りません。メモリがOSに返されない限り、プロセスがオブジェクトに割り当てられていなくても、OSはそれがあなたのプロセスに属していると考えているので、アクセス違反はそのメモリから読み取ることは不可能です。インタフェース自体を指すのではなく、インタフェースを参照することについて話をするのはナンセンスです。オブジェクトなしでは、インタフェースがありません。インタフェースにはそれ自身のデータは含まれていません。
追加された 著者 Rob Kennedy,
いいえ、@マージャン、戻り値は完全に無関係です。 AddRefの戻り値でもルックするコードは決して見つかりません。参照カウントを無効にするのは、AddRefを実装し、参照の数を更新するコードを書かないことです。オブジェクトは独自の参照を追跡します。オブジェクトが自身の参照カウントを0に減らすと、オブジェクトはそれを認識し自身を削除します。したがって、参照カウントを無効にする必要はありません。 _Release で自己削除の手順をスキップするだけです。
追加された 著者 Rob Kennedy,
@ Wodzu:おそらく。コンパイラによって導入された一時変数は時々、refcountをゼロ以上に保ちますが、プロシージャ/ファンクションが終了するまでインスタンスは周りに保持されます。
追加された 著者 Marjan Venema,
#Rob:-1を返すとrefcountingが無効にならない場合はどうなりますか?ああ、私があなたが意味するものを見ていると思います。 -1を返すことは、「実際には参照を数えない方法です」(したがって、参照カウントがゼロになるのを避ける)
追加された 著者 Marjan Venema,
@ロブ:説明をありがとう。それを読んで自分を "D'oh"にしました - 私は私の脳のかゆみのために残っている片頭痛を主張します:-)
追加された 著者 Marjan Venema,
答えMarjanに感謝しますが、私は一つのことを得ることはありません。 RefCountが0に落ちた場合、MyObjectは自動的に解放されました。しかし、あなたはMyObject.RefCountをしていますが、あなたはAccesViolationを取得していません。コンパイラは、あなた自身がインターフェイスではなくインターフェイスになることを認識していますか?
追加された 著者 Wodzu,
@Robしたがって、Marjanの例の最後のコード行は間違っています(インターフェイスのnilling後にRefCountが表示されます)。
追加された 著者 Wodzu,

あなたの問題を解決するための1つのアプローチは、コードを変更して、インタフェース参照によってオブジェクトを参照することだけです。言い換えれば、

var
  obj: TMyObject;
...
obj := TMyObject.Create;
try
  obj.DoStuff;
  //etc. etc.
finally
  obj.Free;
end;

あなたが書く

var
  obj: IMyObject;//NOTE: interface variable
...
obj := TMyObject.Create;
obj.DoStuff;
//etc. etc.
obj := nil;//or let it go out of scope and release that way

これは不便なので、代わりに自動生涯管理を無効にする方が便利です。実装するオブジェクトに対してこれを行う必要があります:

type
  TInterfacedObjectWithoutLifetimeManagement = class(TObject, IInterface)
  private
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

function TInterfacedObjectWithoutLifetimeManagement.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TInterfacedObjectWithoutLifetimeManagement._AddRef: Integer;
begin
  Result := -1;
end;

function TInterfacedObjectWithoutLifetimeManagement._Release: Integer;
begin
  Result := -1;
end;

このクラスからクラスを派生させることができます。

このアプローチには非常に大きな警告が1つあります。 TInterfacedObjectWithoutLifetimeManagement から派生したクラスによって実装されているインタフェースを変数(ローカル、グローバル、クラスメンバ)で保持しているとします。すべてのそのようなインタフェース変数は、実装オブジェクトに対して Free を呼び出す前に確定する必要があります。

この規則に従わないと、それらのインタフェース変数が有効範​​囲外になると、コンパイラは引き続き _Release を呼び出すコードを発行し、オブジェクトのメソッドを呼び出すとエラーになります破壊されました。これは特に重要なクライアントのマシンでコードが実行されるまでランタイムエラーが発生しないため、特に厄介なタイプのエラーです。換言すれば、そのようなエラーは断続的な性質を有する可能性がある。

3
追加された
素晴らしいクラス名( TInterfacedObjectWithoutLifetimeManagement )。理解できるように+1してください。
追加された 著者 Warren P,

これまで誰も言及していないもう1つのオプションは、必要な限りオブジェクトインスタンスを明示的に呼び出して _Release を呼び出すことです。

3
追加された
実際には非常に単純なオプションです。多くの場合、良い意味で、後で_releaseを呼び出すと参照カウントがゼロになり、オブジェクトは最終的に解放され、リークされません。
追加された 著者 Warren P,