procedure UnSafeInftCall(Obj: TObject); begin
// Case 1
Obj.Hello; //<-- Syntax error
// Case 2
THello(Obj).Hello;
// Case 3
IHello(Obj).Hello; end;
procedure SafeInftCall(Obj: TObject); var
pIntfHello: IHello; begin
// Case 4 if Obj.GetInterface(IHello, pIntfHello) then
pIntfHello.Hello;
// Case 5 try
pIntfHello := Obj as IHello;
pIntfHello.Hello; exceptend;
// Case 6 if Supports(Obj, IHello, pIntfHello) then
pIntfHello.Hello; end;
使用 Supports 的2个问题
这个问题的发现实出偶然:网友许子健设计的一个接口应用中统一使用了 as 进行转换,而我当时推荐他使用 Supports,因为 Supports 在查询接口失败后并不抛异常,而是返回 False。虽然只是小小的代码改动,但是他的程序意外崩溃了。
请看下面的代码: type
THelloImplementor = class(TInterfacedObject, IHello) public procedure Hello; end;
procedure TestMe; var
Obj: THelloImplementor; begin
Obj := THelloImplementor.Create; try if Supports(Obj, IHello) then //<-- Obj.Destroy is called begin
//Own code end finally
ShowMessage(Obj.ClassName); //<-- Crashed!
Obj.Free; end; end;
奇怪吗,为什么用 Supports 查询接口出错了呢?通过调试发现,在执行Supports 之后,Obj 的实例被意外的释放了。于是乎意外应该是在 Supports 之内发生的。现在我们来看一下Supports 的实现: function Supports(const Instance: TObject; const IID: TGUID): Boolean; overload; var
Temp: IInterface; begin
Result := Supports(Instance, IID, Temp); end;
好了,下面再介绍一个隐藏的比较深的问题。这个和接口的委托机制有关。请看下面的代码: type
TVirtualImplementor = class(TInterfacedObject{TObject does not have problem}, IHello) public
FImplementorOfIHello: THelloImplementor; property ImplementorOfIHello: THelloImplementor readFImplementorOfIHello implements IHello; //<--Be careful! end;
procedure TestMeAgain; var
VI: TVirtualImplementor;
pIntfHello: IHello; begin
VI := TVirtualImplementor.Create; try
// Method 1 try
pIntfHello := VI as IHello;
pIntfHello.Hello; except end;
// Method 2 if Supports(VI, IHello, pIntfHello) then //<-- VI.Destroy is called
pIntfHello.Hello; finally
ShowMessage(VI.ClassName);//<-- Crashed!
VI.Free; end; end;
如果使用 as 做类型转换,程序是可以顺利运行的。但是为什么用Supports 就出错了呢?我们应该会很自然的联想上面那个问题。但问题是,这次接口指针 pIntfHello实实在在地获得了接口,而且在 VI 释放之前并没有清除,也就是说VI 不应该同上面的情况一样被自动销毁的。那么我们就再看一下 Supports 的实现: function Supports(const Instance: TObject; const IID: TGUID; out Intf): Boolean; overload; var
LUnknown: IUnknown; begin
Result := (Instance <> nil) and
((Instance.GetInterface(IUnknown, LUnknown) and Supports(LUnknown, IID, Intf)) or //<-- RefCount changed!
Instance.GetInterface(IID, Intf)); end;