zoukankan      html  css  js  c++  java
  • Delphi 的接口机制——接口操作的编译器实现过程(1)

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

           (1)有些类型(结构体)的成员类型与C++中的不是等效对应关系,如SHFileOperation函数的参数类型是SHFILEOPSTRUCT结构体,delphi中它的两个路径成员被定义成PWideChar型,与C++的LPCTSTR不一致,PWideChar是以空字符()结尾的,致使这两个成员不能包含多个文件路径。【注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生成维护接口引用计数的代码时可以看到这一点。 

    var
    MyObject: TMyObject;
    MyIntf, MyIntf2: IInterface;
    begin
    MyObject := TMyObject.Create; // 创建 TMyObject 对象
    MyIntf := MyObject; // 将接口指向 MyObject 对象
    MyIntf2 := MyIntf; // 接口指针的赋值
    end;


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

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

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

    procedure _IntfCopy(var Dest: IInterface; const Source: IInterface);
    var
    P: Pointer;
    begin
    P := Pointer(Dest);
    if Source <> nil then
    Source._AddRef;
    Pointer(Dest) := Pointer(Source);
    if P <> nil then
    IInterface(P)._Release;
    end;
            函数_IntfCopy 的代码比较简单,就是增加 Source 接口对象的引用计数,减少被赋值的接口对象的引用计数,最后把源接口赋值至目标接口。

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

    接口指针变量的清除工作
          

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

    function _IntfClear(var Dest: IInterface): Pointer;
    var
    P:Pointer;
    begin
    Result := @Dest;
    if Dest <> nil then
    begin
    P := Pointer(Dest);
    Pointer(Dest) := nil;
    IInterface(P)._Release;
    end;
    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 函数减少引用计数。

    { System.pas }
    procedure _IntfAddRef(const Dest: IInterface);
    begin
    if Dest <> nil then Dest._AddRef;
    end;
           为什么以传值方式要特别处理引用计数呢?因为复制了接口指针。

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

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

    2   有关结构体SHFILEOPSTRUCT及其两个路径成员的详细介绍请参见http://blog.csdn.net/tht2009/article/details/6753706和http://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,由于大富翁论坛好像已关闭了,所以真正出处已无从考证。
    ---------------------
    作者:tht2009
    来源:CSDN
    原文:https://blog.csdn.net/tht2009/article/details/6767435
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    HDU 1874 畅通工程续(dijkstra)
    HDU 2112 HDU Today (map函数,dijkstra最短路径)
    HDU 2680 Choose the best route(dijkstra)
    HDU 2066 一个人的旅行(最短路径,dijkstra)
    关于测评机,编译器,我有些话想说
    测评机的优化问题 时间控制
    CF Round410 D. Mike and distribution
    数字三角形2 (取模)
    CF Round410 C. Mike and gcd problem
    CF Round 423 D. High Load 星图(最优最简构建)
  • 原文地址:https://www.cnblogs.com/findumars/p/11128315.html
Copyright © 2011-2022 走看看