12

私は、特に複雑なインターフェースの実装を子オブジェクトに委譲するオブジェクトを持っています。これこそまさにの仕事だと思いますTAggregatedObject。「子」オブジェクトは「コントローラ」への弱い参照を維持し、すべてのQueryInterfaceリクエストは親に戻されます。IUnknown これにより、常に同じオブジェクトであるルールが維持されます。

したがって、私の親 (つまり"Controller"IStream ) オブジェクトは、インターフェイスを実装することを宣言します。

type
   TRobot = class(TInterfacedObject, IStream)
   private
      function GetStream: IStream;
   public
      property Stream: IStream read GetStrem implements IStream;
   end;

注:これは架空の例です。私がこの単語を選んだのは、Robot 複雑に聞こえるからです。また、単語の長さはわずか 5 文字で、短いためです。私もIStream短いので選びました。IPersistFileまたはを使用するつもりでし IPersistFileInitたが、それらは長くなり、サンプルコードを実現するのが難しくなります。言い換えれば、これは架空の例です。

これで、実装する子オブジェクトができましたIStream:

type
   TRobotStream = class(TAggregatedObject, IStream)
   public
      ...
   end;

残っているのはこれだけです。これが私の問題の始まりです。RobotStream要求されたときに作成することです。

function TRobot.GetStream: IStream;
begin
    Result := TRobotStream.Create(Self) as IStream;
end;

このコードはコンパイルに失敗し、エラーが発生しますOperator not applicable to this operand type.

これは、Delphi が をas IStream実装していないオブジェクトに対してを実行しようとしているためIUnknownです。

TAggregatedObject = class
 ...
   { IUnknown }
   function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
   function _AddRef: Integer; stdcall;
   function _Release: Integer; stdcall;
 ...

IUnknownメソッドが存在する可能性がありますが、オブジェクトはサポートしていることを通知しませんIUnknown。インターフェイスがなければIUnknown、DelphiQueryInterfaceはキャストを実行するために呼び出すことができません。

だから私は自分のTRobotStreamクラスを変更して、欠落しているインターフェースを実装していることを宣伝します(それはそうです;それはその祖先から継承しています):

type
   TRobotStream = class(TAggregatedObject, IUnknown, IStream)
   ...

そして今、それはコンパイルされますが、次の行で実行時にクラッシュします:

Result := TRobotStream.Create(Self) as IStream;

何が起こっているかはわかりますが、その理由を説明することはできません。Delphi はIntfClear、親Robotオブジェクトで、子オブジェクトのコンストラクタの途中で を呼び出しています。

これを防ぐ適切な方法がわかりません。私はキャストを強制しようとすることができます:

Result := TRobotStream.Create(Self as IUnknown) as IStream;

参照を維持することを願っています。参照を保持していることがわかりました-コンストラクターの途中でクラッシュしません。

注:これは私にとって混乱を招きます。インターフェイスが必要な場所にオブジェクトを渡しているためです。私は、コンパイラが暗黙的に型キャストを実行していると仮定します。

Result := TRobotStream.Create(Self IUnknownとして);

呼び出しを満たすために。構文チェッカーがエラーを出さなかったという事実から、すべてが正しいと思いました。


しかし、クラッシュは終わっていません。私は行を次のように変更しました:

Result := TRobotStream.Create(Self as IUnknown) as IStream;

そして、コードは確かにTRobotStream私の親オブジェクトを破壊することなくのコンストラクターから戻りますが、スタックオーバーフローが発生します。

その理由は、TAggregatedObjectすべてQueryInterface(つまり、型キャスト) を親オブジェクトに戻すことです。私の場合、 aTRobotStreamを anにキャストしていIStreamます。

TRobotStreamの最後にそのことを尋ねるIStreamと:

Result := TRobotStream.Create(Self as IUnknown) as IStream;

向きを変え、コントローラーIStreamインターフェイスを要求します。これにより、次の呼び出しがトリガーされます。

Result := TRobotStream.Create(Self as IUnknown) as IStream;
   Result := TRobotStream.Create(Self as IUnknown) as IStream;

向きを変えて呼び出します:

Result := TRobotStream.Create(Self as IUnknown) as IStream;
   Result := TRobotStream.Create(Self as IUnknown) as IStream;
      Result := TRobotStream.Create(Self as IUnknown) as IStream;

ブーム! スタックオーバーフロー。


やみくもに、最終的な へのキャストを削除してIStream、Delphi に暗黙的にオブジェクトをインターフェイスにキャストさせます (上記で見たものは正しく動作しません)。

Result := TRobotStream.Create(Self as IUnknown);

そして今、クラッシュはありません。私はこれをあまり理解していません。複数のインターフェイスをサポートするオブジェクトを作成しました。Delphi がインターフェースをキャストすることを知っているのはどうしてでしょうか? 適切な参照カウントを実行していますか? 私はそうではないことを上で見ました。顧客のためにクラッシュを待っている微妙なバグはありますか?

