设为首页 加入收藏

TOP

ATL接口映射宏详解(三)
2012-11-04 15:22:38 来源: 作者: 【 】 浏览:1240
Tags:ATL 接口 映射 详解
  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 *)强制转换,可以获得这个虚函数表的顶端地址,这正是我们所需要的。或许会问为什么得到的是虚函数表的地址,而不是一个类实例的地址呢?别忘了,接口是没有数据的,它只有纯虚函数。对于客户来说,它只能通过接口定义的虚函数来访问它,而不可能访问实现接口的类的成员变量,组件的数据对客户来说是不可见的,所以只用得到虚函数表的地址就行了。

首页 上一页 1 2 3 4 5 6 7 下一页 尾页 3/7/7
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇基于VC实现Java和Matlab的通信 下一篇用ATL实现VC插件

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: