荔园在线

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

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


发信人: Deny (冬天来咯), 信区: Program
标  题: 一、COM_INTERFACE_ENTRY(x)
发信站: 荔园晨风BBS站 (Thu Nov 15 19:53:29 2001), 转信

一、COM_INTERFACE_ENTRY(x)
首先我们从一个最典型的应用开始:
定义一个最简单的ATL DLL:
class ATL_NO_VTABLE CMyObject :
public CComObjectRootEx,
public CComCoClass,
public IDispatchImpl
{
        .....
        BEGIN_COM_MAP(CMyObject)
        COM_INTERFACE_ENTRY(IMyObject) //一个双接口
        COM_INTERFACE_ENTRY(IDispatch)
        END_COM_MAP()
        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 >::CreateInstance(...)
2.ATL::CComCreator2 >, ATL::CComCreator > >::CreateInstance(...)
1.ATL::CComClassFactory::CreateInstance(...)
4.ATL::AtlModuleGetClassObject(...)
9.ATL::AtlInternalQueryInterface(...)
8.ATL::CComObjectRootBase::InternalQueryInterface(...)
7.ATL::CComClassFactory::_InternalQueryInterface(...)
6.ATL::CComObjectCached::QueryInterface(...)
5.ATL::CComCreator >:: 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)
                        {
                                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 CComCreator
{
{
        public:
        static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,
                                                LPVOID* ppv)

        {.....
        }
};
原来它里面只有一个CreateInstance函数,我们现在终于大体明白
_ClassFactoryCre
atorClass::CreateInstance表示什么意思了,它就代表CComClassFactory::
CreateIn stance(..)吧,差不多就是这样了。那我们再来看看CComCreator2有什么不同:

template
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,这是我们给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接口时就
有类似的过程,但还是把它单独提了出来,只为了看看典型的情形,呵呵。




--
  ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  ┄┄∮┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄∮┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  ┄┄┄┄┄┄┄∮┄┄_  _┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  ┄┄┄┄┄┄┄┄┄ ( ˇ ) ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
  ┄┄┄∮┄┄┄┄┄┄╲╱┄┄┄┄┄┄┄┄┄最后一首歌·让我想起远去的你┄┄
  ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄

※ 修改:·Deny 於 Mar 31 10:37:05 修改本文·[FROM: 192.168.1.166]
※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.1.201]


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

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