ATL接口映射宏详解

来源:互联网 发布:桔子浏览器手机版 知乎 编辑:程序博客网 时间:2024/06/06 17:48

ATL接口映射宏详解()

 

五.COM_INTERFACE_ENTRY_AGGREGATE(iid, punk)ATL例程COMMAP

 

    这一节中将介绍ATL中用于聚集对象的宏。聚集对象的概念请参阅其它参考书。

    现在先看一看这个宏的典型用法:

 

 

class CAgg :

public IDispatchImpl< IAgg, &IID_IAgg,&LIBID_AGGREGLib >,

public ISupportErrorInfo,

public CComObjectRoot,

public CComCoClass< CAgg,&CLSID_CAgg >

{

.....

};

 

CAgg是一个聚集类,它的实现与一般的ATL组件没有区别,只是注意在它的类定义中不要加入DECLARE_NO_AGGREGATABLE.

class COuter :

public CChainBase,

public IDispatchImpl< IOuter, &IID_IOuter,&LIBID_COMMAPLib >,

public CComCoClass< COuter,&CLSID_COuter >

{

HRESULT FinalConstruct();

void FinalRelease();

 

BEGIN_COM_MAP(COuter)

COM_INTERFACE_ENTRY_AGGREGATE(IID_IAgg, m_pUnkAgg.p)

END_COM_MAP()

 

DECLARE_GET_CONTROLLING_UNKNOWN()

 

CComPtr< IUnknown > m_pUnkAgg;

};

 

COuter包含了聚合组件CAgg,它包含了几个不同之处:

(1)加入了COM_INTERFACE_ENTRY_AGGREGATE(IID_IAgg,m_pUnkAgg.p)宏。

 

#define COM_INTERFACE_ENTRY_AGGREGATE(iid, punk)\

{&iid,\

(DWORD)offsetof(_ComMapClass, punk),\

_Delegate},

 

offsetof我们在上一节中已经见过,可以猜到它求的就是punk在类中的位置。也就是m_pUnkAggCOuter中的位置。

(2)加入了宏DECLARE_GET_CONTROLLING_UNKNOWN(),其定义为:

 

#define DECLARE_GET_CONTROLLING_UNKNOWN() public:\

virtual IUnknown* GetControllingUnknown() {returnGetUnknown();}

 

我们也没必要继续深究下去,仅从字面意思就可以看出这个函数将返回组件的IUnknown指针。

(3)COuter中加入一个成员变量:CComPtr< IUnknown > m_pUnkAgg; m_pUnkAgg将用于获得被聚集组件的IUnknown指针。

 

(4)重载了FinalConstruct,FinalRelease

 

HRESULT COuter::FinalConstruct()

{

IUnknown* pUnkOuter = GetControllingUnknown();

HRESULT hRes = CoCreateInstance(CLSID_CAgg, pUnkOuter,CLSCTX_ALL, IID_IUnknown, (void**)&m_pUnkAgg);

return hRes;

}

 

void COuter::FinalRelease()

{

m_pUnkAgg.Release();

.....

}

 

当创建组件COuter后将会调用FinalConstruct,所以会在这里创建聚集组件。原则上聚集组件可以仅在需要的时候才创建,但也可以随着包含它的组件一起创建。聚集组件的创建没什么特别之处,只是要注意它将查询IUnknown指针,并返回给m_pUnkAgg.外部组件将通过m_pUnkAgg操作聚集组件。另外注意到使用pUnkOuter作为CoCreateInstance的参数,这将导致创建CComAggObject<COuter >对象,内部包含一个CComContainedObject的包含对象。与上一节中的CComCachedTearOff<>类似,CComAggObject<COuter >也不是从COuter派生的,所以真正的组件对象不是CComAggObject< COuter >对象,而是它内部包含的CComContainedObject<COuter >对象。同样pUnkOuter得到的将是CComAggObject<>IUnknown指针,也同样调用它的QueryInterface会转而调用CComContainedObject_InternalQueryInterface函数(呵呵,现在可都还是我猜的,看我猜的对不对吧)

