序言:
這幾天看了看ATL的介面映射宏,不知不覺看得比較深入了,突然就萌發了把它寫出來的想法。ATL中定義了很多介面映射宏,有幾個還是比較重要的,雖然好象沒有必要把它所有的細節都弄得很清楚,但深入學習的過程中也可以順帶學一學其他的ATL類,對它的機制也可以更清楚一些,應該還是會有些好處的吧。我按照我學習的過程把它寫出來,也 不知道大家能不能看懂。想模仿一下侯老師的手筆力爭把其內部細節解釋清楚,但也不敢大言不慚的美其名曰“深入淺出”,呵呵,只希望能對大家有所協助了。
以後將分別介紹ATL中各個形式為COM_INTERFACE_ENTRY_XX的介面映射宏並將按照從易到難的順序講解,每一部分都將建立在前一部分的基礎上。每一部分都將通過分析實際的調用函數堆棧來進行分析,堆棧的寫法是從下向上。文中所涉及的代碼都為略寫,只列出相關部分。
一、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() ..... }; |
編寫一段最簡單的查詢介面代碼:
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< ATL::CComObject< CMyObject > >::CreateInstance(...) 2.ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< CMyObject > >, ATL::CComCreator< ATL::CComAggObject< CMyObject > > >::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 OBJECT_ENTRY(clsid, class) {&clsid, class::UpdateRegistry, / class::_ClassFactoryCreatorClass::CreateInstance, ///關鍵 class::_CreatorClass::CreateInstance, / NULL, 0, class::GetObjectDescription, / class::GetCategoryMap, class::ObjectMain }, #define END_OBJECT_MAP() {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}}; |
產生一個靜態全域_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 T1 > class CComCreator { public: static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) {..... } }; |
原來它裡面只有一個CreateInstance函數,我們現在終於大體明白_ClassFactoryCreatorClass::CreateInstance 表示什麼意思了,它就代表CComClassFactory::CreateInstance(..)吧,差不多就是這樣了。那我們再來看看CComCreator2有什麼不同:
template < class T1, class T2 > 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(...)中的一個參數傳遞?答案在下面呢,讓我們繼續路由下去!