• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Delphi的接口机制——接口操作的编译器实现过程(1)

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

      学习COM编程技术也快有半个月了,这期间看了很多资料和别人的程序源码,也尝试了用delphi、C++、C#编写COM程序,个人感觉Delphi是最好上手的。C++的模版生成的代码太过复杂繁琐,大量使用编译宏替代函数代码,让初学者知其然而不知其所以然;C#封装过度,COM编程注定是要与操作系统频繁打交道的,需要调用大量API函数和使用大量系统预定义的常量与类型(结构体),这些在C#中都需手工声明,不够简便;Delphi就简单多了,通过模版创建的工程代码关系结构非常清晰,而且其能非常容易使用API函数和系统预定义的常量和类型(只需引用先关单元即可),但在使用过程中也发现了一些缺点。【注1】

       (1)有些类型(结构体)的成员类型与C++中的不是等效对应关系,如SHFileOperation函数的参数类型是SHFILEOPSTRUCT结构体,delphi中它的两个路径成员被定义成PWideChar型,与C++的LPCTSTR不一致,PWideChar是以空字符(\0)结尾的,致使这两个成员不能包含多个文件路径。【注2】

       (2)有些接口的函数参数定义不一致,如IContextMenu.InvokeCommand函数参数在Delphi中是CMINVOKECOMMANDINFO类型,在c++中是LPCMINVOKECOMMANDINFO型 ,致使该接口函数不能使用扩展的CMINVOKECOMMANDINFOEX型参数。【注3】

 

       Delphi操作COM的另一便处在于他的接口的引用计数管理,这为我们写程序解决了一大麻烦:不用管接口的AddRef和Release了,直接把接口当“接口指针变量”(【注4】)使用,编译器会执行一些特殊的代码自动维护接口的引用计数。当然,这也会带来另一个问题,接口相当于“变量”一样使用,这就涉及到“变量”的生命周期问题,当把这样一个局部“变量”通过强制类型转换(【注5】)给一个全局变量时,待之后转换回来时将引发错误。因为局部“变量”生命已结束,要被清理,其所代表的接口被减少引用计数释放了,如果人为让“变量”AddRef一次,就能消除这个错误。

       关于Delphi的接口引用计数管理,在网上看到的一篇介绍的文章,查很久了它的出处,目前已知最早是SaveTime于2004年2月3日发表于大富翁论坛。【注6】

       下面将它整理了一下,以便加深对delphi对接口引用计数的理解。

 

接口指针变量赋值

       接口是生存期自管理对象,即使是局部接口指针变量,也总是被初始化为 nil。接口被初始化为nil是很重要的,从下文中Delphi生成维护接口引用计数的代码时可以看到这一点。 

[delphi] view plaincopyprint?
 
  1. var  
  2.     MyObject: TMyObject;  
  3.     MyIntf, MyIntf2: IInterface;  
  4.   begin  
  5.     MyObject := TMyObject.Create;  // 创建 TMyObject 对象  
  6.     MyIntf  := MyObject;           // 将接口指向 MyObject 对象  
  7.     MyIntf2 := MyIntf;             // 接口指针的赋值  
  8.   end;  

 

        当接口与一个对象连接时,编译器会执行一些特殊的代码维护接口对象的引用计数。例如以上代码,当执行到MyIntf :=MyObject 语句时,编译器的实现是:     

       1. 如果 MyObject <> nil,则设置一临时接口指针 P 指向 MyObject 对象内存空间中的“接口跳转表”指针(后面会分析“接口跳转表”),否则 P := nil;     

       2. 执行 System.pas 中的 _IntfCopy(MyIntf, P) 操作,进行引用计数管理。