运行pOuter->QueryInterface(IID_IAgg, (void **)&pAgg1)

 

函数堆栈一:

 

9.ATL::AtlInternalQueryInterface(...)

8.ATL::CComObjectRootBase::InternalQueryInterface(...)

7.CAgg::_InternalQueryInterface(...)

6.ATL::CComAggObject< CAgg>::QueryInterface(...)

5.ATL::CComObjectRootBase::_Delegate(...)

4.ATL::AtlInternalQueryInterface(...)

3.ATL::CComObjectRootBase::InternalQueryInterface(...)

2.COuter::_InternalQueryInterface(...)

1.ATL::CComObject< COuter >::QueryInterface(...)

解释:

 

1-5:这几步函数调用我们已经见了很多次了,因为在这个宏定义使用了_Delegate,所以将调用CComObjectRootBase::_Delegate(...).

 

static HRESULT _Delegate(void* pv,REFIID iid,void**ppvObject,DWORD dw)

{

HRESULT hRes = E_NOINTERFACE;

IUnknown* p = *(IUnknown**)((DWORD)pv + dw);

if (p != NULL) hRes = p->QueryInterface(iid,ppvObject);

return hRes;

}

第二句话的含义我们在上一节中已经见过了,最后的结果p=COuter::m_pUnkAgg.

6:正如我们刚才所料,现在调用的是CComAggObject< CAgg>::QueryInterface()

 

STDMETHOD(QueryInterface)(REFIID iid, void **ppvObject)

{

//如果查询的是IUnknown,....

else

hRes = m_contained._InternalQueryInterface(iid,ppvObject);

return hRes;

}

 

也正如我们所料,将交给它的包含对象去做.(这段代码在上一节好象也见过是吧,呵呵)

7-9:同上一节一样,将交给CAgg::_InternalQueryInterface(...),剩下的工作将由CAgg完成了。最后返回的指针实际上将是CComContainedObject<CAgg >组件的接口指针。

 

运行pAgg1->QueryInterface(IID_IAgg, (void **)&pAgg2)

 

函数堆栈二:

 

9.CAgg::_InternalQueryInterface(...)

8.ATL::CComAggObject< CAgg>::QueryInterface(...)

7.ATL::CComObjectRootBase::_Delegate(...)

6.ATL::AtlInternalQueryInterface(...)

5.ATL::CComObjectRootBase::InternalQueryInterface(...)

4.COuter::_InternalQueryInterface(...)

3.ATL::CComObject< COuter >::QueryInterface(...)

2.ATL::CComObjectRootBase::OuterQueryInterface(...)

1.ATL::CComContainedObject< CAgg>::QueryInterface(...)

 

解释:

 

1-9:浏览整个堆栈,与我们上一节所见的堆栈二太相近了,这是因为都是使用了包含对象。包含对象起了个代理的作用,他先把查询交给外部对象(COuter)去做(1,2),当外部对象发现要查询的是聚集组件的接口时(IAgg),就会再把查询交还给它保留的聚集组件的指针(m_pUnkAgg,7步中,注意这不是真正的聚集组件),m_pUnkAgg再把查询交给包含对象(8步中),包含对象再把查询交给真正实现接口的类CAgg(9).若外部对象发现要查询的是外部组件的接口时,那就很简单了,直接查询就行了。这样就防止了外部组件与聚集组件查询操作的不一致性。唉,真个过程真麻烦,不过还好,与上一节的宏很类似。相关的源码可参看上一节。

 

六、COM_INTERFACE_ENTRY_AGGREGATE_BLIND ATL例程COMMAP

 

    上一节我们讲了COM_INTERFACE_ENTRY_AGGREGATE,这节要介绍的宏与它很类似。

 

#define COM_INTERFACE_ENTRY_AGGREGATE_BLIND(punk)\

{NULL,\

(DWORD)offsetof(_ComMapClass, punk),\

_Delegate},

 

