私は、特に複雑なインターフェースの実装を子オブジェクトに委譲するオブジェクトを持っています。これこそまさにの仕事だと思います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つの可能な方法が残っています. どれが有効ですか?
Result := TRobotStream.Create(Self);
Result := TRobotStream.Create(Self as IUnknown);
Result := TRobotStream.Create(Self) as IStream;
Result := TRobotStream.Create(Self as IUnknown) as IStream;
本当の質問
かなりの数の微妙なバグに遭遇し、コンパイラの複雑さを理解するのが困難です。これにより、私はすべてが完全に間違っていると信じるようになります。必要に応じて、私が言ったことをすべて無視して、質問に答えるのを手伝ってください。
インターフェイスの実装を子オブジェクトに委任する適切な方法は何ですか?
TContainedObject
の代わりに使用する必要があるかもしれませんTAggregatedObject
。TAggregatedObject
おそらく、親が存在する必要があり、子が存在する場所で、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);