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


我有一个对象,它将一个特别复杂的接口的实现委托给子对象。这个 确切地说 我认为是工作 TAggregatedObject。 “儿童“对象保持对它的弱引用”调节器“, 和所有 QueryInterface 请求将传递回父级。这保持了规则 IUnknown   永远是同一个对象。

所以,我的父母(即 “控制器”)object声明它实现了 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.

这是因为德尔福正试图执行 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 界面,Delphi无法调用 QueryInterface 执行演员表演。

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

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

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

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

现在我可以看到 什么是 发生了,但我无法解释 为什么。德尔福正在打电话 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 (即类型转换)返回父对象。就我而言,我正在施展 TRobotStream 到了 IStream

当我问的时候 TRobotStream 为了它 IStream 在......的最后:

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

它转过身来问它 调节器 为了 IStreaminterface,触发对以下内容的调用:

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. 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 等价规则。

罗伯肯尼迪可能还记得;并问道   我提出一个要求问题的问题   解决问题的方法,而不是问题   解决我的一个问题   解决方案。

编辑: grammerified

编辑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);

1170
2017-08-14 14:13


起源



答案:


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

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必须声明为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.
enter code here

9
2017-08-14 15:00



这似乎可以解决我遇到的所有问题。但是TRobotStream的声明怎么样?它是否来自TAggregatedObject? TContainedObject?我是否指定它暴露IUnknown?我是否必须将FStream转换为IUnknown?我是不是该 不 将FStream转换为IUnknown?如果我将FStream转换为IUnknown,这无关紧要吗? - Ian Boyd
我已经更新了我的答案以澄清事情。 - Uwe Raabe
是不是同时保留了类引用(fstream)和将接口引用分配给灾难的方法?类引用不计入接口引用计数,因此在第一个分发的接口引用终止后,类也是如此。克雷格·杨或多或少在下面的答案中也这么说,因此获得+1。 - Marco van de Voort
@Marco:你的担忧很明显,但仔细看看TAggregatedObject及其描述。其接口的引用计数被委托给容器。不仅可以保持对对象实例的引用,还必须在销毁容器期间销毁对象。问题中描述的问题正是TAggregatedObject的用途。它只是以错误的方式使用。 - Uwe Raabe
好吧仍然无法正常工作。使用您的声明,尝试调用 var rs: IStream; rs := TRobot.Create; 在创建期间 TRobotStream 引用计数回到零,破坏了自我 TRobot 对象,虽然我们在里面,但在线上崩溃 Result := Stream;。 - Ian Boyd


答案:


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

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必须声明为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.
enter code here

9
2017-08-14 15:00



这似乎可以解决我遇到的所有问题。但是TRobotStream的声明怎么样?它是否来自TAggregatedObject? TContainedObject?我是否指定它暴露IUnknown?我是否必须将FStream转换为IUnknown?我是不是该 不 将FStream转换为IUnknown?如果我将FStream转换为IUnknown,这无关紧要吗? - Ian Boyd
我已经更新了我的答案以澄清事情。 - Uwe Raabe
是不是同时保留了类引用(fstream)和将接口引用分配给灾难的方法?类引用不计入接口引用计数,因此在第一个分发的接口引用终止后,类也是如此。克雷格·杨或多或少在下面的答案中也这么说,因此获得+1。 - Marco van de Voort
@Marco:你的担忧很明显,但仔细看看TAggregatedObject及其描述。其接口的引用计数被委托给容器。不仅可以保持对对象实例的引用,还必须在销毁容器期间销毁对象。问题中描述的问题正是TAggregatedObject的用途。它只是以错误的方式使用。 - Uwe Raabe
好吧仍然无法正常工作。使用您的声明,尝试调用 var rs: IStream; rs := TRobot.Create; 在创建期间 TRobotStream 引用计数回到零,破坏了自我 TRobot 对象,虽然我们在里面,但在线上崩溃 Result := Stream;。 - Ian Boyd


您的类不需要委派从任何特定类继承。只要已经实现了适当的方法,您就可以从TObject继承。我将保持简单并使用TInterfacedObject进行说明,它提供了您已经识别的3种核心方法。

此外,你不应该需要 TRobotStream = class(TAggregatedObject, IUnknown, IStream)。您可以简单地声明IStream继承自IUnknown。顺便说一句,我总是给我的接口一个GUID(按Ctrl + Shit + G)。

根据您的特定需求,可以应用许多不同的方法和技术。

  • 委托接口类型
  • 委托上课类型
  • 方法别名

最简单的委托是通过接口。

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;

也许有一些事情需要注意:

  • 确保您委派的对象具有适当的生命周期。
  • 务必保留对被请求者的引用。请记住,接口是引用计数,并且一旦计数降至零就会被销毁。 这实际上可能是您头痛的原因

3
2017-08-14 15:09



IStream是一个标准接口。一个COM流可以这么说 - Marco van de Voort
答案是缺少创建内部对象的代码 FRobotStream,它是从哪里下来的,它是如何构建的等等 - Ian Boyd