Delphiでのインターフェイスの使用の機能

Delphiのインターフェイスはすぐには表示されませんでしたが、COMの操作をサポートすることが必要になったとき、私の意見では、言語にうまく適合しませんでした。



率直に言って、私は通常、COMメカニズムを介して外部とやり取りするためではなく、インターフェイスを使用します。 そして、私だけではないことを疑います。 Delphiでは、インターフェイスが別の有用な用途を発見しました。



実際、インターフェイスは次の2つの場合に役立ちます。

  1. 多重継承を使用する場合。
  2. ARC(自動参照カウント)によりメモリ管理がはるかに簡単になる場合。


Delphiでは、他の一部のプログラミング言語(C ++など)で一般的に行われているように、フォームには歴史的に何も存在せず、フォームに多重継承はありません。 そしてそれは良いことです。



Delphiでは、多重継承の問題はインターフェイスによって解決されます。 インターフェイスは完全に抽象化されたクラスであり、そのすべてのメソッドは仮想および抽象です。 ( GunSmoker


そして、これは実際には真実ですが、そうではありません! インターフェイスは抽象クラスに非常に似ています。 これらは非常に似ていますが、最終的には、クラスとインターフェースの動作が大きく異なります。



今後の変更に関連して、つまり、新しいコンパイラでのARCの出現に伴い、Delphiオブジェクトの寿命を管理するトピックは、新しい「聖戦」が予測されるため、新しい関連性を獲得します。 私は今、どちらか一方を突然取って欲しくありません。私は、実用的なプログラマーとしてオブジェクトの寿命を管理するための「古典的な」アプローチと「リンク」メカニズムの交差する既存の領域を探りたいだけです。



それにもかかわらず、新しいコンパイラのARCがインターフェイスを単なる抽象クラスとして実際に認識することを可能にするという希望を表明できます。 私はそのような革命的な変化に注意して関係しているけれども。



多くの場合、データベースへの「インターフェイスマズル」のプログラマは、オブジェクトのメモリ管理の問題を無視します。これは、トピックの重要性を損なうものではありません。 私の意見では、クラスとインターフェースは非常に慎重に組み合わせる必要があります。 リンクカウンターのせいです。 これを理解するために、簡単な練習をしましょう。



例は、ワンボタンフォームです。 純粋にテストの例。 自宅でこれを繰り返さないでください。



unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type IMyIntf = interface procedure TestMessage; end; TMyClass = class(TInterfacedObject, IMyIntf) public procedure TestMessage; destructor Destroy; override; end; TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; procedure Button1Click(Sender: TObject); public procedure Kill(Intf: IMyIntf); end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var MyClass: TMyClass; begin Memo1.Clear; try MyClass := TMyClass.Create; try Kill(MyClass); finally MyClass.Free; end; except on E: Exception do Memo1.Lines.Add(E.Message); end; end; procedure TForm1.Kill(Intf: IMyIntf); begin Intf.TestMessage; end; { TMyClass } destructor TMyClass.Destroy; begin Form1.Memo1.Lines.Add('TMyClass.Destroy'); inherited; end; procedure TMyClass.TestMessage; begin Form1.Memo1.Lines.Add('TMyClass.TestMessage'); end; end.
      
      





開始し、ボタンを押すと、次のテキストがMemo1に表示されます。

  TMyClass.TestMessage
 TMyClass.Destroy
 TMyClass.Destroy
無効なポインター操作 


Destroyは2回呼び出され、その結果、「無効なポインター操作」になります。 なんで?



一度-これは理解できます。 Button1Clickハンドラーで、MyClass.Freeが呼び出されます。 二度目は? 問題の本質は、Killプロシージャにあります。 その実装の過程を分析します。



 //  Intf.RefCount = 0,     TInterfacedObject //  Intf      Kill //  Intf._AddRef,  RefCount = 1 procedure TForm1.Kill(Intf: IMyIntf); begin Intf.TestMessage; //     ,  Intf._Release // ,   RefCount   ,  : TMyClass.Destroy //     ,      ,  . //      . end;
      
      





つまり、問題は、TInterfacedObjectとその子孫の参照カウントがゼロであることです。 オブジェクトの場合、これは正常ですが、インターフェイスの場合、差し迫った死の兆候です。



誰が責任を負い、何をすべきか?


誰も責任があるとは思いません。 それをする必要はありません。 ガベージコレクタのない言語では、ライフタイムを制御したインターフェイスをより便利に実装できる可能性は低いです。 プログラマに_AddRefおよび_Releaseを明示的に呼び出すことを強制しない限り。 もっと便利になるとは思いません。



参照カウントの有無にかかわらず、2種類のインターフェイスを導入することもできましたが、これによりさらに混乱が生じます。



参照カウンタはインターフェースではなくオブジェクトに属していることを理解してください。 インターフェイスはこのカウンターのみを制御します。 Delphiに2つのタイプのインターフェイスがある場合、そのような状況で異なるタイプの2つのインターフェイスを実装するオブジェクトに対してどのように動作するか。 潜在的な落とし穴を探す余地はたくさんあります。



オブジェクト参照カウンタをリセットするには、_AddRefメソッドと_Releaseメソッドをオーバーライドして、参照カウンタをリセットしてもオブジェクトが解放されないようにします。 たとえば、このようにクラスを例から変更する(クラスがインターフェイスを継承できるように、3つのメソッド_AddRef、_Release、およびQueryInterfaceを実装する必要があります):



  TMyClass = class(TObject, IMyIntf) protected function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; public procedure TestMessage; destructor Destroy; override; end; function TMyClass.QueryInterface(const IID: TGUID; out Obj): HResult; begin if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; function TMyClass._AddRef: Integer; begin Result := -1; end; function TMyClass._Release: Integer; begin Result := -1; end;
      
      





ただし、同じインターフェイスが異なるオブジェクトに実装されているコードでは、参照カウンターを使用するか、使用しないかのどちらかで混乱しやすいため、このような手順は複雑さを増します。



ただし、VCLでは、参照カウンターの再定義が使用されます。 TComponentの子孫の場合、リンクカウンターは非常に複雑に機能します。



 function TComponent._AddRef: Integer; begin if FVCLComObject = nil then Result := -1 // -1 indicates no reference counting is taking place else Result := IVCLComObject(FVCLComObject)._AddRef; end; function TComponent._Release: Integer; begin if FVCLComObject = nil then Result := -1 // -1 indicates no reference counting is taking place else Result := IVCLComObject(FVCLComObject)._Release; end;
      
      





一方、状況にアプローチして、パラメーター定義にconstを追加することにより、Killプロシージャをわずかに変更できます。 この場合、リンクカウンターは単純に使用されないため、すべてが正常に機能し始めます。



 procedure TForm1.Kill(const Intf: IMyIntf); begin Intf.TestMessage; end;
      
      





これで、結果は次のようになります。

  TMyClass.TestMessage
 TMyClass.Destroy 


そして、問題を回避するための第1および第2の方法は、コードを操作するときに留意すべきニュアンスの数を増やすだけであり、それによって潜在的にエラーの数を増やす可能性があります。 そのため、このニーズが本当に存在する場合にのみ、インターフェースとオブジェクトを組み合わせて作業することをお勧めします。 そして、他のすべての場合には絶対に避けてください。



また、以前にVCLで作業しているときに、多くの人がインターフェースを使用する必要に直面したことがなかった場合は、クロスプラットフォームのように見える新しいFireMonkeyライブラリに照らして、「イデオロギー」に依存せずに内部のインターフェースの使用を慎重に監視する必要がありますスリムさ」Embarcaderoが提供する言語機能。



All Articles