ATL接口映射宏详解[2]

5:CComCreator::CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
T1* p = NULL;
ATLTRY(p = new T1(pv))//创建类厂对象
if (p != NULL)
{
p->SetVoid(pv);
p->InternalFinalConstructAddRef();
hRes = p->FinalConstruct();
p->InternalFinalConstructRelease();
if (hRes == S_OK)
hRes = p->QueryInterface(riid, ppv);
if (hRes != S_OK)
delete p;
}
}
}
注意这里的T1是CComObjectCached,这是我们给CComCreator
的模板参数。我们又一次看到了我们熟悉的操作符'new'!直到现在我们终于创建了组
件的类厂。但还没完,继续往下走,看看SetVoid(pv)里干了些什么?
void CComClassFactory::SetVoid(void* pv)
{
m_pfnCreateInstance = (_ATL_CREATORFUNC*)pv;
}
大家还记得我们曾经把CMyObject::_CreatorClass::CreateInstance作为参数传给
pEntry->pfnGetClassObject(...)吧,当时我们不明白是怎么回事,现在已经豁然开
朗!原来是类厂需要它来创建组件对象!虽然我们只是从字面意思猜出这一点,但实
际上也正如我们所预料的那样,在CComClassFactory::CreateInstance(...)中,我们
看到了m_pfnCreateInstance(pUnkOuter, riid, ppvObj);现在一切都已经明白了,
ATL为我们创建类厂而作的层层包装我们都已经打开,剩下的创建组件的过程已经是
我们很熟悉的过程了!
但是现在还没有完,我们还需要为类厂对象查询一个IUnknown指针,这个指针就存在
我们在前面所看到的pEntry->pCF中。

6:STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}
现在调用的是CComObjectCached::QueryInterface,至于这个类有何特别之处,我们现
在好象还不需要知道,我也很累的说,呵呵。

7:HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) /
{ return InternalQueryInterface(this, _GetEntries(), iid, ppvObject); }
所有的类的_InternalQueryInterface(...)都是在BEGIN_COM_MAP中定义的。
CComObjectCached没有BEGIN_COM_MAP宏,所以现在调用的是CComClassFactory的。
注意把this指针和接口映射数组_GetEntries()传给了InternalQueryInterface(),
这是InternalQueryInterface(...)实现查询的依据。
在BEGIN_COM_MAP(x)中定义了以下一个静态的接口映射数组:
_ATL_INTMAP_ENTRY _entries[];
每一个接口映射宏实际上都是向这个数组中增加了一项。一个接口映射宏包括三个部
分:接口的IID号、偏移值(大部分时候下)、需要执行的函数,对一般接口来说不用
执行其他函数。_GetEntries()就是返回这个数组。还有一些细节问题以后再说。

8:static HRESULT WINAPI InternalQueryInterface(void* pThis,
const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
...
HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries, iid, ppvObject);
...
}
现在调用的是CComObjectRootBase::InternalQueryInterface(...)

9:现在我们终于到了QueryInterface的鼻祖了。AtlInternalQueryInterface(...)是整
个查询过程的终点,它遍历接口映射表,并根据每一项做出相应的动作。ATL中的消
息映射宏有很多种,相应的动作也很多,但现在我们不管那些,现在我们要做的就是
查到一个IUnknown接口,这很容易,我们甚至不需要遍历接口映射表。
ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
if (ppvObject == NULL)
return E_POINTER;
*ppvObject = NULL;
if (InlineIsEqualUnknown(iid)) // use first interface
{
IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
...//还有一大堆呢,但现在用不上,就节省点空间吧
}
这里有一个规定,接口映射表的第一个接口必须是_ATL_SIMPLEENTRY型的。至于为什
么有这个要求,以及pThis+pEntries->dw是什么意思,我们以后再说吧,那也是一堆
问题。总之,我们现在如愿以偿轻松的获得了我们所需要的类厂对象以及IUnknown指
针。

