Print This Page

深入分析D语言接口与COM接口的关系

News

Google
2008-03-03
Category: 更新(update)
Posted by: 刘策(yayv)
2007-04-24
Category: 更新(update)
Posted by: 刘策(yayv)
新设栏目 D语言高级教程 , 并转贴一篇 深入分析D语言接口与COM接口的关系 精华文章
2007-04-24
Category: 更新(update)
Posted by: 刘策(yayv)
新增2篇文章,均是对D语言数组进行探索的。一篇主要探索D语言数组长度修改时产生的影响, 另一篇主要探索D语言数组在多维的情况下如何初始化。

2007-04-22

深入分析D语言接口与COM接口的关系   作者:   h_rain

关键字:
  D语言,COM,Interface    


前两天为了解决dxpcom项目中遇到的xpcom接口兼容性问题,看了一下DMD编译器的源码,对D的接口有了一些了解,现在总结出来,备忘。

D中有了专门用于标识接口的关键字interface,而不用象C++中使用抽象类来代替。
D代码:

代码
  1. interface ITest  
  2. {  
  3. int test();  
  4. }  

C++代码:
代码
  1. class ITest  
  2. {  
  3. int test()=0;  
  4. }  

而D中的接口与C++中的接口不同之处是,D中的接口仍然含有ClassInfo,存放在虚表的0项上。

从DMD的源码中可以得知,D中的类,接口都在虚表的0项上保存了ClassInfo指针。
这样,D中的接口是无法与C++接口兼容的,则D就无法调用Windows的COM对象,至少是无法“优雅”的调用(仍然可以使用struct进行二进制兼容代替)。

为了解决这个问题,DMD就需要能够表示出与C++兼容的COM接口,即需要一个虚表是"干净"的接口。又由于,从一个COM接口继承的接口仍然是一个 COM接口,而COM模型的实现上又恰好定义了一个“IUnknown”根接口(COM体系中的所有的接口都是继承了IUnknown)。

所以,出于简单实现的原则,DMD区分一个接口是D接口还是COM接口,关键就是判断这个接口是不是叫做IUnknown,以及这个接口是否继承自 IUnknown,虽然接口都是通过Interface关键字声明。更有趣的是,DMD仅仅判断接口的名字是否为"IUnknown"而根本不管接口中的 方法如何定义。

以上所述内容在进行Windows COM编程时,几乎不会被察觉,因为Windows的所有接口都是继承自IUnknown,只要正常使用就可以了。

而在进行Mozilla xpcom编程的时候,xpcom的根接口叫做ISupports,DMD根本就不会认为这是需要编译为C++兼容的COM接口,而仍然会将虚表的0项进行保留,结果给使用者造成了虚表指针偏移了的印象。

基于D的这个识别COM接口的方式,在dxpcom项目中,qiezi使用了别名的方式进行了变换,既将dxpcom项目中的所有的接口名称进行了优雅的统一,又能够使DMD生成正确的COM接口:

代码
  1. extern(Windows)  
  2. interface IUnknown {  
  3.   static const char[] IID_STR = NS_ISUPPORTS_IID_STR;  
  4.   static const nsIID IID = NS_ISUPPORTS_IID;  
  5.   
  6.   /* void QueryInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */  
  7.   nsresult QueryInterface(nsIID * uuid, void * *result);  
  8.   
  9.   /* [noscript, notxpcom] nsrefcnt AddRef (); */  
  10.   nsrefcnt AddRef();  
  11.   
  12.   /* [noscript, notxpcom] nsrefcnt Release (); */  
  13.   nsrefcnt Release();  
  14.   
  15. }  
  16.   
  17. alias IUnknown nsISupports;  

这个现象同时也很好的说明了,D中的别名(alias)在符号的处理方面仅仅是一个符号的替换,同C/C++中的#define的作用相同。

下面的两段代码就能很好的诠释本文的内容(感谢qiezi提供)

代码一,无法通过运行期断言,因为接口IInterface仍然为标准D接口,虚表的0项为ClassInfo指针无法被显示的调用,在执行的结果中就表现为虚表进行了偏移。

