12

我有一个对象,它将一个特别复杂的接口的实现委托给一个子对象。这正是我认为的工作TAggregatedObject。“”对象维护对其“控制器”的弱引用,所有QueryInterface请求都传回父对象。这维护了IUnknown 始终是同一个对象的规则。

所以,我的父(即“控制器”)对象声明它实现了IStream接口:

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

注意:这是一个假设的例子。我选择这个词Robot 是因为它听起来很复杂,而且这个词只有 5 个字母长——它很短。我也选择了IStream它,因为它很短。我打算使用 IPersistFileor 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接口,Delphi 不能调用QueryInterface来执行转换。

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

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

现在它可以编译了,但是在运行时就崩溃了:

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

现在我可以看到发生了什么,但我无法解释为什么。DelphiIntfClear在我的父Robot对象上调用子对象的构造函数。

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

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

并希望保持参考。事实证明,它确实保留了引用 - 在构造函数中没有崩溃。

注意:这让我很困惑。因为我正在传递一个需要接口的对象。我会假设编译器正在隐式执行类型转换,即:

Result := TRobotStream.Create(Self 作为我未知);

为了满足调用。语法检查器没有抱怨的事实让我假设一切都是正确的。


但崩溃还没有结束。我已将行更改为:

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

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

原因是将所有(即类型转换)TAggregatedObject推迟到父对象。QueryInterface就我而言,我正在将 a 投射TRobotStreamIStream.

当我在末尾询问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. 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等价规则。

Rob Kennedy 可能还记得;并要求我提出一个问题,要求解决问题,而不是在我的一个解决方案中解决问题。

编辑:语法化

编辑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 必须声明为 TRobotStream 而不是 IStream,以便在销毁 TRobot 实例时将其销毁。注意:TAggregatedObject 没有引用计数,因此容器必须注意其生命周期。

更新(德尔福 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 继承,前提是已经实现了适当的方法。我将保持简单并使用 TInterfacedObject 进行说明,它提供了您已经确定的 3 个核心方法。

此外,您不应该需要TRobotStream = class(TAggregatedObject, IUnknown, IStream). 您可以改为简单地声明 IStream 继承自 IUnknown。顺便说一句,我总是给我的界面一个 GUID(按组合 Ctrl+Shift+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;

或许有几点需要注意:

  • 确保您委派的对象具有适当的生命周期。
  • 请务必保留对受委托人的引用。请记住,接口是引用计数的,一旦计数下降到零,就会被销毁。这实际上可能是您头痛的原因
于 2010-08-14T15:09:09.043 回答