1. 程式人生 > >C++純手工打造COM:COM之來龍去脈——元件如何被建立

C++純手工打造COM:COM之來龍去脈——元件如何被建立

本文意旨幫助初涉COM的學者能對COM元件的建立過程有一個清晰的瞭解。全文以《COM技術內幕》第7章的示例程式碼為藍本,稍做修改之後進行詳細介紹。如果你也閱讀過此書的相關內容,那麼理解起來將會更容易。

COM技術內幕》這本書的示例程式碼編寫於1996年。時至今日,編譯器發生了或多或少的變化,將本書作者編寫的程式碼重新組織到Visual studio 2008中併成功編譯,對於當時剛接觸COM的我來說都略有困難,當時的我甚至不知道程序中伺服器的元件需要註冊才能呼叫(當然,現在我不需要註冊也能呼叫),這些都是學習COM的過程中遇到的痛苦,但不是全部,因為學習的過程也是有樂趣的,那就是有所收穫。

在介紹COM的建立過程之前,我想用一定的篇幅來解釋一下COM到底是什麼!開句玩笑,嘿嘿!好了,要正文了。

先整體,再區域性,先看看我從別處扒來的圖:

首先,我們來看一下客戶是如何使用API函式來啟動元件的建立的:


  1. //in client.cpp
  2. #include <objbase.h>
  3. #include <iostream>
  4. #include "..\Chapter7\Iface.h"//包含了諸如IX的宣告
  5. #include "..\Chapter7\GUIDS.cpp"//包含了諸如IID_IX、CLSID_Component1的定義
  6. int main()  
  7. {  
  8.     CoInitialize(NULL) ;  
  9.     IX* pIX = NULL ;   
  10.     HRESULT hr = ::CoCreateInstance(CLSID_Component1,  
  11.                                     NULL,   
  12.                                     CLSCTX_INPROC_SERVER,  
  13.                                     IID_IX,   
  14.                                     (void
    **)&pIX) ;  
  15.     if (SUCCEEDED(hr))  
  16.     {  
  17.         pIX->Fx() ;   
  18.   }  
  19.   CoUninitialize();  
  20.   return 0;  
  21. }  

正如你所看到的,客戶通過引數CLSID_Component1和 IID_IX呼叫了CoCreateInstance函式來建立所需要的元件。在此你應該對CLSID_Component1和 IID_IX高度警覺,因為這兩個引數是建立元件的依據。下面我開始分析CoCreateInstance的呼叫過程。

<>首先有一點必須要明白,CoCreateInstance實際上是用CoGetClassObject實現的:

  1. HRESULT CoCreateInstance(const CLSID& clsid,IUnknown * pUnknownOuter,DWORD dwClsContext,const IID& iid,void ** ppv)  
  2. {  
  3.     *ppv=NULL;  
  4.     IClassFactory* pIFactory=NULL;  
  5.     HRESULT hr=CoGetClassObject(clsid,dwClsContext,NULL,IID_IClassFactory,(void**)&pIFactory);  
  6.     If(SUCCEEDED(hr))  
  7.     {  
  8.         hr=pIFactory->CreateInstance(pUnknownOuter,iid,&ppv);  
  9.         pIFactory->Release();  
  10.     }  
  11.     return hr;  
  12. }  

由上面的程式碼可以看到,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函式:

  1. //in component’s DLL
  2. STDAPI DllGetClassObject(const CLSID& clsid,  
  3.                          const IID& iid,  
  4.                          void** ppv)  
  5. {  
  6.     if (clsid != CLSID_Component1)  
  7.     {  
  8.         return CLASS_E_CLASSNOTAVAILABLE ;  
  9.     }  
  10.     CFactory* pFactory = new CFactory ;    
  11.     if (pFactory == NULL)  
  12.     {  
  13.         return E_OUTOFMEMORY ;  
  14.     }  
  15.     HRESULT hr = pFactory->QueryInterface(iid, ppv);  
  16.     pFactory->Release() ;  
  17.     /*if pFactory->QueryInterface failed,pFactory->Release() will delete itself.*/
  18.     return hr ;  
  19. }  

由此可看到,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)將只能請求IUnknownIClassFactory介面指標,正如下面的程式碼所示:

  1. HRESULT __stdcall CFactory::QueryInterface(const IID& iid, void** ppv)  
  2. {      
  3.     if ((iid == IID_IUnknown) || (iid == IID_IClassFactory))  
  4.     {  
  5.         *ppv = static_cast<IClassFactory*>(this) ;   
  6.     }  
  7.     else
  8.     {  
  9.         *ppv = NULL ;  
  10.         return E_NOINTERFACE ;  
  11.     }  
  12.     reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;  
  13.     return S_OK ;  
  14. }  

這沒有問題,我們就是要這樣子做!因為CoGetClassObject在呼叫DLL中的DllGetClassObject函式時,傳遞給它的iid值是固定的IID_IClassFactory,因此我們類廠的QueryInterface即上述的CFactory::QueryInterface函式也只需要對IID_IUnknown和IID_IClassFactory處理就行。注意,CFactory::QueryInterface函式返回之後,* ppv就儲存了建立所需元件的類廠指標。至此,CoGetClassObject函式呼叫完成。

<>接下來,我們來看看前面強調但未正式提及的引數IID_IX。為了避免你反覆滾滑鼠滾輪,我又將CoCreateInstance的實現程式碼再次貼到這裡,同時也因為對CoCreateInstance的實現進行分析顯示太重要了:

  1. HRESULT CoCreateInstance(const CLSID& clsid,IUnknown * pUnknownOuter,DWORD dwClsContext,const IID& iid,void ** ppv)  
  2. {  
  3.     *ppv=NULL;  
  4.     IClassFactory* pIFactory=NULL;  
  5.     HRESULT hr=CoGetClassObject(clsid,dwClsContext,NULL,IID_IClassFactory,(void**)&pIFactory);  
  6.     If(SUCCEEDED(hr))  
  7.     {  
  8.         hr=pIFactory->CreateInstance(pUnknownOuter,iid,&ppv);  
  9.         pIFactory->Release();  
  10.     }  
  11.     return hr;  
  12. }  

可以看到,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:

  1. HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter,  
  2.                                            const IID& iid,  
  3.                                            void** ppv)   
  4. {  
  5.     if (pUnknownOuter != NULL)  
  6.     {  
  7.         return CLASS_E_NOAGGREGATION ;  
  8.     }  
  9.     /* Create an actual component even if the requested interface is not supported. 
  10.     But subsequent calling Release will delete itselt if QueryInterface failed.*/
  11.     CA* pA = new CA ;  
  12.     if (pA == NULL)  
  13.     {  
  14.         return E_OUTOFMEMORY ;  
  15.     }  
  16.     // Get the requested interface.
  17.     HRESULT hr = pA->QueryInterface(iid, ppv) ;  
  18.     // Release the IUnknown pointer.
  19.     // (If QueryInterface failed, component will delete itself.)
  20.     pA->Release() ;  
  21.     return hr ;  
  22. }  <