我有一个对象,它将一个特别复杂的接口的实现委托给一个子对象。这正是我认为的工作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
它,因为它很短。我打算使用IPersistFile
orIPersistFileInit
,但它们更长,并且使示例代码更难实现。换句话说:这是一个假设的例子。
现在我有我的子对象将实现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 投射TRobotStream
到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 知道如何转换接口?它是否执行正确的引用计数?我在上面看到它没有。是否有一个微妙的错误等待客户崩溃?
所以我有四种可能的方式来拨打我的一条线。其中哪一个是有效的?
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
孩子在哪里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);