C++純手工打造COM:COM之來龍去脈——元件如何被建立
本文意旨幫助初涉COM的學者能對COM元件的建立過程有一個清晰的瞭解。全文以《COM技術內幕》第7章的示例程式碼為藍本,稍做修改之後進行詳細介紹。如果你也閱讀過此書的相關內容,那麼理解起來將會更容易。
《COM技術內幕》這本書的示例程式碼編寫於1996年。時至今日,編譯器發生了或多或少的變化,將本書作者編寫的程式碼重新組織到Visual studio 2008中併成功編譯,對於當時剛接觸COM的我來說都略有困難,當時的我甚至不知道程序中伺服器的元件需要註冊才能呼叫(當然,現在我不需要註冊也能呼叫),這些都是學習COM的過程中遇到的痛苦,但不是全部,因為學習的過程也是有樂趣的,那就是有所收穫。
在介紹COM的建立過程之前,我想用一定的篇幅來解釋一下COM到底是什麼!開句玩笑,嘿嘿!好了,要正文了。
先整體,再區域性,先看看我從別處扒來的圖:
首先,我們來看一下客戶是如何使用API函式來啟動元件的建立的:
- //in client.cpp
- #include <objbase.h>
- #include <iostream>
- #include "..\Chapter7\Iface.h"//包含了諸如IX的宣告
-
#include "..\Chapter7\GUIDS.cpp"//包含了諸如IID_IX、CLSID_Component1的定義
- int main()
- {
- CoInitialize(NULL) ;
- IX* pIX = NULL ;
- HRESULT hr = ::CoCreateInstance(CLSID_Component1,
- NULL,
- CLSCTX_INPROC_SERVER,
- IID_IX,
-
(void
- if (SUCCEEDED(hr))
- {
- pIX->Fx() ;
- }
- CoUninitialize();
- return 0;
- }
正如你所看到的,客戶通過引數CLSID_Component1和 IID_IX呼叫了CoCreateInstance函式來建立所需要的元件。在此你應該對CLSID_Component1和 IID_IX高度警覺,因為這兩個引數是建立元件的依據。下面我開始分析CoCreateInstance的呼叫過程。
<一>、首先有一點必須要明白,CoCreateInstance實際上是用CoGetClassObject實現的:
- HRESULT CoCreateInstance(const CLSID& clsid,IUnknown * pUnknownOuter,DWORD dwClsContext,const IID& iid,void ** ppv)
- {
- *ppv=NULL;
- IClassFactory* pIFactory=NULL;
- HRESULT hr=CoGetClassObject(clsid,dwClsContext,NULL,IID_IClassFactory,(void**)&pIFactory);
- If(SUCCEEDED(hr))
- {
- hr=pIFactory->CreateInstance(pUnknownOuter,iid,&ppv);
- pIFactory->Release();
- }
- return hr;
- }
由上面的程式碼可以看到,CoCreateInstance首先呼叫CoGetClassObject。在此需要強調的一點是,CoGetClassObject從CoCreateInstance接受了CLSID_Component1和 IID_IX中的第一個實參(形參為clsid),即CLSID_Component1。到目前為此,先記住這一點就夠了。下面我們來分析CoGetClassObject函式的呼叫過程。
1、CoGetClassObject首先根據形參clsid在登錄檔項HKEY_CLASS_ROOT\CLSID下查詢與之匹配的值,如果找得到(前提是元件已經註冊,註冊的作用之一是在登錄檔中寫入實現該元件的DLL儲存在磁碟中的哪個位置,例如我註冊的元件在登錄檔中的情況是這樣的(如果圖片中的文字看不清,可以滑鼠右鍵另存為,再開啟看):
),那麼CoGetClassObject就將該DLL載入到當前程序地址空間中。
2、並呼叫該DLL中由程式設計師實現的DllGetClassObject函式:
- //in component’s DLL
- STDAPI DllGetClassObject(const CLSID& clsid,
- const IID& iid,
- void** ppv)
- {
- if (clsid != CLSID_Component1)
- {
- return CLASS_E_CLASSNOTAVAILABLE ;
- }
- CFactory* pFactory = new CFactory ;
- if (pFactory == NULL)
- {
- return E_OUTOFMEMORY ;
- }
- HRESULT hr = pFactory->QueryInterface(iid, ppv);
- pFactory->Release() ;
- /*if pFactory->QueryInterface failed,pFactory->Release() will delete itself.*/
- return hr ;
- }
由此可看到,DllGetClassObject首先對clsid進行判斷,如果clsid的值表明不是元件本身的CLSID,即CLSID_Component1,就返回CLASS_E_CLASSNOTAVAILABLE。否則就建立一個類廠例項(它也由程式設計師在編寫元件時自行實現),並通過此類廠例項指標請求由iid標識的介面,即pFactory->QueryInterface(iid, ppv)這行程式碼。 先等一等,還記得前面我說,CoGetClassObject從CoCreateInstance接受了CLSID_Component1和 IID_IX中的第一個實參(形參為clsid),即CLSID_Component1嗎?我們先不管 IID_IX去哪裡了。在此需要關心的是,這裡的iid是從哪裡傳進來的?值是什麼?答案是由CoGetClassObject傳進來的,值是IID_IClassFactory。回到前面步驟<一>中CoCreateInstance函式的實現,你將會看到,iid的值確實為IID_IClassFactory。這意味著什麼?pFactory->QueryInterface(iid, ppv)將只能請求IUnknown或IClassFactory介面指標,正如下面的程式碼所示:
- HRESULT __stdcall CFactory::QueryInterface(const IID& iid, void** ppv)
- {
- if ((iid == IID_IUnknown) || (iid == IID_IClassFactory))
- {
- *ppv = static_cast<IClassFactory*>(this) ;
- }
- else
- {
- *ppv = NULL ;
- return E_NOINTERFACE ;
- }
- reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
- return S_OK ;
- }
這沒有問題,我們就是要這樣子做!因為CoGetClassObject在呼叫DLL中的DllGetClassObject函式時,傳遞給它的iid值是固定的IID_IClassFactory,因此我們類廠的QueryInterface即上述的CFactory::QueryInterface函式也只需要對IID_IUnknown和IID_IClassFactory處理就行。注意,CFactory::QueryInterface函式返回之後,* ppv就儲存了建立所需元件的類廠指標。至此,CoGetClassObject函式呼叫完成。
<二>、接下來,我們來看看前面強調但未正式提及的引數IID_IX。為了避免你反覆滾滑鼠滾輪,我又將CoCreateInstance的實現程式碼再次貼到這裡,同時也因為對CoCreateInstance的實現進行分析顯示太重要了:
- HRESULT CoCreateInstance(const CLSID& clsid,IUnknown * pUnknownOuter,DWORD dwClsContext,const IID& iid,void ** ppv)
- {
- *ppv=NULL;
- IClassFactory* pIFactory=NULL;
- HRESULT hr=CoGetClassObject(clsid,dwClsContext,NULL,IID_IClassFactory,(void**)&pIFactory);
- If(SUCCEEDED(hr))
- {
- hr=pIFactory->CreateInstance(pUnknownOuter,iid,&ppv);
- pIFactory->Release();
- }
- return hr;
- }
可以看到,CoGetClassObject呼叫完成之後,pIFactory像前面所說的那樣,它指向了建立所需元件的類廠指標。隨後通過這個指標呼叫了CreateInstance函式,即pIFactory->CreateInstance(pUnknownOuter,iid,&ppv)這行程式碼。這時你會發現,傳遞給CreateInstance函式的iid正是傳給CoCreateInstance的那個iid,也就是客戶程式碼中的實參IID_IX。而這裡的CreateInstance函式是通過pIFactory來呼叫的,pIFactory的型別雖然是IClassFactory指標,但它實際指向的是派生類的例項,即我們的類廠CFactory。常規情況下,如果基類指標指向派生類,那麼只能通過此基類指標呼叫派生類中的基類方法,但這裡的CreateInstance是虛擬的,因此pIFactory->CreateInstance(pUnknownOuter,iid,&ppv)這行程式碼將執行派生類中的實現,而我們在派生類即CFactory中重寫了CreateInstance:
- HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter,
- const IID& iid,
- void** ppv)
- {
- if (pUnknownOuter != NULL)
- {
- return CLASS_E_NOAGGREGATION ;
- }
- /* Create an actual component even if the requested interface is not supported.
- But subsequent calling Release will delete itselt if QueryInterface failed.*/
- CA* pA = new CA ;
- if (pA == NULL)
- {
- return E_OUTOFMEMORY ;
- }
- // Get the requested interface.
- HRESULT hr = pA->QueryInterface(iid, ppv) ;
- // Release the IUnknown pointer.
- // (If QueryInterface failed, component will delete itself.)
- pA->Release() ;
- return hr ;
- } <