だから私は私の1行を呼び出すために4つの可能な方法が残っています. どれが有効ですか?

  1. Result := TRobotStream.Create(Self);
  2. Result := TRobotStream.Create(Self as IUnknown);
  3. Result := TRobotStream.Create(Self) as IStream;
  4. Result := TRobotStream.Create(Self as IUnknown) as IStream;

本当の質問

かなりの数の微妙なバグに遭遇し、コンパイラの複雑さを理解するのが困難です。これにより、私はすべてが完全に間違っていると信じるようになります。必要に応じて、私が言ったことをすべて無視して、質問に答えるのを手伝ってください。

インターフェイスの実装を子オブジェクトに委任する適切な方法は何ですか?

TContainedObjectの代わりに使用する必要があるかもしれませんTAggregatedObjectTAggregatedObjectおそらく、親が存在する必要があり、子が存在する場所で、2 つが連携して機能しTContainedObjectます。多分それは逆です。この場合はどちらにも当てはまらないかもしれません。

注:私の投稿の主要部分はすべて無視できます。それは私がそれについて考えたことを示すためでした。私が試したことを含めることで、可能な答えを毒殺したと主張する人がいます。人々は私の質問に答えるのではなく、私の失敗した質問に集中するかもしれません。

本当の目標は、インターフェイスの実装を子オブジェクトに委譲することです。この質問には、問題を解決するための私の詳細な試みが含まれてい TAggregatedObjectます。私の他の 2 つのソリューション パターンも表示されません。そのうちの 1 つは循環参照カウントに悩まされており、 は IUnknown等価規則を破っています。

ロブ・ケネディは覚えているかもしれません。そして、私の解決策の問題の解決策ではなく、問題の解決策を求める質問をするように頼まれました。

編集:文法化

編集 2:ロボット コントローラーのようなものはありません。そうですね - 私はいつも Funuc RJ2 コントローラーを使っていました。しかし、この例では違います!

編集 3*

  TRobotStream = class(TAggregatedObject, IStream)
    public
        { IStream }
     function Seek(dlibMove: Largeint; dwOrigin: Longint;
        out libNewPosition: Largeint): HResult; stdcall;
     function SetSize(libNewSize: Largeint): HResult; stdcall;
     function CopyTo(stm: IStream; cb: Largeint; out cbRead: Largeint; out cbWritten: Largeint): HResult; stdcall;
     function Commit(grfCommitFlags: Longint): HResult; stdcall;
     function Revert: HResult; stdcall;
     function LockRegion(libOffset: Largeint; cb: Largeint; dwLockType: Longint): HResult; stdcall;
     function UnlockRegion(libOffset: Largeint; cb: Largeint; dwLockType: Longint): HResult; stdcall;
     function Stat(out statstg: TStatStg; grfStatFlag: Longint): HResult; stdcall;
     function Clone(out stm: IStream): HResult; stdcall;

     function Read(pv: Pointer; cb: Longint; pcbRead: PLongint): HResult; stdcall;
     function Write(pv: Pointer; cb: Longint; pcbWritten: PLongint): HResult; stdcall;
  end;

  TRobot = class(TInterfacedObject, IStream)
  private
      FStream: TRobotStream;
      function GetStream: IStream;
  public
     destructor Destroy; override;
      property Stream: IStream read GetStream implements IStream;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
    rs: IStream;
begin
    rs := TRobot.Create;
    LoadRobotFromDatabase(rs); //dummy method, just to demonstrate we use the stream
    rs := nil;
end;

procedure TForm1.LoadRobotFromDatabase(rs: IStream);
begin
    rs.Revert; //dummy method call, just to prove we can call it
end;

destructor TRobot.Destroy;
begin
  FStream.Free;
  inherited;
end;

function TRobot.GetStream: IStream;
begin
  if FStream = nil then
     FStream := TRobotStream.Create(Self);
  result := FStream;
end;

ここでの問題は、次の呼び出し中に「親」TRobotオブジェクトが破棄されることです。

FStream := TRobotStream.Create(Self);
4

2 に答える 2

10

作成された子オブジェクトのフィールド インスタンスを追加する必要があります。

type
  TRobot = class(TInterfacedObject, IStream)
  private
     FStream: TRobotStream;
     function GetStream: IStream;
  public
     property Stream: IStream read GetStream implements IStream;
  end;

destructor TRobot.Destroy;
begin
  FStream.Free; 
  inherited; 
end;

function TRobot.GetStream: IStream;
begin
  if FStream = nil then 
    FStream := TRobotStream.Create(Self);
  result := FStream;
end;

更新 TRobotStream は、既に推測したように TAggregatedObject から派生する必要があります。宣言は次のようにする必要があります。

type
  TRobotStream = class(TAggregatedObject, IStream)
   ...
  end;

IUnknown について言及する必要はありません。

TRobot.GetStream では、この行result := FStreamは暗黙的FStream as IStreamに実行されるため、これを書き出す必要もありません。

