Delphi:如何将子接口实现委托给子对象?

Ian*_*oyd 11 delphi interface delegation delphi-5

我有一个对象,它将一个特别复杂的接口的实现委托给子对象.这正是我认为的工作TAggregatedObject." child "对象维护对其" 控制器 " 的弱引用,并且所有QueryInterface请求都传递回父级.这样可以保持IUnknown 始终为同一对象的规则.

所以,我的父(即"Controller")对象声明它实现了IStream接口:

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

注意:这是一个假设的例子.我选择这个词Robot 是因为它听起来很复杂,并且单词只有5个字母 - 它很短.我也选择IStream因为它的短.我打算使用 IPersistFileIPersistFileInit,但它们更长,并使示例代码更难实现.换句话说:这是一个假设的例子.

现在我有我的子对象将实现IStream:

type
   TRobotStream = class(TAggregatedObject, IStream)
   public
      ...
   end;
Run Code Online (Sandbox Code Playgroud)

所有剩下的,这就是我的问题开始的地方:创建RobotStream它被要求的时间:

function TRobot.GetStream: IStream;
begin
    Result := TRobotStream.Create(Self) as IStream;
end;
Run Code Online (Sandbox Code Playgroud)

此代码无法编译,错误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;
 ...
Run Code Online (Sandbox Code Playgroud)

IUnknown 方法可能存在,但该对象不会通告它支持IUnknown.没有IUnknown接口,Delphi无法调用QueryInterface执行转换.

所以我改变我的TRobotStream类来宣传它实现了缺少的接口(它做了;它从它的祖先继承它):

type
   TRobotStream = class(TAggregatedObject, IUnknown, IStream)
   ...
Run Code Online (Sandbox Code Playgroud)

现在它编译,但在运行时崩溃:

Result := TRobotStream.Create(Self) as IStream;
Run Code Online (Sandbox Code Playgroud)

现在我可以看到什么情况发生,但我无法解释为什么.Delphi IntfClear在我的父Robot对象上调用了从子对象的构造函数出来的路径.

我不知道防止这种情况的正确方法.我可以尝试强迫演员:

Result := TRobotStream.Create(Self as IUnknown) as IStream;
Run Code Online (Sandbox Code Playgroud)

并希望保留参考.事实证明它确实保留了引用 - 在构造函数的出路上没有崩溃.

注意:这让我很困惑.因为我传递了一个预期接口对象.我会假设编译器隐式预先形成类型转换,即:

Result := TRobotStream.Create(Self 就像IUnknown);

为了满足这个要求.语法检查器没有抱怨的事实让我认为一切都是正确的.


但崩溃还没有结束.我把线改为:

Result := TRobotStream.Create(Self as IUnknown) as IStream;
Run Code Online (Sandbox Code Playgroud)

并且代码确实从构造函数返回TRobotStream而没有破坏我的父对象,但现在我得到了堆栈溢出.

原因是TAggregatedObject将所有QueryInterface(即类型转换)推迟回父对象.在我的情况下,我正在铸造TRobotStream一个IStream.

当我在最后询问TRobotStreamIStream时:

Result := TRobotStream.Create(Self as IUnknown) as IStream;
Run Code Online (Sandbox Code Playgroud)

它转过来并询问其控制器IStream接口,这会触发对以下内容的调用:

Result := TRobotStream.Create(Self as IUnknown) as IStream;
   Result := TRobotStream.Create(Self as IUnknown) as IStream;
Run Code Online (Sandbox Code Playgroud)

转过来并打电话:

Result := TRobotStream.Create(Self as IUnknown) as IStream;
   Result := TRobotStream.Create(Self as IUnknown) as IStream;
      Result := TRobotStream.Create(Self as IUnknown) as IStream;
Run Code Online (Sandbox Code Playgroud)

繁荣! 堆栈溢出.


盲目地,我尝试删除最终的强制转换IStream,让Delphi尝试隐含地将对象强制转换为接口(我刚刚看到上面的代码无效):

Result := TRobotStream.Create(Self as IUnknown);
Run Code Online (Sandbox Code Playgroud)

现在没有崩溃; 我对此不太了解.我构建了一个对象,一个支持多个接口的对象.现在,Delphi知道如何构建界面?它是否正在执行正确的引用计数?我在上面看到它没有.是否有一个微妙的错误等待客户崩溃?

所以我留下了四种可能的方式来调用我的一行.哪一项有效?

  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而不是TAggregatedObject.也许两者一起工作,父母应该TAggregatedObject和孩子在一起TContainedObject.也许这是相反的方式.也许在这种情况下都不适用.

注意:我的帖子主要部分中的所有内容都可以忽略.这只是为了表明我已经考虑过了.有些人会争辩说,通过包括我所尝试的内容,我已经毒害了可能的答案; 人们可能会关注我失败的问题,而不是回答我的问题.

真正的目标是将接口实现委托给子对象.这个问题包含了我解决问题的详细尝试 TAggregatedObject.你甚至没有看到我的其他两种解决方案模式.其中一个遭受循环引用计数,并打破 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;
Run Code Online (Sandbox Code Playgroud)

这里的问题是"父" TRobot对象在调用期间被销毁:

FStream := TRobotStream.Create(Self);
Run Code Online (Sandbox Code Playgroud)

Uwe*_*abe 9

您必须为创建的子对象添加字段实例:

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;
Run Code Online (Sandbox Code Playgroud)

正如您已经猜到的那样,更新 TRobotStream应该从TAggregatedObject派生.声明应该是:

type
  TRobotStream = class(TAggregatedObject, IStream)
   ...
  end;
Run Code Online (Sandbox Code Playgroud)

没有必要提到IUnknown.

在TRobot.GetStream中,该行result := FStream有一个含义,FStream as IStream所以写出来也没有必要.

FStream必须声明为TRobotStream而不是IStream,因此当TRobot实例被销毁时它可以被销毁.注意: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.
Run Code Online (Sandbox Code Playgroud)