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

这几个类都和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);        
    return AtlAdvise(pUnk, (IUnknown*)this, *piid, &m_dwEventCookie);
}
<style type="text/css"> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }</style>

显然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/atfield EMail: [email protected] 转载请注明出处

你可能感兴趣的:(css,asp.net,asp,vb,VB.NET)