Delphi:如何将接口实现委托给子对象?
我有一个对象,它将一个特别复杂的接口的实现委托给子对象。我认为这个 正是 是 TAggreatedObject
的工作。 “子”对象维护对其“控制器”的弱引用,并且所有 QueryInterface< /code> 请求被传递回父级。这维持了
IUnknown
的规则 始终是同一个对象。
因此,我的父对象(即“Controller”)声明它实现了 IStream 接口:
type
TRobot = class(TInterfacedObject, IStream)
private
function GetStream: IStream;
public
property Stream: IStream read GetStrem implements IStream;
end;
注意:这是一个假设的示例。我选择了“机器人”这个词 因为这听起来很复杂,并且 这个词只有 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;
此代码无法编译,出现错误运算符不适用于此操作数类型。
。
这是因为 delphi 试图在未实现 IUnknown
的对象上执行 as IStream
:
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;
现在我可以看看发生了什么,但我无法解释为什么。 Delphi 在子对象的构造函数中调用父 Robot
对象上的 IntfClear
。
我不知道防止这种情况的正确方法。我可以尝试强制演员表:
Result := TRobotStream.Create(Self as IUnknown) as IStream;
并希望保留一个参考。事实证明,它确实保留了引用——在构造函数之外没有崩溃。
注意:这让我感到困惑。因为我正在传递一个对象,其中 预计会有一个界面。我会 假设编译器是隐式的 执行类型转换,即:
Result := TRobotStream.Create(Self
as IUnknown);
为了满足召唤。这 事实上语法检查器没有 抱怨让我假设一切都是 正确。
但崩溃还没有结束。我已将该行更改为:
Result := TRobotStream.Create(Self as IUnknown) as IStream;
并且代码确实从 TRobotStream
的构造函数返回,而不破坏我的父对象,但现在我遇到了堆栈溢出。
原因是 TAggreatedObject
将所有 QueryInterface
(即类型转换)推迟回父对象。就我而言,我将 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;
Boom!堆栈溢出。
盲目地,我尝试删除对IStream,让 Delphi 尝试将对象隐式转换为接口(我刚刚在上面看到的不起作用):
Result := TRobotStream.Create(Self as IUnknown);
现在没有崩溃;我不太明白这一点。我构造了一个对象,一个支持多个接口的对象。现在 Delphi 知道如何转换接口了吗?它是否执行正确的引用计数?我在上面看到它没有。是否有一个微妙的错误等待着客户崩溃?
所以我只剩下四种可能的方式来拨打我的专线。其中哪一个是有效的?
结果 := TRobotStream.Create(Self);
结果 := TRobotStream.Create(Self as IUnknown);
结果 := TRobotStream.Create(Self) as IStream ;
Result := TRobotStream.Create(Self as IUnknown) as IStream;
真正的问题
我遇到了很多微妙的错误,并且很难理解编译器的复杂性。这让我相信我所做的一切都是完全错误的。如果需要的话,请忽略我所说的一切,并帮助我回答问题:
将接口实现委托给子对象的正确方法是什么?
也许我应该使用 TContainedObject
而不是 TAggreatedObject
。也许两者协同工作,其中父级应该是 TAggreatedObject
,子级是 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;
这里的问题是“父”TRobot
对象在调用期间被销毁:
FStream := TRobotStream.Create(Self);
i have an object which delegates implementation of a particularly complex interface to a child object. This is exactly i think is the job of TAggregatedObject
. The "child" object maintains a weak reference to its "controller", and all QueryInterface
requests are passed back to the parent. This maintains the rule that IUnknown
is always the same object.
So, my parent (i.e. "Controller") object declares that it implements the IStream
interface:
type
TRobot = class(TInterfacedObject, IStream)
private
function GetStream: IStream;
public
property Stream: IStream read GetStrem implements IStream;
end;
Note: This is a hypothetical example. i chose the word
Robot
because it sounds complicated, and and
word is only 5 letters long - it's
short. i also choseIStream
because
its short. i was going to useIPersistFile
orIPersistFileInit
,
but they're longer, and make the
example code harder to real. In other
words: It's a hypothetical example.
Now i have my child object that will implement IStream
:
type
TRobotStream = class(TAggregatedObject, IStream)
public
...
end;
All that's left, and this is where my problem starts: creating the RobotStream
when it's asked for:
function TRobot.GetStream: IStream;
begin
Result := TRobotStream.Create(Self) as IStream;
end;
This code fails to compile, with the error Operator not applicable to this operand type.
.
This is because delphi is trying to perform the as IStream
on an object that doesn't implement IUnknown
:
TAggregatedObject = class
...
{ IUnknown }
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
...
The IUnknown methods may be there, but the object doesn't advertise that it supports IUnknown
. Without an IUnknown
interface, Delphi can't call QueryInterface
to perform the cast.
So i change my TRobotStream
class to advertise that it implements the missing interface (which it does; it inherits it from its ancestor):
type
TRobotStream = class(TAggregatedObject, IUnknown, IStream)
...
And now it compiles, but crashes at runtime on the line:
Result := TRobotStream.Create(Self) as IStream;
Now i can see what's happening, but i can't explain why. Delphi is calling IntfClear
, on my parent Robot
object, on the way out of the child object's constructor.
i don't know the proper way to prevent this. i could try forcing the cast:
Result := TRobotStream.Create(Self as IUnknown) as IStream;
and hope that keeps a reference. Turns out that it does keep the reference - no crash on the way out of the constructor.
Note: This is confusing to me. Since i am passing an object where
an interface is expected. i would
assume that the compiler is implicitly
preforming a typecast, i.e.:
Result := TRobotStream.Create(Self
as IUnknown);
in order to satisfy the call. The
fact that the syntax checker didn't
complain let me to assume all was
correct.
But the crashes aren't over. i've changed the line to:
Result := TRobotStream.Create(Self as IUnknown) as IStream;
And the code does indeed return from the constructor of TRobotStream
without destroying my parent object, but now i get a stack overflow.
The reason is that TAggregatedObject
defers all QueryInterface
(i.e. type casts) back to the parent object. In my case i am casting a TRobotStream
to an IStream
.
When i ask the TRobotStream
for its IStream
at the end of:
Result := TRobotStream.Create(Self as IUnknown) as IStream;
It turns around and asks its controller for the IStream
interface, which triggers a call to:
Result := TRobotStream.Create(Self as IUnknown) as IStream;
Result := TRobotStream.Create(Self as IUnknown) as IStream;
which turns around and calls:
Result := TRobotStream.Create(Self as IUnknown) as IStream;
Result := TRobotStream.Create(Self as IUnknown) as IStream;
Result := TRobotStream.Create(Self as IUnknown) as IStream;
Boom! Stack overflow.
Blindly, i try removing the final cast to IStream
, let Delphi try to implicitely cast the object to an interface (which i just saw above doesn't work right):
Result := TRobotStream.Create(Self as IUnknown);
And now there is no crash; which i don't understand this very much. i've constructed an object, an object which supports multiple interfaces. How is it now that Delphi knows to cast the interface? Is it performing the proper reference counting? i saw above that it doesn't. Is there a subtle bug waiting to crash for the customer?
So i'm left with four possible ways to call my one line. Which one of them is valid?
Result := TRobotStream.Create(Self);
Result := TRobotStream.Create(Self as IUnknown);
Result := TRobotStream.Create(Self) as IStream;
Result := TRobotStream.Create(Self as IUnknown) as IStream;
The Real Question
i hit quite a few subtle bugs, and difficult to understand intricacies of the compiler. This leads me to believe that i have done everything completely wrong. If needed, ignore everything i said, and help me answer the question:
What is the proper way to delegate interface implementation to a child object?
Maybe i should be using TContainedObject
instead of TAggregatedObject
. Maybe the two work in tandem, where the parent should be TAggregatedObject
and the child is TContainedObject
. Maybe it's the other way around. Maybe neither apply in this case.
Note: Everything in the main part of my post can be ignored. It was just
to show that i have thought about it.
There are those who would argue that
by including what i have tried, i have
poisoned the possible answers; rather
than answering my question, people
might focus on my failed question.The real goal is to delegate interface
implementation to a child object. This
question contains my detailed attempts
at solving the problem withTAggregatedObject
. You don't even
see my other two solution patterns.
One of which suffers from circular
refernce counts, and the breaks theIUnknown
equivalence rule.Rob Kennedy might remember; and asked
me to make a question that asks for a
solution to the problem, rather than a
solution to a problem in one of my
solutions.
Edit: grammerified
Edit 2: No such thing as a robot controller. Well, there is - i worked with Funuc RJ2 controllers all the time. But not in this example!
Edit 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;
Problem here is that the "parent" TRobot
object is destroyed during the call to:
FStream := TRobotStream.Create(Self);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您必须为创建的子对象添加一个字段实例:
更新
正如您已经猜到的那样,TRobotStream 应该派生自 TAggregateObject。声明应该是:
没有必要提及 IUnknown。
在 TRobot.GetStream 中,
result := FStream
行隐式执行FStream as IStream
,因此也没有必要将其写出。FStream 必须声明为 TRobotStream 而不是 IStream,因此它可以在 TRobot 实例被销毁时被销毁。注意:TAggregatedObject 没有引用计数,因此容器必须注意其生命周期。
更新(Delphi 5 代码):
You have to add a field instance for the created child object:
Update
TRobotStream should be derived from TAggregatedObject as you already guessed. The declaration should be:
It is not necessary to mention IUnknown.
In TRobot.GetStream the line
result := FStream
does an impliciteFStream as IStream
so writing this out isn't necessary either.FStream has to be declared as TRobotStream and not as IStream so it can be destroyed when the TRobot instance is destroyed. Note: TAggregatedObject has no reference counting so the container has to take care of its lifetime.
Update (Delphi 5 code):
进行委托的类不需要从任何特定类继承。如果已实现适当的方法,您可以从 TObject 继承。我将保持简单并使用 TInterfacedObject 进行说明,它提供了您已经确定的 3 个核心方法。
另外,您不需要
TRobotStream = class(TAggreatedObject, IUnknown, IStream)
。您可以简单地声明 IStream 继承自 IUnknown。顺便说一句,我总是给我的界面一个 GUID(按组合键 Ctrl+Shift+G)。根据您的特定需求,可以应用多种不同的方法和技术。
最简单的委托是通过接口。
也许有一些事情需要注意:
There is no need for your class that does the delegation to inherit from any particular class. You could inherit from TObject provided the appropriate methods have been implemented. I'll keep things simple and illustrate using TInterfacedObject which provides the 3 core methods which you have already identified.
Also, you should not need
TRobotStream = class(TAggregatedObject, IUnknown, IStream)
. You could instead simply declare that IStream inherits from IUnknown. By the way, I always give my interfaces a GUID (Press the conbination Ctrl+Shift+G).There are a number of different approaches and techniques that can be applied depending on your particular needs.
The simplest delegation is by interface.
There are perhaps a few thing to watch out for: