5:
注意这里的T1是CComObjectCached< ATL::CComClassFactory >,这是我们给CComCreator 的模板参数。我们又一次看到了我们熟悉的操作符'new'!直到现在我们终于创建了组件的类厂。但还没完,继续往下走,看看SetVoid(pv)里干了些什么?
大家还记得我们曾经把CMyObject::_CreatorClass::CreateInstance作为参数传给 pEntry->pfnGetClassObject(...)吧,当时我们不明白是怎么回事,现在已经豁然开朗!原来是类厂需要它来创建组件对象!虽然我们只是从字面意思猜出这一点,但实际上也正如我们所预料的那样,在 CComClassFactory::CreateInstance(...)中,我们看到了m_pfnCreateInstance(pUnkOuter, riid, ppvObj);现在一切都已经明白了, ATL为我们创建类厂而作的层层包装我们都已经打开,剩下的创建组件的过程已经是我们很熟悉的过程了!
但是现在还没有完,我们还需要为类厂对象查询一个IUnknown指针,这个指针就存在我们在前面所看到的pEntry->pCF中。
6:
现在调用的是CComObjectCached::QueryInterface,至于这个类有何特别之处,我们现在好象还不需要知道,我也很累的说,呵呵。
7:
所有的类的_InternalQueryInterface(...)都是在BEGIN_COM_MAP中定义的。 CComObjectCached没有BEGIN_COM_MAP宏,所以现在调用的是CComClassFactory的。注意把this指针和接口映射数组_GetEntries()传给了InternalQueryInterface(), 这是InternalQueryInterface(...)实现查询的依据。在BEGIN_COM_MAP(x)中定义了一个静态的接口映射数组:
_ATL_INTMAP_ENTRY _entries[];
每一个接口映射宏实际上都是向这个数组中增加了一项。一个接口映射宏包括三个部分:接口的IID号、偏移值(大部分时候下)、需要执行的函数,对一般接口来说不用执行其他函数。_GetEntries()就是返回这个数组。还有一些细节问题以后再说。
8:
现在调用的是CComObjectRootBase::InternalQueryInterface(...)
9:现在我们终于到了QueryInterface的鼻祖了。
AtlInternalQueryInterface(...)是整个查询过程的终点,它遍历接口映射表,并根据每一项做出相应的动作。ATL中的消息映射宏有很多种,相应的动作也很多,但现在我们不管那些,现在我们要做的就是查到一个IUnknown接口,这很容易,我们甚至不需要遍历接口映射表。
这里有一个规定,接口映射表的第一个接口必须是_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.如果继续路由下去的话,我们的堆栈还可以很长,但这只是重复的枯躁的劳动。我就不继续走下去了,我也很累的说,唉。
函数调用堆栈二:
解释如下:
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接口,所以我们来看看我们以前没列出的代码
函数的逻辑很清楚,只有两点可能不太理解,一个是 (IUnknown*)((int)pThis+pEntries->dw)是什么意思,另一个是pEntries->pFunc到底 要干些什么事。前一个问题将在讲述COM_INTERFACE_ENTRY2中讲述,后一个问题将在以后讲述不同类型的接口时分别解释。饭总是要一口一口吃的嘛,呵呵。 现在我们只需关心一下我们的IMyObject是怎么被查找的。看一下它的宏我们把COM_INTERFACE_ENTRY(IMyObject)解开以后形式为:
同样对于offsetofclass(IMyObject, CMyObject)我们也将留到下一次再讲。根据这个结构,我们很容易就能获得IMyObject接口指针。
0:OK,it is over.依次退栈返回。
其实这次查询发生的过程在刚才的调用序列中也发生了,当查询IClassFactory接口时就有类似的过程,但还是把它单独提了出来,只为了看看典型的情形,呵呵。
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也实现了这个函数。
{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接口时就有类似的过程,但还是把它单独提了出来,只为了看看典型的情形,呵呵。