代码
  1. extern(Windows):     
  2. int test1(IInterface p)     
  3. {     
  4.     return 1;     
  5. }     
  6.     
  7. int test2(IInterface p)     
  8. {     
  9.     return 2;     
  10. }     
  11.     
  12. int test3(IInterface p)     
  13. {     
  14.     return 3;     
  15. }     
  16.     
  17. struct InterfaceVtbl     
  18. {     
  19. extern(Windows):     
  20.     int function(IInterface) test1;     
  21.     int function(IInterface) test2;     
  22.     int function(IInterface) test3;     
  23. }     
  24.     
  25. struct Interface     
  26. {     
  27.     InterfaceVtbl* vtbl;     
  28.     
  29.     InterfaceVtbl vtbl_;     
  30.     
  31.     static Interface opCall()     
  32.     {     
  33.         Interface res;     
  34.         res.vtbl_.test1 = &test1;     
  35.         res.vtbl_.test2 = &test2;     
  36.         res.vtbl_.test3 = &test3;     
  37.         res.vtbl = &res.vtbl_;     
  38.         return res;     
  39.     }     
  40. }     
  41.     
  42. interface IInterface     
  43. {     
  44.     int test1();     
  45.     int test2();     
  46.     int test3();     
  47. }     
  48.     
  49. extern (D):     
  50.     
  51. void main()     
  52. {     
  53.     Interface i = Interface();     
  54.     assert(i.vtbl.test1(cast(IInterface)&i) == 1);     
  55.     assert(i.vtbl.test2(cast(IInterface)&i) == 2);     
  56.     assert(i.vtbl.test3(cast(IInterface)&i) == 3);     
  57.     
  58.     IInterface ii = cast(IInterface)&i;     
  59.     assert(ii.test1() == 1);     
  60.     assert(ii.test2() == 2);     
  61.     assert(ii.test3() == 3);     
  62. }  

代码二,与代码一的结构完全一致,却能够通过运行时断言的检查。Ψ一的不同仅仅是IInterface的名字换成了IUnknown!!

代码
  1. extern(Windows):     
  2. int test1(IUnknown p)     
  3. {     
  4.     return 1;     
  5. }     
  6.     
  7. int test2(IUnknown p)     
  8. {     
  9.     return 2;     
  10. }     
  11.     
  12. int test3(IUnknown p)     
  13. {     
  14.     return 3;     
  15. }     
  16.     
  17. struct InterfaceVtbl     
  18. {     
  19. extern(Windows):     
  20.     int function(IUnknown) test1;     
  21.     int function(IUnknown) test2;     
  22.     int function(IUnknown) test3;     
  23. }     
  24.     
  25. struct Interface     
  26. {     
  27.     InterfaceVtbl* vtbl;     
  28.     
  29.     InterfaceVtbl vtbl_;     
  30.     
  31.     static Interface opCall()     
  32.     {     
  33.         Interface res;     
  34.         res.vtbl_.test1 = &test1;     
  35.         res.vtbl_.test2 = &test2;     
  36.         res.vtbl_.test3 = &test3;     
  37.         res.vtbl = &res.vtbl_;     
  38.         return res;     
  39.     }     
  40. }     
  41.     
  42. interface IUnknown     
  43. {     
  44.     int test1();     
  45.     int test2();     
  46.     int test3();     
  47. }     
  48.     
  49. extern (D):     
  50.     
  51. void main()     
  52. {     
  53.     Interface i = Interface();     
  54.     assert(i.vtbl.test1(cast(IUnknown)&i) == 1);     
  55.     assert(i.vtbl.test2(cast(IUnknown)&i) == 2);     
  56.     assert(i.vtbl.test3(cast(IUnknown)&i) == 3);     
  57.     
  58.     IUnknown ii = cast(IUnknown)&i;     
  59.     assert(ii.test1() == 1);     
  60.     assert(ii.test2() == 2);     
  61.     assert(ii.test3() == 3);     
  62. }  

另外需要说明的是extern(D),extern(Windows),extern(Pascal)等特征,只是用来描述函数的调用约定,与接口的类型无关。
一句话:D中的类与标准D接口都有ClassInfo在虚表的0项上,而COM接口的虚表是干净的;而将一个接口声明为COM接口的方式为:将这个接口命名为IUnknown或继承自IUnknown。


Previous page: D语言高级教程
Next page: D语言例程