荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Deny (冬天来咯), 信区: Program
标  题: 二、COM_INTERFACE_ENTRY2(x, x2)
发信站: 荔园晨风BBS站 (Thu Nov 15 20:07:30 2001), 转信

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

ATL中是以多重继承的方式来实现组件的,但在继承树中如果有多个分支实现了同

个接口,当查询这个接口时就需要知道把哪个分支返回给它。这个宏就是干这个工
作的
通常这个宏是用于IDispatch接口。我们先来看看它的典型用法:
class COuter :
public IDispatchImpl &LIBID_COMMAPLib)>,
public IDispatchImpl &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:
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模板类从类T中派生,所以COuter要从两个它的模板类中
继承,IOuter1、IOuter2都是双接口,即都是从IDispatch派生的类,所以可得
COuter
有两条分支,也是个菱形结构,所以按照我们的示例,这个偏移值也应该是4。为
了证明
我们的设想,我们再来通过函数堆栈来验证我们的结果。

函数堆栈:
5.ATL::AtlInternalQueryInterface(...)
4.ATL::CComObjectRootBase::InternalQueryInterface(...)
3.CMyObject::_InternalQueryInterface(...)
2.ATL::CComObject::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 *)强制转换,可以获
得这
个虚函数表的顶端地址,这正是我们所需要的。或许会问为什么得到的是虚函数表
的地
址,而不是一个类实例的地址呢?别忘了,接口是没有数据的,它只有纯虚函数。
对于
客户来说,它只能通过接口定义的虚函数来访问它,而不可能访问实现接口的类的
成员
变量,组件的数据对客户来说是不可见的,所以只用得到虚函数表的地址就行了。




--

         .-. .-.                        ?
        (   `   )     □    悄悄地把心锁起来,藏在你不知道的地方    □
         `.   .'      □    等到你来的那一天,我让你的心不再空白    □
           `.'


※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.1.201]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店