查询接口小议

原创 2007年08月02日 11:59:00
前面的废话

接口大大增强了类设计的灵活性,类似于c++中的多重继承。不管你是否真正了解接口 (Interface),但它已经默默的在为你的程序服务了。你可以去看一下 TComponent 的定义部分,你会发现它内部已经封装了2个接口:IInterface, IInterfaceComponentReference不难发现,Delphi 中除了原子类 TObject 之外,任何类有且只有一个父类,但同时它可以拥有0..n个接口。接口是一组抽象的函数集,不能被实例化,函数实现部分必须由它的实现类或间接实现 (外包) 类完成。

 
如何查询接口
先请看下面的代码:

type
  IHello = 
interface(IUnknown)
    [
'{1EE7A0AA-F525-4DD5-AB1B-900348BF8322}']
    
procedure Hello;
  
end;
  
  THello = 
class(TObject, IHello)
    
// Implements IHello
    
procedure Hello;
  
end;

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;
  
except end
  
// Case 6
  
if Supports(Obj, IHello, pIntfHello) then
    pIntfHello.Hello;
end;

 
前面3种情况都是不安全的接口函数调用,这里就不仔细说了。
情况4:这种方法是最直接的。GetInterface 函数会去 VMT 中寻找是否定义过 IHello 这个接口,如果找到的话,并且把实现者的实例返回到 pIntfHello 中。这样就可以安全的使用了。
情况5:这里使用了保留字 as 进行强制类型转换。如果转换失败运行期会丢出异常,我们可以通过 try…except 处理掉异常。其实 as 内部机制就是调用了 _IntfCast,只是比情况4 多了一个抛出异常而已。
情况6:可以通过Supports 函数查询接口。于情况4不同的是 Supports 会自动检查 Obj 是否是个有效的实例,帮你省了一行代码。(但是这个函数会让你在接口转换时付出其它额外的代价)
 
使用 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;

 
当执行 Result := Supports(Instance, IID, Temp) 时,Temp 这个接口指针指向 Instance (Obj),同时因为接口引用计数的关系Obj.FRefCount 加一。当离开Supports 函数时本地变量指针Temp 会被清除于是Obj.FRefCount 减一。这个加一减一表面上没有差别,但是你完全无法预计 Instance 内部会对 FRefCount 的变化做什么样的处理。而恰恰 Obj 是从臭名昭著的 TInterfacedObject 继承来的。这个类会当 FRefCount=0 时释放掉实例本身。这个就是该程序出错的真正原因。
 
好了,下面再介绍一个隐藏的比较深的问题。这个和接口的委托机制有关。请看下面的代码

type
  TVirtualImplementor = 
class(TInterfacedObject{TObject does not have problem}, IHello)
  
public
    FImplementorOfIHello: THelloImplementor;
    
property ImplementorOfIHello: THelloImplementor read FImplementorOfIHello implements IHello; //<-- Be careful!
  
end;

procedure TestMe;
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 <> 
niland
            ((Instance.GetInterface(IUnknown, LUnknown) 
and Supports(LUnknown, IID, Intf)) or //<-- RefCount changed!
             Instance.GetInterface(IID, Intf)); 
end;

 

倒数第三行的地方,Supports 并不是直接通过 Instance 去查询是否支持IHello 接口的而是先去查询 Instance 是否支持 IUnknown,然后通过 IUnknown 去查询 IHello接口。我不清楚 Delphi 为什么这样处理。

现在我们手动调试程序:

当执行Instance.GetInterface(IUnknown, LUnknown) 之后,LUnknown 指针指向了 Instance ( VI),同时因为接口引用计数的关系 VI.FRefCount 加一。

然后程序继续执行 Supports(LUnknown, IID, Intf)。这时 Intf 指针指向了IHello 的真正实现者 VI.FImplementorOfHello,同样的的,VI.FImplementorOfHello.FRefCount 加一。

当查询成功离开 Supports 函数时,本地变量指针 LUnknown 会被清除,于是 VI.FRefCount 减一。不巧的是 VI 也是由 TInterfacedObject 继承来的……。种种不幸最终酿成又一场惨祸。

 
最后总结
这篇文章除了要介绍一下接口的查询方法外,主要是要想交代一下我在具体使用接口中发现的一些问题。上述代码中包涵了3个问题:

