Programming 版 (精华区)
发信人: lofe ()感激生活(), 信区: Programming
标 题: ATL接口映射宏详解--(1)
发信站: 哈工大紫丁香 (Sun Sep 3 08:15:23 2000), 转信
以后将分别介绍ATL中各个形式为COM_INTERFACE_ENTRY_XX的接口映射宏。
并将按照从易到难的顺序讲解,每一部分都将建立在前一部分的基础上。
每一部分都将通过分析实际的调用函数堆栈来进行分析,堆栈的写法是从下向上。
文中所涉及的代码都为略写,只列出相关部分。
一、COM_INTERFACE_ENTRY(x)
首先我们从一个最典型的应用开始:
定义一个最简单的ATL DLL:
class ATL_NO_VTABLE CMyObject :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMyObject, &CLSID_MyObject>,
public IDispatchImpl<IMyObject, &IID_IMyObject, &LIBID_TEST2Lib>
{
.....
BEGIN_COM_MAP(CMyObject)
COM_INTERFACE_ENTRY(IMyObject) //一个双接口
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
.....
};
编写一段最简单的查询接口代码:
IUnknown *pUnk;
IMyObject *pMyObject;
CoCreateInstance(CLSID_MyObject, NULL, CLSCTX_INPROC_SERVER,
IID_IUnknown, (void **)&pUnk);
pUnk->QueryInterface(IID_IMyObject, (void **)&pMyObject);
执行客户代码,首先我们看看组件对象是如何被创建的。
函数调用堆栈一:
4...........
3.ATL::CComCreator<ATL::CComObject<CMyObject> >::CreateInstance(...)
2.ATL::CComCreator2<ATL::CComCreator<ATL::CComObject<CMyObject> >,
ATL::CComCreator<ATL::CComAggObject<CMyObject> > >::CreateInstance(...)
1.ATL::CComClassFactory::CreateInstance(...)
4.ATL::AtlModuleGetClassObject(...)
9.ATL::AtlInternalQueryInterface(...)
8.ATL::CComObjectRootBase::InternalQueryInterface(...)
7.ATL::CComClassFactory::_InternalQueryInterface(...)
6.ATL::CComObjectCached<ATL::CComClassFactory>::QueryInterface(...)
5.ATL::CComCreator<ATL::CComObjectCached<ATL::CComClassFactory> >::
CreateInstance(...)
4.ATL::AtlModuleGetClassObject(...)
3.ATL::CComModule::GetClassObject(...)
2.DllGetClassObject(...)
1.CoCreateInstance(...)(客户端)
解释如下:
1:CoCreateInstance(CLSID_MyObject, NULL, CLSCTX_INPROC_SERVER,
IID_IUnknown, (void **)&pUnk);
其内部将调用OLE API函数CoGetClassObject(), 而CoGetClassObject则会通过
LoadLibrary(...)装入DLL,并调用DLL中的DllGetClassObject()函数。
2:STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
return _Module.GetClassObject(rclsid, riid, ppv);
}
其中值得注意的是_Module变量,在DLL中定义了全局变量:
CComModule _Module;
ATL通过一组宏:
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_MyObject, CMyObject)
END_OBJECT_MAP()
#define BEGIN_OBJECT_MAP(x) static _ATL_OBJMAP_ENTRY x[] = {
#define END_OBJECT_MAP() {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};
#define OBJECT_ENTRY(clsid, class) {&clsid, class::UpdateRegistry,
class::_ClassFactoryCreatorClass::CreateInstance, //关键
class::_CreatorClass::CreateInstance,
NULL, 0, class::GetObjectDescription,
class::GetCategoryMap, class::ObjectMain },
生成一个静态全局_ATL_OBJMAP_ENTRY型数组:ObjectMap[];
然后ATL又在
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
.....
_Module.Init(ObjectMap, hInstance, &LIBID_TEST2Lib);
.....
}初始化_Module //注意在有的情况下是在InitInstance()中初始化_Module
那么_Module初始化都做了些什么呢,其实他什么也没做,在CComModule::Init中,它
调用AtlModuleInit(_ATL_MODULE* pM, _ATL_OBJMAP_ENTRY* p, HINSTANCE h),在其
中关键的只有一句:pM->m_pObjMap = p;可见_Module仅仅是把这个全局对象映射数组
ObjectMap[]给存了起来。那么为什么可以通过_Module.GetClassObject得到类厂呢?
其实关键在于我们的组件CMyObject继承的又一个基类CComCoClass!
在CComCoClass中缺省定义了一个宏DECLARE_CLASSFACTORY()而
#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(CComClassFactory)
#define DECLARE_CLASSFACTORY_EX(cf)
typedef CComCreator< CComObjectCached< cf > > _ClassFactoryCreatorClass;
CComCreator,CComObjectCached我们暂且不管,但一看到CComClassFactory,顾名思
义,我们就知道我们要的类厂终于出现了!每个组件内部原来都有一个类厂对象。
绕了一大圈,我们现在已经知道了_Module中包含了我们所要的每个组件的类厂对象,
这对目前来说已经足够了,现在继续路由下去!
3:HRESULT CComModule::GetClassObject(REFCLSID rclsid,REFIID riid,LPVOID* ppv)
{
return AtlModuleGetClassObject(this, rclsid, riid, ppv);
}
CComModule::GetClassObject的实现非常简单,仅仅是调用ATL的API函数。
4:ATLINLINE ATLAPI AtlModuleGetClassObject(_ATL_MODULE* pM, REFCLSID rclsid,
REFIID riid, LPVOID* ppv)
{
_ATL_OBJMAP_ENTRY* pEntry = pM->m_pObjMap;//从_Module中取出对象映射数组
while (pEntry->pclsid != NULL)
{
if ((pEntry->pfnGetClassObject != NULL) && InlineIsEqualGUID(rclsid,
*pEntry->pclsid))
{
if (pEntry->pCF == NULL)
{
if (pEntry->pCF == NULL)
hRes = pEntry->pfnGetClassObject(pEntry->pfnCreateInstance,
IID_IUnknown, (LPVOID*)&pEntry->pCF);
}
if (pEntry->pCF != NULL)
hRes = pEntry->pCF->QueryInterface(riid, ppv);
break;
}
pEntry = _NextObjectMapEntry(pM, pEntry);
}
}
现在好象已经有点看不懂了,看来我们得看看_ATL_OBJMAP_ENTRY的结构了
struct _ATL_OBJMAP_ENTRY
{
const CLSID* pclsid;
HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister);
_ATL_CREATORFUNC* pfnGetClassObject;
_ATL_CREATORFUNC* pfnCreateInstance;
IUnknown* pCF;
DWORD dwRegister;
_ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
_ATL_CATMAPFUNC* pfnGetCategoryMap;
}
pclsid很清楚就代表着我们组件的CLSID;pfnGetClassObject我们也已经知道了它就
是CMyObject::_ClassFactoryCreatorClass::CreateInstance(我们组件所包含的类
厂对象的CreateInstance函数);pCF我们也可以猜出它是指向这个类厂的IUnknown指
针,代表这个类厂对象是否被创建过,若类厂对象已经存在,就不用再创建新的类厂
对象了。现在就剩下pfnCreateInstance我们还不明白怎么回事。其实答案还是在
CComCoClass中!
在CComCoClass中缺省定义了宏DECLARE_AGGREGATABLE(x),这个宏表示这个组件既可以
是聚集的也可以是非聚集的,关于聚集的概念我们暂且不理,先看它的定义:
#define DECLARE_AGGREGATABLE(x) public:\
typedef CComCreator2< CComCreator< CComObject< x > >, \
CComCreator< CComAggObject< x > > > _CreatorClass;
我们看到了一个熟悉的字符串_CreatorClass, 原来这还有一个组件包含的对象。但还
有一个问题我们没有搞清楚,就是为什么_ClassFactoryCreator和_CreatorClass后面
都要跟着一个CreateInstance? 看来我们必须先来看看CComCreator是个什么东西了。
template <class T1>
class CComCreator
{
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{.....
}
};
原来它里面只有一个CreateInstance函数,我们现在终于大体明白_ClassFactoryCre
atorClass::CreateInstance表示什么意思了,它就代表CComClassFactory::CreateIn
stance(..)吧,差不多就是这样了。那我们再来看看CComCreator2有什么不同:
template <class T1, class T2>
class CComCreator2
{
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
return (pv == NULL) ?
T1::CreateInstance(NULL, riid, ppv) :
T2::CreateInstance(pv, riid, ppv);
}
};
这个类与CComCreator很类似,都只有一个CreateInstance成员函数,从_CreatorClass
中我们可以知道它实际上包含两个类CComObject,CComAggObject的CreateInstance函
数(通过CComCreator),其中CComObject用于非聚集对象,CComAggObject用于聚集对象
根据情况它建立相应的对象。(ATL中实际生成的组件对象不是CMyObject,而是
CComObject,CComAggObject或CComPolyObject对象,这个概念很重要,但现在暂且不谈)
现在我们对AtlModuleGetClassObject(...)基本已经知道是怎么回事了,它就是根据
存在对象映射数组中的创建类厂的函数的地址来创建类厂。pfnGetClassObject以及
pfnCreateInstance我们基本上都已经知道是怎么回事了,但还有一个问题为什么要
把pEntry->pfnCreateInstance作为pEntry->pfnGetClassObject(...)中的一个参数
传递?答案在下面呢,让我们继续路由下去!
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<ATL::CComClassFactory>,这是我们给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<CMyObject>::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接口时就
有类似的过程,但还是把它单独提了出来,只为了看看典型的情形,呵呵。
-------------未完待续----------------
今天第一次写,没想到这么累,花了这么多时间,唉,看来还得熬两次夜了
--
才疏学浅,胡言乱语;不对之处,敬请指正。
路漫漫兮,其修远。
吾将上下而求索。
※ 修改:.haojs 于 Sep 3 08:12:59 修改本文.[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)
页面执行时间:213.049毫秒