服务热线:
您当前的位置:首页 > 世纪星月刊 > 第10期 (2011年10月)

【技术前沿】为COM组建增加多个双接口

2011/11/1 13:59:25

 

研发部 杨盛海

 

  不使用任何工具开发COM组件,实在是一种非常繁琐复杂的工作,需要考虑到方方面面的事情,组件功能、接口暴露、组件工厂、引用计数、类别属性、组件注册等等,很少有人能够完整将一个功能非常简单的组件从头到尾不看资料写下来。所以微软就开发了ATL,用来简化COM组件的开发。

  ATL不是完全按照标准的COM实现过程来封装,而是采用许多可以节省空间,节约运算时间的方式来巧妙地实现,要看懂ATL的整个架构不是一件容易的事,因为,其一,它本身实现的COM架构就和标准的架构不一样。其二,封装了许多模板类,要看清它是怎么工作的,需要理解这些模板类的实现。其三,ATL把许多代码封装成了带参数的宏,要详细了解它的组件实现过程,还得要逐个理解这些个无处不在的宏。

  尽管如此,你仍然可以很轻松地使用ATL,而不去关心它是怎么运行的。同MFC一样,ATL提供了一个很方便的实现最基本COM框架的向导,你自己只要把想实现的功能加上去就行。我们可以利用向导很方便地实现一些简单的组件。但,稍微复杂一点的组件,就得费点脑筋了。比如,为组件增加多个双接口。

  双接口即为标准IUnknown接口和支持自动化应用的IDispatch接口。前者是为了通常的应用,比如定义一个接口,可以从IUnknown接口来查询。后者是为了更自由地使用该接口,而不必在客户应用程序启动时加载。如果为一个接口同时支持两种使用方式,那该接口就具备了这两种素质,一种是性能,一种是自由。客户可以根据这两种素质来自由选择。

  自微软开发ATL以来,使用ATL为COM增加多个双接口就是一个老大难的问题,尽管网上有不少个人声称有解决方案,但也都存在一些问题,微软自己也在技术支持网站上说不建议为组件增加多个双接口。

  我想大多数想增加双接口的人肯定是既想用虚表的高效性,又想用自动化接口的灵活性。想达到这个目的,还是有一些途径的,我在翻阅相关资料时,偶然查到这么一种用法,在idl文件中加自定义接口,想加多少加多少,最后再加一个集成的继承于自动化接口的接口,举个例子,加两个自定义接口,IA和IB,均继承自IUnknown,IA有方法fa,IB有方法fb,然后再加一个自动化接口IC,然后把IA和IB的方法原封不动地添加到IC下面,只不过在前面加上ID号。然后在COM组件的父类中添加IA、IB和IDispatchImpl<IC,......>,在组件的接口入口点处添加IA、IB和IDispatch,最后在组件中实现fa和fb方法。详细的伪代码如下:
idl文件:
[uuid(...), object, oleautomation, pointer_default(unique)]
interface IA : IUnknown
{
 HRESULT fa();
}

[uuid(...), object, oleautomation, pointer_default(unique)]
interface IB : IUnknown
{
 HRESULT fb();
}

[uuid(...), dual, helpstring(...), hidden]
interface IC : IDispatch
{
 [id(1), helpstring(...)] HRESULT fa();
 [id(2), helpstring(...)] HRESULT fb();
}

.h文件:
class ATL_NO_VTABLE CObj :
 public CComObjectRootEx<...>,
 public CComCoClass<...>,
 public IA,
 public IB,
 public IDispatchImpl<IC, ...>
{
 public:
 ...
BEGIN_COM_MAP(CObj)
 COM_INTERFACE_ENTRY(IA)
 COM_INTERFACE_ENTRY(IB)
 COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

public:
 STDMETHOD(fa)();
 STDMETHOD(fb)();
};

  这样做对于一些简单的目的来说是可行的。比如我想通过晚绑定来访问fa和fb,完全可以。比如说我想在C++中通过IA和IB接口访问fa和fb,完全可以,不过这需要为程序包含组件的头文件和接口定义源码,算是一种缺陷吧。
其实这种方法缺陷还是不小的,但这和ATL中微软对COM的实现方法有关,这种组件不能被客户端用加载类型库的方式使用,我试过,明明我的组件中有两个接口,加载类型库后,却只能认出一个接口,而且只认准了其中一个接口,不管我怎么修改接口名称、接口说明等内容,它死活认不出另一个接口,然后我通过OLEView来查接口,也只认其中一个,不知道类型库是怎样被生成和加载的。
 
  这就为组件的应用堵死了一条路,有些惘然若失。后来想了想,客户端要想以自定义接口的方式使用组件,需要包含头文件和接口定义文件,也算是对组件的封装和一种保护吧,具有一定的商业意义。
在网上还有人研究出了另外一种支持多个双接口的方式,这种方式更加的专业,因为它完全就是按照ATL的思路来实现的,也就是相当于ATL的扩充,对于这种方式,我想微软是最欢迎的,也是最知道它的优劣的。虽说微软不建议为组件增加双接口,但有人做出来了,他肯定也不反对。

  这种方法定义了一下支持多个双接口的模板类,一如ATL所做的那样:CMultiDiapatch<T>,使最终的组件类继承于这个模板类。为每个接口定义IDispatchImpl类:
typedef IDispatchImpl<IA, &IID_IA,  ...> dispBase1;
typedef IDispatchImpl<IB, &IID_IB,  ...> dispBase2;

  随后又自己定义了一些和ATL很相似的宏:BEGIN_MULTI_DISPATCH_MAP(...)和MULIT_DISPATCH_ENTRY(...),功能上和BEGIN_COM_MAP宏和COM_INTERFACE_ENTRY宏是类似的,具体内容都是围绕着如何将IDispatch接口函数的调用地址返回这个事来做的。伪代码如下:
BEGIN_MULTI_DISPATCH_MAP(...)
        MULTI_DISPATCH_ENTRY(dispBase1)
        MULTI_DISPATCH_ENTRY(dispBase2)
END_MULTI_DISPATCH_MAP()

  具体的宏代码我就不贴在这儿了,只消明白它是干什么的就行。

  这种方式同样可以很好地应用到一些简单的组件之中,但也有它的一些缺陷。比如,作者自己测试的,把组件用在IE浏览器中就有问题,后面找了很长时间的原因,借助他人的帮助才知道问题出在哪儿,在作者文章的后面,很多人从自己的角度测试了这种方式,发现仍有许多条件下,这种方式不可用。这也正应了微软的说法,不建议搞ATL扩展。也许微软自己也想弄一个多个双接口,没弄出来。
  
  我们看到这里,不应当嘲笑那个程序员的不自量力,事实上,很大程度上,他的那个作法也已经可行了。我想他比很多自以为精通COM技术的所谓高手强多了,先不说他的这种探索精神,也许能想到这些方法的人不少,但能够系统地把自己的想法和做法放到网上共享,并直言不讳地说出其中的不足,与广大网友讨论,共同切磋技术的人,相对来说,太少了,这种做法很值得我们的某些高手学习。

  说到底,大部分我们在应用的技术不过是遵循一些行业领导者所制订的规则罢了,技术强者领悟得多,技术逊者领悟得差点,谁也比谁聪明不了哪儿去,藏着掖着的,往往最后藏不住,受人尊敬的往往是厚道的具有分享精神的人。

 


企业邮箱  |  法律公告  |  隐私保护  |  联系我们  |