从定义上就可以看出,它与上一节介绍宏的唯一区别就在于,它没有指明接口ID!!

所以在它的定义中第一项也是NULL

这个宏的用法与我们COM_INTERFACE_ENTRY_AGGREGATE一模一样。大家可以参考上一节内容以及ATL的例程COMMAP

我们来看看AtlInternalQueryInterface()中的相关代码。

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))

{

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;

}

 

注意变量bBlind

 

BOOL bBlind = (pEntries->piid == NULL);

若没指定接口ID,也继续执行后面的操作,可见即使并非我们所需要的IID,也会执行_Delegate.

从上可见,这个宏适用于一个聚集组件有多个接口的情况,这样只要是查询这个聚集组件的接口,就会进入_Delegate函数。但要特别注意的是这个宏的位置!!比如若是这样的顺序:

 

BEGIN_COM_MAP

COM_INTERFACE_ENTRY_AGGREGATE_BLIND(m_pUnkAggBlind.p)

COM_INTERFACE_ENTRY(IOuter)

END_COM_MAP

 

当查询IOuter接口时就会出错!!!

七、COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid)ATL例程COMMAP

 

先看看这个宏的定义:

 

#define COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk,clsid)\

{&iid,\

(DWORD)&_CComCacheData<\

CComAggregateCreator< _ComMapClass, &clsid>,\

(DWORD)offsetof(_ComMapClass, punk)\

>::data,\

_Cache},

 

先看看它的典型用法:

class CAutoAgg :

public IDispatchImpl< IAutoAgg, &IID_IAutoAgg,&LIBID_AGGREGLib >,

public ISupportErrorInfo,

public CComObjectRoot,

public CComCoClass< CAutoAgg,&CLSID_CAutoAgg>

{

......

};

 

与一般的组件并无二样。

class COuter :

public CChainBase,

public IDispatchImpl,

public CComCoClass

{

BEGIN_COM_MAP(COuter)

COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IAutoAgg,m_pUnkAutoAgg.p, CLSID_CAutoAgg)

END_COM_MAP()

 

CComPtr m_pUnkAutoAgg;

};

 

与宏COM_INTERFACE_ENTRY_AGGREGRATE(_)不同,COuter不用在FinalConstruct中创建聚集组件。外部组件会自动创建聚集组件!!!

1

 

template < class Creator, DWORD dwVar >

_ATL_CACHEDATA _CComCacheData< Creator, dwVar>::data = {dwVar, Creator::Creat eInstance};

 

2

 

static HRESULT WINAPI _Cache(void* pv, REFIID iid,void** ppvObject, DWORD dw)

{

HRESULT hRes = E_NOINTERFACE;

_ATL_CACHEDATA* pcd = (_ATL_CACHEDATA*)dw;

IUnknown** pp = (IUnknown**)((DWORD)pv +pcd->dwOffsetVar);

if (*pp == NULL) hRes = pcd->pFunc(pv,IID_IUnknown, (void**)pp);

if (*pp != NULL) hRes = (*pp)->QueryInterface(iid,ppvObject);

return hRes;

}

 

3

 

template < class T, const CLSID* pclsid >

class CComAggregateCreator

{

public:

static HRESULT WINAPI CreateInstance(void* pv,REFIID/*riid*/, LPVOID* ppv )

{

ATLASSERT(*ppv == NULL);

ATLASSERT(pv != NULL);

T* p = (T*) pv;

return CoCreateInstance(*pclsid,p->GetControllingUnknown(), CLSCTX_ALL, IID_IUnknown, ppv);

}

};

 

因为_Cache,_CComCacheData,CComAggregateCreator这几个类和函数我们已经在前面见过或者见过类似的,所以就不再多讲了。总之我们可以看到,若m_pUnkAutoAgg.p不为空则直接查询,否则创建聚集组件。

与宏COM_INTERFACE_ENTRY_AGGREGATE相比,这个宏似乎更好一些,仅当需要时才会创建,使用更简单。

八、COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND( punk, clsid )ATL例程COMMAP

 