[delphi] view plaincopyprint?
 
  1. procedure _IntfCopy(var Dest: IInterface; const Source: IInterface);  
  2. var  
  3.   P: Pointer;  
  4. begin  
  5.   P := Pointer(Dest);  
  6.   if Source <> nil then  
  7.     Source._AddRef;  
  8.   Pointer(Dest) := Pointer(Source);  
  9.   if P <> nil then  
  10.     IInterface(P)._Release;  
  11. end;  

        函数_IntfCopy 的代码比较简单,就是增加 Source 接口对象的引用计数,减少被赋值的接口对象的引用计数,最后把源接口赋值至目标接口。

       对于两个接口的赋值的情况,如MyIntf2 := MyIntf,这时比 MyIntf := MyObject 的情况要简单一些,编译器不需要进行对象到接口的转换工作,这时真正执行的代码是:_IntfCopy(MyIntf2, MyIntf)。

 

接口指针变量的清除工作

      

       在一个过程(procedure/function)执行结束时,编译器会生成代码减少接口指针变量的引用计数。编译器使用接口指针为参数调用 _IntfClear 函数,_IntfClear 函数的作用是减少接口对象的引用计数并设置接口为 nil :

[delphi] view plaincopyprint?
 
  1. function _IntfClear(var Dest: IInterface): Pointer;    
  2. var   
  3. P:Pointer;    
  4. begin     
  5.  Result := @Dest;     
  6.  if Dest <> nil then      
  7.     begin        
  8.        P := Pointer(Dest);        
  9.        Pointer(Dest) := nil;        
  10.        IInterface(P)._Release;      
  11.     end;    
  12. end;  

       通过对以上代码及分析,我们可以总结过程(procedure/function)中的接口引用计数使用规则:
       1. 一般不需要使用 _AddRef/_Release 函数设置接口引用计数;
       2. 可以将接口赋值为接口或对象,Delphi 自动处理源/目标接口对象的引用计数;
       3. 如果要提前释放接口对象,可以设置接口指针为 nil,但不要调用 _Release。因为 _Release 不会把接口指针变量设置为 nil,最后 Delphi 自动调用 _IntfClear时会出错。

      对于全局接口指针变量,在接口指针变量被赋值时增加对象的引用计数,在程序退出之前编译器自动调用 _IntfClear 函数减少引用计数以清除对象。


接口指针作为参数


       1. 以var 或const 方式传递接口指针时,像普通的参数传递一样。
       2. 以out 方式传递接口指针时,编译器会先调用_IntfClear 函数减少引用计数,清除接口指针为 nil 。(out 也是以引用方式传送参数)。
       3. 以传值方式传递接口指针时,编译器会在参数被使用之前调用_IntfAddRef 函数增加引用计数,在过程结束之前调用_IntfClear 函数减少引用计数。

[delphi] view plaincopyprint?
 
  1. { System.pas }  
  2.   procedure _IntfAddRef(const Dest: IInterface);  
  3.   begin  
  4.     if Dest <> nil then Dest._AddRef;  
  5.   end;  

       为什么以传值方式要特别处理引用计数呢?因为复制了接口指针。

 

      下一节介绍接口对象的内存空间

 

 

 

 

 


1   我用的是Delphi2010,更新的XE、XE2版本可能已更正了这些问题,在此举例说明而已。

2   有关结构体SHFILEOPSTRUCT及其两个路径成员的详细介绍请参见http://blog.csdn.net/tht2009/article/details/6753706http://msdn.microsoft.com/en-us/library/bb759795(VS.85).aspx

3   有关接口函数InvokeCommand的详细介绍请参见http://msdn.microsoft.com/en-us/library/bb776096(VS.85).aspx

4   我也不知严格上能否这样称呼,姑且这样类比吧!

5   如通过Pointer(IShellFolder)将一个局部声明的IShellFolder接口保存到一个Pointer型的变量Data中,通过Data:=Pointer(IShellFolder)不会增加IShellFolder接口对象的引用。实际中很少遇到这种情况,我也是在无意中发现这个问题的。

6   请见http://blog.csdn.net/huangsn10/article/details/6112546,由于大富翁论坛好像已关闭了,所以真正出处已无从考证。

http://blog.csdn.net/tht2009/article/details/6767435


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
拓端数据tecdat|matlab代写预测ARMA-GARCH条件均值和方差模型发布时间:2022-07-18
下一篇:
matlab关键字发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap