Programming 版 (精华区)

发信人: lofe ()感激生活(), 信区: Programming
标  题: ATL接口映射宏详解--(2)
发信站: 哈工大紫丁香 (Sun Sep  3 08:15:36 2000), 转信

二、COM_INTERFACE_ENTRY2(x, x2)        参ATL例程:COMMAP

    ATL中是以多重继承的方式来实现组件的,但在继承树中如果有多个分支实现了同一
个接口,当查询这个接口时就需要知道把哪个分支返回给它。这个宏就是干这个工作的
通常这个宏是用于IDispatch接口。我们先来看看它的典型用法:
class COuter :
   public IDispatchImpl<IOuter1, &IID_IOuter1, //IOuter1是一个双接口
                        &LIBID_COMMAPLib)>,
   public IDispatchImpl<IOuter2, &IID_IOuter2, //IOuter2也是一个双接口
                        &LIBID_COMMAPLib)>,
   public ...
{
public:
   COuter(){}
   ...
   BEGIN_COM_MAP(COuter)
      COM_INTERFACE_ENTRY2(IDispatch, IOuter2) //将暴露IOuter2所继承的路线
      COM_INTERFACE_ENTRY(IOuter1)
      COM_INTERFACE_ENTRY(IOuter2)
   ...
   END_COM_MAP
};
IDispatchImpl<...>这个类中实现了IDispatch接口,所以现在组件中有两个IDispatch
的实现。那查询IDispatch接口时,返回哪个实现呢?
我们再来看看COM_INTERFACE_ENTRY2(x, x2)的定义
#define BEGIN_COM_MAP(x) public: \
        typedef x _ComMapClass; \
        ....................
#define COM_INTERFACE_ENTRY2(x, x2)\
        {&_ATL_IIDOF(x),\       //得到接口的IID值
        (DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\
        _ATL_SIMPLEMAPENTRY},   //表明是一个简单接口
现在问题就在于(DWORD)((x*)(x2*)((_ComMapClass*)8))-8是个什么意思?

我们先来考察一下下面一段代码:
class A1
{
public:
        virtual void Test(){}
};

class A2 : public A1
{
public:
        virtual void Test(){}
};

class A3 : public A1
{
public:
        virtual void Test(){}
};

class A : public A2, public A3
{
};

{
        DWORD   dw;
        dw      =       (DWORD)((A *)8);                //dw = 0x08
        dw      =       (DWORD)((A3 *)(A *)8);          //dw = 0x0c
        dw      =       (DWORD)((A1 *)(A3 *)(A *)8);    //dw = 0x0c
        dw      =       (DWORD)((A1 *)(A3 *)(A *)8) - 8;//dw = 4
}
这个继承图是个典型的菱形结构,在类A中保存有两个虚函数表指针,分别代表着它的两
个分支。当为类A申明一个对象并实例化时,系统会为其分配内存。在这块内存的最顶端
保留着它的两个虚函数表指针。分析程序运行的结果,可以看出,最后的结果4代表了指
向接口A3的虚函数表指针与类A对象的内存块顶端之间的偏移量。

下面我们再看一个更为复杂点的继承关系:
class B1
{
public:
        virtual void Test(){}
};

class B2
{
public:
        virtual void Test(){}
};

class B3
{
public:
        virtual void Test(){}
};

class B4 : public B1, public B2
{
public:
        virtual void Test(){}
};

class B5 : public B2, public B3
{
public:
        virtual void Test(){}
};

class B : public B4, public B5
{
};

{
        DWORD   dw;
        dw      =       (DWORD)((B *)8);                //dw = 0x08
        dw      =       (DWORD)((B5 *)(B *)8);          //dw = 0x10
        dw      =       (DWORD)((B2 *)(B5 *)(B *)8);    //dw = 0x10
        dw      =       (DWORD)((B2 *)(B5 *)(B *)8) - 8;//dw = 8
}
类B将保留四个虚函数表指针,因为它共有四个分支。我们的目的是想获得B::B5::B2这
个分支中的B2接口,最后的结果8正是我们所需要的,它表示在类B内存块的偏移量。
从上面两个例子中,我们已经明白了(DWORD)((x*)(x2*)((_ComMapClass*)8))-8的作用
通过这个值我们能获得我们所需要的接口。
下面我们针对我们的实际情况COM_INTERFACE_ENTRY2(IDispatch, IOuter2)来分析一下
IDispatchImpl<class T,...>模板类从类T中派生,所以COuter要从两个它的模板类中
继承,IOuter1、IOuter2都是双接口,即都是从IDispatch派生的类,所以可得COuter
有两条分支,也是个菱形结构,所以按照我们的示例,这个偏移值也应该是4。为了证明
我们的设想,我们再来通过函数堆栈来验证我们的结果。

函数堆栈:
5.ATL::AtlInternalQueryInterface(...)
4.ATL::CComObjectRootBase::InternalQueryInterface(...)
3.CMyObject::_InternalQueryInterface(...)
2.ATL::CComObject<CMyObject>::QueryInterface(...)
1.pUnk->QueryInterface(IID_IDispatch, (void **)&pDispatch)

解释:
1:这是我们的验证代码,pUnk是组件的IUnknown指针
2--5:这些代码我们现在都已经很熟悉了,我们只需再看看AtlInternalQueryInterface
的具体实现。
ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
        const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
   ...........
   while (pEntries->pFunc != NULL)
   {
        BOOL bBlind = (pEntries->piid == NULL);
        if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
        {
           if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
           {
                ATLASSERT(!bBlind);
                IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw);
                pUnk->AddRef();
                *ppvObject = pUnk;
                return S_OK;
           }
           .....//如果是非简单接口的话...
        }
        pEntries++;
   }
   return E_NOINTERFACE;
}
关键的一句话就是IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw);
通过观察变量,正如我们所料pEntries->dw=4。(int)pThis+pEntries->dw)保证了我们
可以得到IOuter2分支的虚函数表,又因为IDispatch也是从IUnknown继承,在虚函数表
的最顶端放的是IUnknown的虚函数指针,所以进行(IUnknown *)强制转换,可以获得这
个虚函数表的顶端地址,这正是我们所需要的。或许会问为什么得到的是虚函数表的地
址,而不是一个类实例的地址呢?别忘了,接口是没有数据的,它只有纯虚函数。对于
客户来说,它只能通过接口定义的虚函数来访问它,而不可能访问实现接口的类的成员
变量,组件的数据对客户来说是不可见的,所以只用得到虚函数表的地址就行了。


                ----------未完待续--------

--
才疏学浅,胡言乱语;不对之处,敬请指正。



                路漫漫兮,其修远。
                吾将上下而求索。
※ 修改:.haojs 于 Sep  3 08:13:12 修改本文.[FROM: bbs.hit.edu.cn]
--
※ 转寄:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: bbs.hit.edu.cn]

--
☆ 来源:.哈工大紫丁香 bbs.hit.edu.cn.[FROM: haojs.bbs@bbs.whnet.]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:8.140毫秒