(转)ATL:IDispatchImpl, IDispEventImpl, IDispEventSimpleImpl的区别和联系

来源:互联网 发布:淘宝主图文字规定 编辑:程序博客网 时间:2024/06/08 00:31

这几个类都和IDispatch的实现有关系,但是他们提供的IDispatch的实现是不同的。

IDispatchImpl只能用于双接口(Dual Interface)的实现。IDispatchImpl本身的IDispatch接口实现是使用ITypeInfo::Invoke的。ITypeInfo简单来说是一个代表TypeLibrary中一个类型的COM对象,比如某个interface IA。而ITypeInfo::Invoke是把DispID使用TypeLibrary中的接口定义,换算成虚函数的Index,然后通过虚函数表(Vtable)来直接调用接口的函数。举例来说:

[dual] Interface IA : IDispatch { [id(1)] HRESULT FuncA(); [id(2)] HRESULT FuncB(); }

IA所对应的ITypeInfo对象(严格来说是实现ITypeInfo接口的COM对象)调用Invoke方法,传入id=2。ITypeInfo::Invoke便可以通过TypeLibrary中的类型信息换算出2对应着IA中虚函数表的索引=(IUnknown) +4 (IDispatch)+2 (FuncB) -1 (从0开始算起) = 8 的函数指针。如果IA是dispinterface(纯IDispatch的接口),ITypeInfo::Invoke则会直接失败。

顺便说一句,ATL本身对多个IDispatch实现支持不太好,无法很容易的将多个IDispatch实现合并成一个。比如考虑一下:我们可能继承自多个IDispatchImpl,他们分别具有自己的IDispatch实现,支持不同的接口。我们可以通过COM_INTERFACE_ENTRY2(IDispatch, XXX) 选择其中一个,但是无法很容易的合并。

 

IDispEventImpl和ISimpleEventImpl这两个类顾名思义主要是用于事件处理的。在COM中,为了让C/C++这种静态语言和VB/Jscript等动态语言都可以调用事件,而且事件本身速度一般不是特别重要,通常COM中的事件都是只支持IDispatch接口,而不支持普通的虚函数接口,也就是定义为dispinterface,如下:

dispinterface IA { methods: [id(1)] int FuncA(); [id(2)]int FuncB(); }

在这种情况下,为了让ATL知道DISPID和函数实现的对应关系,用户必须手动提供一张表格,来定义这种对应关系,也就是通过一些BEGIN_SINK_MAP/SINK_ENTRY等等宏来定义,这些细节MSDN有详细描述,也比较好理解,这里不再赘述。最重要的是,这两个类不可以直接用来提供dispinterface的实现,而是必须用在事件处理中。原因很简单:IDispEventImpl和IDispEventSimplImpl所定义的IDispatch实现位于单独的Vtable中。大家都知道在ATL中,为了支持QI,所有支持的QI的接口都会使用BEGIN_COM_MAP,COM_INTERFACE_ENTRY等宏来定义,形成一张表,以供ATL所提供的QueryInterface实现来查找。这个实现,本质上是基于C++的Casting机制,这也是为什么ATL需要引入COM_INTERFACE_ENTRY2这样的宏来告诉ATL从哪个继承分支来获得某个接口(比如有多个接口IA、IB都实现IC,那么你需要告诉ATL是从IA Cast到IC还是从IB Cast到IC)。由于IDispEventImpl和IDispEventSimpleImpl这两个类并没有直接继承IDispatch,这也意味着COM_INTERFACE_ENTRY这样的宏完全失去了作用,也就是说无法通过QueryInterface来直接获得IDispEventImpl和IdispEventSimpleImpl这两个类所实现事件回调接口。ATL的处理办法是,强制大家都必须要用IdispEventSimplImpl::DispEventAdvise/ IdispEventSimplImpl::DispEventUnadvise来注册事件,而普通的AtlAdvise和AtlUnadvise是无法工作的。原因也正是QueryInterface无法QI到对应的事件接口,而DispEventAdvise可以直接强制cast,如下:

HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid) { ATLENSURE(m_dwEventCookie == 0xFEFEFEFE); returnAtlAdvise(pUnk, (IUnknown*)this, *piid, &m_dwEventCookie); }

显然IDispEventSimpleImpl是不继承自Iunknown的,因此这个Cast是强制的Cast,等价于reinterpret_cast。如果大家看看IdispEventSimpleImpl的实现,这个类不继承自Iunknown,但是却定义了Iunknown和IDispatch的所有方法,这样,强制Cast到Iunknown是可以工作的,因为Vtable的layout是一致的。而IDispEventSimpleImpl的QueryInterface的实现则是支持QI到事件回调接口的,这也就解决了问题。

按我来看这个设计实在是有些奇怪,为什么不可以直接利用COM_INTERFACE_ENTRY来支持QI呢?当然了,ATL目前的这个设计可以解决问题,只是比较容易用错,我自己也是一段时间不用就忘记了,这里把我的发现写下来,可以做今后的参考,同时也希望能够对用ATL的朋友有些帮助。

作者:张羿 (ATField)Blog: http://blog.csdn.net/atfieldEMail: atfield_zhang@hotmail.com

转自:http://blog.csdn.net/ATField/article/details/3018647

原创粉丝点击