(1) TInterfacedObject 由于会在 FRefCount=0 时释放掉对象实例,所以在使用上要格外小心。建议重新封装一个TInterfacedObjectEx,或者改用 TComponent

(2) Supports 内部这行代码虽不知其用意,但显然是不安全的!尤其是在使用委托机制实现接口封装的时候。说明:我暂时无法证明去掉有问题的这行是否能保证不引入其它问题。

(3) 上述代码设计上的问题是:既然 TVirtualImplementor 把接口的实现工作委托 (外包) 给了 TRealImplementor,那么TVirtualImplementor 就应该定义成 TObject。尽管程序可以运行,但是逻辑上还是有些不通。


此外,委托 (implements) 这个概念挺有意思,它提高了接口的复用度。有时间的话,我会详细再写一篇介绍。
 

Go语言_接口查询

下面贴出一段接口查询代码的代码供大家学习 package main import "fmt" type IFile interface { Read() Write() } typ...
  • u010003835
  • u010003835
  • 2016年06月24日 13:47
  • 1077

模糊查询 根据接口获取联想数据 查询出来数据还可以继续输入

  • cy123cy456cy
  • cy123cy456cy
  • 2016年12月17日 11:33
  • 476

java分页查询接口的实现

java分页查询的实现分页要传入当前所在页数和每页显示记录数,再分页查询数据库,部分代码如下所示。传入参数实体类:public class MessageReq { private String...
  • u012780336
  • u012780336
  • 2017年02月07日 18:56
  • 2845

调用webservice接口实现几个查询

写着玩玩..... 1. 查询手机号码归属地 查询手机号码归属地 请输入正确的手机号进行查询... ...
  • u012675743
  • u012675743
  • 2015年09月10日 10:16
  • 1449

自然语言查询接口IDCQ(二)算法描述

自然语言查询接口实现算法,是可独立于特定数据库的,这里采用企业进销存管理系统做示例展示。 算法分为3大部分:分词 词性匹配 SQL语句构造...
  • xxx_qz
  • xxx_qz
  • 2017年02月22日 18:41
  • 537

苹果序列号/IMEI号查询--Api接口

接口列表 1. 苹果序列号/IMEI号查询 接口信息 URL:http://apis.juhe.cn/appleinfo/index 支持格式:json/xml http请求方式:get DEMO...
  • qxs965266509
  • qxs965266509
  • 2013年09月27日 23:00
  • 7388

IP地址查询接口及调用方法

IP地址查询接口及调用方法 设计蜂巢 | 2011-12-29 | javascript 设计蜂巢IP地址查询接口:http://www.hujuntao.com/api/ip/i...
  • kong1940742529
  • kong1940742529
  • 2017年02月09日 12:20
  • 3467

航班实时起降查询调用代码实现

航班实时起降查询代码实现功能:当日航班,含国际 orgCity dstCity 通过始发城市查询航班 | flightNo 通过航班号查询航班。通过此接口可实现获取航班实时起降查询数据,应用与APP、...
  • juheAPI
  • juheAPI
  • 2015年08月11日 15:16
  • 3271

12306实时余票查询接口

12306实时余票查询接口代码文档及返回示例,可查询实时火车票余票,包括车次、车次始发站、车次终点站、出发时间、到达时间、车次类型、总历时时间等等。 接口名称:12306实时余票查询接口 接口平台...
  • nysi_i
  • nysi_i
  • 2016年02月18日 11:17
  • 2970

golang中接口查询

什么是接口查询接口查询,就是在一个接口变量中,去判断,那个把值赋给接口变量的对象,究竟有没有实现另一个接口,这个所谓的另一个接口,就是我要查询的那个接口。在这个已知的接口变量中,查询另一个接口的过程,...
  • hzwy23
  • hzwy23
  • 2017年02月25日 13:15
  • 5995
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:查询接口小议
举报原因:
原因补充:

(最多只允许输入30个字)