看看它的定义:

 

#define COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(punk,clsid)\

{NULL,\

(DWORD)&_CComCacheData<\

CComAggregateCreator< _ComMapClass, &clsid>,\

(DWORD)offsetof(_ComMapClass, punk)\

>::data,\

_Cache},

呵呵,这个宏综合了 COM_INTERFACE_ENTRY_AUTOAGGREGATE() COM_INTERFACE_ENTRY_AGGREGATE_BLIND() 的特点,既可以自动创建也可以很方便地查询聚集组件中的多个接口。不再赘述!!

九、COM_INTERFACE_ENTRY_CHAIN(classname)ATL例程COMMAP

 

先看看它的定义:

 

#define COM_INTERFACE_ENTRY_CHAIN(classname)\

{NULL,\

(DWORD)&_CComChainData< classname, _ComMapClass>::data,\

_Chain},

 

典型用法:

 

class CChain :

public IDispatchImpl< IChain, &IID_IChain,&LIBID_COMMAPLib >,

public ISupportErrorInfo,

public CComObjectRoot,

public CComCoClass< CChain,&CLSID_CChain >

{

........

};

 

它与一般的组件无异。

class COuter :

public CChain,

....

{

BEGIN_COM_MAP(COuter)

......

COM_INTERFACE_ENTRY_CHAIN(CChain)

END_COM_MAP()

};

 

我们对查询的过程已经很熟悉了,可以直接来看看_Chain的功能。_Chain()CComObjectRootBase的成员函数:

static HRESULT WINAPI _Chain(void* pv, REFIID iid,void** ppvObject,DWORD dw)

{

_ATL_CHAINDATA* pcd = (_ATL_CHAINDATA*)dw;

void* p = (void*)((DWORD)pv + pcd->dwOffset);

return InternalQueryInterface(p, pcd->pFunc(), iid,ppvObject);

}

 

struct _ATL_CHAINDATA

{

DWORD dwOffset;

const _ATL_INTMAP_ENTRY* (WINAPI *pFunc)();

};

 

我们再看看宏定义中的dw部分:

template < class base, class derived >

_ATL_CHAINDATA_CComChainData< base, derived >::data =

{offsetofclass(base, derived), base::_GetEntries};

 

基本上我们已经看懂是怎么回事了,void *p将得到基类的指针,InteralQueryInterface我们已经很熟悉了,_Chain把基类的指针以及基类的接口映射宏传给它,实际上是查询基类的接口!!!

一般情况下把这个宏放在BEGIN_COM_MAPEND_COM_MAP之间的最后面,这表示只有在当前类中查不到接口时才去查父类的接口。不过也经常把它放在第一位,这时就是先去查父类接口,只有父类没有实现这种接口时才查自己。在ATL中组件是以多重继承的方式实现的,ATL定义了很多类实现了一些常用的接口,这些类经常被做为组件的基类,所以这个宏被大量使用。

 

所有重要的宏我们都已经讲过了,剩下的都是些很简单的宏了.呵呵,还是把它们都罗列一下,善始善终嘛.

 

十、COM_INTERFACE_ENTRY_IID(iid, x)

 

#define COM_INTERFACE_ENTRY_IID(iid, x)\

{&iid,\

offsetofclass(x, _ComMapClass),\

_ATL_SIMPLEMAPENTRY},

 

十一、COM_INTERFACE_ENTRY2_IID(iid, x, x2)

 

#define COM_INTERFACE_ENTRY2_IID(iid, x, x2)\