FStream は IStream としてではなく TRobotStream として宣言する必要があるため、TRobot インスタンスが破棄されたときに FStream も破棄されます。注: TAggregatedObject には参照カウントがないため、コンテナーがその有効期間を処理する必要があります。

更新(Delphi 5 コード):

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, activex, comobj;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
  private
    procedure LoadRobotFromDatabase(rs: IStream);
  public
  end;

type
  TRobotStream = class(TAggregatedObject, IStream)
  public
    { IStream }
    function Seek(dlibMove: Largeint; dwOrigin: Longint;
       out libNewPosition: Largeint): HResult; stdcall;
    function SetSize(libNewSize: Largeint): HResult; stdcall;
    function CopyTo(stm: IStream; cb: Largeint; out cbRead: Largeint; out cbWritten: Largeint): HResult; stdcall;
    function Commit(grfCommitFlags: Longint): HResult; stdcall;
    function Revert: HResult; stdcall;
    function LockRegion(libOffset: Largeint; cb: Largeint; dwLockType: Longint): HResult; stdcall;
    function UnlockRegion(libOffset: Largeint; cb: Largeint; dwLockType: Longint): HResult; stdcall;
    function Stat(out statstg: TStatStg; grfStatFlag: Longint): HResult; stdcall;
    function Clone(out stm: IStream): HResult; stdcall;
    function Read(pv: Pointer; cb: Longint; pcbRead: PLongint): HResult; stdcall;
    function Write(pv: Pointer; cb: Longint; pcbWritten: PLongint): HResult; stdcall;
  end;

type
  TRobot = class(TInterfacedObject, IStream)
  private
    FStream: TRobotStream;
    function GetStream: IStream;
  public
    destructor Destroy; override;
    property Stream: IStream read GetStream implements IStream;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  rs: IStream;
begin
  rs := TRobot.Create;
  LoadRobotFromDatabase(rs); //dummy method, just to demonstrate we use the stream
  rs := nil;
end;

procedure TForm1.LoadRobotFromDatabase(rs: IStream);
begin
  rs.Revert; //dummy method call, just to prove we can call it
end;

function TRobotStream.Clone(out stm: IStream): HResult;
begin
end;

function TRobotStream.Commit(grfCommitFlags: Integer): HResult;
begin
end;

function TRobotStream.CopyTo(stm: IStream; cb: Largeint; out cbRead, cbWritten: Largeint): HResult;
begin
end;

function TRobotStream.LockRegion(libOffset, cb: Largeint; dwLockType: Integer): HResult;
begin
end;

function TRobotStream.Read(pv: Pointer; cb: Integer; pcbRead: PLongint): HResult;
begin
end;

function TRobotStream.Revert: HResult;
begin
end;

function TRobotStream.Seek(dlibMove: Largeint; dwOrigin: Integer;
  out libNewPosition: Largeint): HResult;
begin
end;

function TRobotStream.SetSize(libNewSize: Largeint): HResult;
begin
end;

function TRobotStream.Stat(out statstg: TStatStg; grfStatFlag: Integer): HResult;
begin
end;

function TRobotStream.UnlockRegion(libOffset, cb: Largeint; dwLockType: Integer): HResult;
begin
end;

function TRobotStream.Write(pv: Pointer; cb: Integer; pcbWritten: PLongint): HResult;
begin
end;

destructor TRobot.Destroy;
begin
  FStream.Free;
  inherited;
end;

function TRobot.GetStream: IStream;
begin
  if FStream = nil then
     FStream := TRobotStream.Create(Self);
  result := FStream;
end;

end.
于 2010-08-14T15:00:42.237 に答える
3

委任を行うクラスが特定のクラスから継承する必要はありません。適切なメソッドが実装されていれば、TObject から継承できます。簡単に説明し、すでに特定した 3 つのコア メソッドを提供する TInterfacedObject を使用して説明します。

また、必要ないはずですTRobotStream = class(TAggregatedObject, IUnknown, IStream)。代わりに、IStream が IUnknown から継承することを単純に宣言することもできます。ちなみに、私はいつもインターフェースに GUID を与えています (組み合わせ Ctrl+Shift+G を押します)。

特定のニーズに応じて適用できるさまざまなアプローチや手法が多数あります。

  • インターフェイス タイプへの委任
  • クラス Type への委譲
  • メソッドのエイリアシング

最も単純な委譲は、インターフェイスによるものです。

TRobotStream = class(TinterfacedObject, IStream)

TRobot = class(TInterfacedObject, IStream)
private
  //The delegator delegates the implementations of IStream to the child object.
  //Ensure the child object is created at an appropriate time before it is used.
  FRobotStream: IStream;
  property RobotStream: IStream read FRobotStream implements IStream;
end;

注意すべき点がいくつかあります。

  • 委任するオブジェクトの有効期間が適切であることを確認してください。
  • 委任先への参照を必ず保持してください。インターフェイスは参照カウントされ、カウントがゼロになるとすぐに破棄されることに注意してください。これが実際にあなたの頭痛の原因だったのかもしれません
于 2010-08-14T15:09:09.043 に答える