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_pUnkAgg在COuter中的位置。
(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_MAP和END_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},
_NoInterface是CComObjectRootBase的成员函数,看看它的定义:
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() //用于创建组件