{&iid,\

(DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\

_ATL_SIMPLEMAPENTRY},

 

从定义上看这两个宏与COM_INTERFACE_ENTRY()COM_INTERFACE_ENTRY2()相比,都只是多了一项"iid"。没有别的好处,只不过由用户明确指出接口IID,而不用系统根据接口名字去转换了。

十二、COM_INTERFACE_ENTRY_FUNC( iid, dw, func )

 

#define COM_INTERFACE_ENTRY_FUNC(iid, dw, func)\

{&iid, \

dw, \

func},

 

还记得AtlInternalQueryInterface()中的代码吗?如果在接口映射表中找到了我们要找的接口,并且这个接口不是_ATL_SIMPLEENTRY型的,则执行宏定义中的指定的函数。

这个宏就给我们提供了自己编写处理函数的功能。这个函数必须是如下定义:

HRESULT WINAPI func(void* pv, REFIID riid, LPVOID*ppv, DWORD dw);

AtlInternalQueryInterface调用func,会传进相关的信息。pv是类对象的指针,riid是要查询的接口,ppv是要返回查询得到的接口指针,dw是在宏定义中指定的参数。另外如果函数中不打算返回接口指针,则应把ppv赋为NULL,并返回S_FALSE E_NOINTERFACE。返回S_FALSE刚会继续查找下去,若返回E_NOINTERFACE则会终止查询。若返回接口指针,则应返回S_OK.

十三、COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func)

 

#define COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func)\

{NULL, \

dw, \

func},

 

至于_BLIND类型的特点可以看前面几节。

十四、COM_INTERFACE_ENTRY_NOINTERFACE(x)

 

#define COM_INTERFACE_ENTRY_NOINTERFACE(x)\

{&_ATL_IIDOF(x), \

NULL, \

_NoInterface},

 

_NoInterfaceCComObjectRootBase的成员函数,看看它的定义:

static HRESULT WINAPI _NoInterface(...)

{

return E_NOINTERFACE;

}

 

原来它只是返回E_NOINTERFACE,并且将终止查询。

哈哈,看来是不想让别人查到这个接口啊!!!

十五、COM_INTERFACE_ENTRY_BREAK(x)

 

#define COM_INTERFACE_ENTRY_BREAK(x)\

{&_ATL_IIDOF(x), \

NULL, \

_Break},

 

_Break也是CComObjectRootBase的成员函数,看看它的定义:

static HRESULT WINAPI _Break(...)

{

iid;

_ATLDUMPIID(iid, _T("Break due to QI forinterface "), S_OK);

DebugBreak();

return S_FALSE;

}

 

如果查到这个接口将调用DebugBreak(),并返回S_FALSE,继续查询下去。DebugBreak()是什么效果大家自己试试吧,一定很熟悉的,呵呵。

    至此全部十五个接口映射宏我们都已经讲完了,唉,真是不容易,特别是前面几个宏跟踪起来很麻烦。因为文本方式的限制,所以很多东西不容易表达清楚。有些叫法也是我自己这么叫的,可能与别人的习惯不同。没办法,大家将就将就了,呵呵。

 

 

---全文完---

 

2000/3/31凌晨

补注:关于ATL中类厂的实现问题

1.当创建一个组件时,必须先创建它的类厂,再调用类厂的CreateInstance()来创建组件.

  CComCoClass中定义了宏DECLARE_CLASSFACTORY(),包含了组件的类厂对象.

 _ClassFactoryCreatorClass,它的CreateInstance是用来创建组件的类厂的.也就是

 CComCreator< ccomobjectcached< CCOMCLASSFACTORY >>::CreateInstance();

2.CComCoClass中也定义了宏DECLARE_AGGREGATABLE(),包含了对象_CreatorClass,

  这个对象实际上就是我们要创建的组件对象(具体定义看详解一),它也有一个 CreateInstance,这个函数是用来创建这个组件的!!

  当创建组件的类厂时,会把这个函数的地址告诉给类厂。

3.当我们成功的获得类厂对象后(此时类厂已经创建完毕),我们然后将调用类厂的CreateInstance(),在这个函数中,

  会调用组件的CreateInstance从而创建组件。

4.所以,可见这里总共牵扯到三个CreateInstance:

 (1)_ClassFactoryCreatorClass::CreateInstance() //用于创建组件的类厂对象

 (2)CComClassFactory::CreateInstance() //用于调用_CreatorClass::CreateInstance

 (3)_CreatorClass::CreateInstance() //用于创建组件

原创粉丝点击