4:我差一点以为我们可以胜得返回到第一步了,但在ATL::AtlModuleGetClassObject
处却又停了下来,看看它的源码,原来还要再通过我们刚获得的IUnknown指针查询
IClassFactory指针。又是一通相同的调用,从第6步到第9步一模一样,我们将进行
相同的调用。但注意在第9步中,我们这回查的不再是IUnknown指针了,所以我们需要
看看我刚才还没列出的代码,但这留到下一次函数堆栈再看吧
1:终于终于我们已经完成了创建类厂对象的全部操作,现在我们要做的就是我们熟悉的
调用类厂对象的CreateInstance(...)函数创建组件的过程了。正如我们所见到的,现
在OLE开始调用CComClassFactory::CreateInstance()了,我们还没忘记,在类厂对象
中保留了创建组件用的CreateInstance()函数, 这个过程已经很明朗了。
2.不用再重复了吧,看第4步。
3.不用再重复了吧,看第4步。
4.如果继续路由下去的话,我们的堆栈还可以很长,但这只是重复的枯躁的劳动。我就
不继续走下去了,我也很累的说,唉。

函数调用堆栈二:
0:............
5.ATL::AtlInternalQueryInterface(...)
4.ATL::CComObjectRootBase::InternalQueryInterface(...)
3.CMyObject::_InternalQueryInterface(...)
2.ATL::CComObject::QueryInterface(...)
1.pUnk->QueryInterface(IID_IMyObject, (void **)&pMyObject);(客户端)

解释如下:
1.我们通过刚刚获得的组件对象的IUnknown接口指针来查询IMyObject指针,这才是我们
真正需要的指针。
2.还记得我们说过ATL真正创建的组件并不是CMyObject,而是CComObject,CComAggObject
或CComPolyObject,这里我们创建的是CComObject.所以理所当然我们要调用
CComObject::QueryInterface(...),而确实CComObject也实现了这个函数。
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}
它只是简单地调用_InternalQueryInterface(...),我们也说过,只有类里面申明了
BEGIN_COM_MAP宏才会有_InternalQueryInterface(...),所以现在执行转到了它的父
类CMyObject中去,所以将调用CMyObject::_InterfaceQueryInterface(...)
3.以后的调用我们已经很熟悉了,还用我再说一遍吗,呵呵
4.这个调用我们也很熟悉了,不用多说了吧
5.现在我们将要查询的是一个非IUnknown接口,所以我们来看看我们以前没列出的代码

ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
//确保接口映射的第一项是个简单接口
//若是查询IUnknown接口,执行相应的操作
//以下将遍历接口映射表,试图找到相应的接口

while (pEntries->pFunc != NULL)
{
BOOL bBlind = (pEntries->piid == NULL);
if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
{
//_ATL_SIMPLEMAPENTRY就表明是个简单接口
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
{
ATLASSERT(!bBlind);
IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
else //如果不是一个简单接口,则需要执行相应的函数
{
HRESULT hRes=pEntries->pFunc(pThis,iid,ppvObject,pEntries->dw);
if (hRes == S_OK || (!bBlind && FAILED(hRes)))
return hRes;
}
}
pEntries++;
}
return E_NOINTERFACE;
}
}
函数的逻辑很清楚,只有两点可能不太理解,一个是
(IUnknown*)((int)pThis+pEntries->dw)是什么意思,另一个是pEntries->pFunc到底
要干些什么事。前一个问题将在讲述COM_INTERFACE_ENTRY2中讲述,后一个问题将在
以后讲述不同类型的接口时分别解释。饭总是要一口一口吃的嘛,呵呵。
现在我们只需关心一下我们的IMyObject是怎么被查找的。看一下它的宏
我们把COM_INTERFACE_ENTRY(IMyObject)解开以后形式为:
{&_ATL_IIDOF(IMyObject), //得到IMyObject的IID值
offsetofclass(IMyObject, CMyObject), //定义偏移量
_ATL_SIMPLEMAPENTRY},//表明是个简单接口
同样对于offsetofclass(IMyObject, CMyObject)我们也将留到下一次再讲。
根据这个结构,我们很容易就能获得IMyObject接口指针。
0:OK,it is over.依次退栈返回。
其实这次查询发生的过程在刚才的调用序列中也发生了,当查询IClassFactory接口时就
有类似的过程,但还是把它单独提了出来,只为了看看典型的情形,呵呵。


二、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 *)强制转换,可以获得这
个虚函数表的顶端地址,这正是我们所需要的。或许会问为什么得到的是虚函数表的地
址,而不是一个类实例的地址呢?别忘了,接口是没有数据的,它只有纯虚函数。对于
客户来说,它只能通过接口定义的虚函数来访问它,而不可能访问实现接口的类的成员
变量,组件的数据对客户来说是不可见的,所以只用得到虚函数表的地址就行了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值