青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

C++ Coder

HCP高性能計算架構,實現,編譯器指令優(yōu)化,算法優(yōu)化, LLVM CLANG OpenCL CUDA OpenACC C++AMP OpenMP MPI

C++博客 首頁 新隨筆 聯系 聚合 管理
  98 Posts :: 0 Stories :: 0 Comments :: 0 Trackbacks
http://blog.csdn.net/wangqiulin123456/article/details/8072545

目錄(?)[-]
  1. 走馬觀花看COM服務器
  2. 服務器生命其管理
  3. 實現接口,從IUnknown開始
  4. 構造器和析構器
  5. AddRef() 和 Release()
  6. QueryInterface()
  7. 深入CoCreateInstance()
  8. COM服務器注冊
  9. 創(chuàng)建COM對象——類工廠
  10. 一個定制接口的例子
  11. 類工廠實現
  12. 深入DllGetClassObject()
  13. 再談QueryInterface()
  14. 使用服務器的客戶端
  15. 其它細節(jié)——-COM宏

       本文為剛剛接觸COM的程序員提供編程指南,解釋COM服務器內幕以及如何用C++編寫自己的接口(前一篇博文主要是COM編程的入門COM編程入門不得不看的文章 :第一部分 什么是COM,如何使用COM)。繼上一篇COM編程入門之后,本文將討論有關COM服務器的內容,解釋編寫自己的COM接口和COM服務器所需要的步驟和知識,以及詳細討論當COM庫對COM服務器進行調用時,COM服務器運行的內部機制。

如果你讀過上一篇文章。應該很熟悉COM客戶端是怎么會事了。本文將討論COM的另一端——COM服務器。內容包括如何用C++編寫一個簡單的不涉及類庫的COM服務器。深入到創(chuàng)建COM服務器的內部過程,毫無遮掩地研究那些庫代碼是充分理解COM服務器內部機制的最好方法。

本文假設你精通C++并掌握了上一篇文章所討論的概念和術語。在這一部分將包括如下內容:

  •        走馬觀花看COM服務器——描述COM服務器的基本要求
  •        服務器生命其管理——描述COM服務器如何控制加載時間
  •        實現接口,從IUnknown 開始——展示如何用C++類編寫一個接口實現并描述IUnknown之方法的目的
  •       構造器和析構器
  •       AddRef() 和 Release()
  •       QueryInterface()
  •       深入CoCreateInstance()——探究CoCreateInstance()的調用機理
  •       COM服務器的注冊——描述完成服務器注冊所需要的注冊表入口
  •       創(chuàng)建COM對象——類工廠——描述創(chuàng)建客戶端要使用的COM對象的過程
  •       一個定制接口的例子——例子代碼示范了上述概念
  •       一個使用服務器的客戶端——舉例說明一個簡單的客戶端應用程序,用它來測試COM服務器
  •       其它內容——有關源代碼和調試的注釋

 

走馬觀花看COM服務器

         本文我們將討論最簡單的一種COM服務器,進程內服務器(in-process)。“進程內”意思是服務器被加載到客戶端程序的進程空間。進程內服務器都是DLLs,并且與客戶端程序同在一臺計算機上。進程內服務器在被COM庫使用之前必須滿足兩個條件或標準:

1.必須正確在注冊表的HKEY_CLASSES_ROOT\CLSID 鍵值下注冊;

2.必須輸出DllGetClassObject()函數;

這是進程內服務器運行的最小需求。在注冊表的HKEY_CLASSES_ROOT\CLSID 鍵值下必須創(chuàng)建一個鍵值,用服務器的GUID作為鍵名字,這個鍵值必須包含兩個鍵值清單,一是服務器的位置,二是服務器的線程模型。 COM庫對 DllGetClassObject() 函數進行調用是在CoCreateInstance() API中完成的。

還有三個函數通常也要輸出:

DllCanUnloadNow():由COM庫調用來檢查是否服務器被從內存中卸載;

DllRegisterServer():由類似RegSvr32的安裝實用程序調用來注冊服務器;

DllUnregisterServer():由卸載實用程序調用來刪除由DllRegisterServer()創(chuàng)建的注冊表入口;

另外,只輸出正確的函數是不夠的——還必須遵循COM規(guī)范,這樣COM庫和客戶端程序才能使用服務器。

服務器生命其管理

       DLL服務器的一個與眾不同的方面是控制它們被加載的時間。“標準的”DLLs被動的并且是在應用程序使用它們時被隨機加載/或卸載。從技術上講,DLL服務器也是被動的,因為不管怎樣它們畢盡還是DLL,但COM庫提供了一種機制,它允許某個服務器命令COM卸載它。這是通過輸出函數DllCanUnloadNow()實現的。這個函數的原型如下:

  1. HRESULT DllCanUnloadNow();  

 

       當客戶應用程序調用COM APICoFreeUnusedLibraries()時,通常出于其空閑處理期間,COM庫遍歷這個客戶端應用已加載所有的DLL服務器并通過調用它的DllCanUnloadNow()函數查詢每一個服務器。另一方面,如果某個服務器確定它不再需要駐留內存,它可以返回S_OK讓COM將它卸載。服務器通過簡單的引用計數來確定它是否能被卸載。下面是DllCanUnloadNow()的實現:

  1. extern UINT g_uDllRefCount;  // 服務器的引用計數  
  2. HRESULT DllCanUnloadNow()  
  3. {  
  4.     return (g_uDllRefCount > 0) ? S_FALSE : S_OK;  
  5. }  

 

如何處理引用計數將在下一節(jié)涉及到具體代碼時討論。

實現接口,從IUnknown開始

       有必要回想一下IUnknown派生的每一個接口。因為IUnknown包含了兩個COM對象的基本特性——引用計數和接口查詢。當你編寫組件對象類時(coclass),還要寫一個滿足自己需要的IUnknown實現。以實現IUnknown接口的組件對象類為例——下面這個例子可能是你編寫的最簡單的一個組件對象類。我們將在一個叫做CUnknownImpl的C++類中實現IUnknown。下面是這個類的聲明:

  1. class CUnknownImpl : public IUnknown  
  2. {  
  3. public:  
  4.     // 構造函數和析構器  
  5.     CUnknownImpl();  
  6.     virtual ~CUnknownImpl();  
  7.   
  8.     // IUnknown 方法  
  9.     ULONG AddRef();  
  10.     ULONG Release)();  
  11.     HRESULT QueryInterface( REFIID riid, void** ppv );  
  12.   
  13. protected:  
  14.     UINT m_uRefCount;  // 對象的引用計數  
  15. };  

 

構造器和析構器

構造器和析構器管理服務器的引用計數:

  1. CUnknownImpl::CUnknownImpl()  
  2. {  
  3.     m_uRefCount = 0;  
  4.     g_uDllRefCount++;  
  5. }  
  6.   
  7. CUnknownImpl::~CUnknownImpl()  
  8. {  
  9.     g_uDllRefCount--;  
  10. }  

 

當創(chuàng)建新的COM對象時,構造器被調用,它增加服務器的引用計數以保持這個服務器駐留內存。同時它還將對象的引用計數初始化為零。當這個COM對象被摧毀時,它減少服務器的引用計數。

AddRef() 和 Release()

      這兩個方法控制 COM 對象的生命期。AddRef()很簡單:

  1. ULONG CUnknownImpl::AddRef()  
  2. {  
  3.     return ++m_uRefCount;  
  4. }  

 

AddRef()只增加對象的引用計數并返回更新的計數。 Release()更簡單:

  1. ULONG CUnknownImpl::Release()  
  2. {  
  3. ULONG uRet = --m_uRefCount;  
  4.   
  5.     if ( 0 == m_uRefCount )  // 是否釋放了最后的引用?  
  6.         delete this;  
  7.   
  8.     return uRet;  
  9. }  

 

        除了減少對象的引用計數外,如果沒有另外的明確引用,Release()將摧毀對象。Release()也返回更新的引用計數。注意Release()的實現假設COM對象在堆中創(chuàng)建。如果你在全局粘上創(chuàng)建某個對象,當對象試圖刪除自己時就會出問題。

       現在應該明白了為什么在客戶端應用程序中正確調用AddRef()和 Release()是如此重要!如果在這了做得不對,你使用的對象會被很快摧毀,這樣的話在整個服務器中內存會很快溢出導致應用程序下次存取服務器代碼時崩潰。

      如果你編寫多線程應用,可能會想到使用++&替代InterlockedIncrement()和InterlockedDecrement()的線程安全問題。++&——用于單線程服務器很保險,因為即使客戶端應用是多線程的并從不同的線程中進行方法調用,COM庫都會按順序進行服務器的方法調用。也就是說,一旦一個方法調用開始,所有其它試圖調用方法的線程都將阻塞,直到第一個方法返回。COM庫本身確保服務器一次不會被一個以上的線程闖入。

QueryInterface()

       QueryInterface()簡稱QI(),由客戶端程序調用這個函數從COM對象請求不同的接口。我們在例子代碼中因為只實現一個接口,QI()會很容易使用。QI()有兩個參數:一個是所請求的接口IID,一個是指針的緩沖大小,如果查詢成功,QI()將接口指針地址存儲在這個緩沖指針中。

  1. HRESULT CUnknownImpl::QueryInterface ( REFIID riid, void** ppv )  
  2. {  
  3. HRESULT hrRet = S_OK;  
  4.   
  5.     // 標準QI()初始化 – 置 *ppv 為 NULL.  
  6.     *ppv = NULL;  
  7.   
  8.     // 如果客戶端請求提供的接口,給 *ppv.賦值  
  9.     if ( IsEqualIID ( riid, IID_IUnknown ))  
  10.         {  
  11.         *ppv = (IUnknown*) this;  
  12.         }  
  13.     else  
  14.         {  
  15.         // 不提供客戶端請求的接口   
  16.         hrRet = E_NOINTERFACE;  
  17.         }  
  18.   
  19.     // 如果返回一個接口指針。 調用AddRef()增加引用計數.  
  20.     if ( S_OK == hrRet )  
  21.         {  
  22.         ((IUnknown*) *ppv)->AddRef();  
  23.         }  
  24.   
  25.     return hrRet;  
  26. }  

 

在QI()中做了三件不同的事情:

1.初始化傳入的指針為NULL;

  1. *ppv =NULL;  

 

2.檢查riid,確定組件對象類(coclass)實現了客戶端所請求接口;

  1. if (IsEqualIID ( riid, IID_IUnknown ))  

 

3.如果確實實現勒索請求的接口,則增加COM對象的引用計數。

  1. ((IUnknown*) *ppv)->AddRef();  
  2. AddRef()調用很關鍵。  
  3.     *ppv = (IUnknown*) this;  

 

要創(chuàng)建新的COM對象引用,就必須調用這個函數通知COM對象這個新引用成立。在AddRef()調用中的強制轉換IUnknown*看起來好像多余,但是在QI()中初始化的*ppv有可能不是IUnknown*類型,所以最好是養(yǎng)成習慣對之進行強行轉換。

上面我們已經討論了一些DLL服務器的內部細節(jié),接下來讓我們回頭看一看當客戶端調用CoCreateInstance()時是如何處理服務器的。

深入CoCreateInstance()

      在本文的第一部分中,我們見過CoCreateInstance()API,其作用是當客戶端請求對象時,用它來創(chuàng)建對象。從客戶端的立場看,它是一個黑盒子。只要用正確的參數調用它即可得到一個COM對象。它并沒有什么魔法,只是在一個定義良好的過程中加載COM服務器,創(chuàng)建請求的COM對象并返回所要的指針。就這些。

下面讓我們來瀏覽一下這個過程。這里要涉及到幾個不太熟悉的術語,但不用著急,后面會對它們作詳細討論。

1.客戶端程序調用CoCreateInstance(),傳遞組件對象類的CLSID以及所要接口的IID;

2.COM庫在HKEY_CLASSES_ROOT\CLSID.鍵值下查找服務器的CLSID鍵值,這個鍵值包含服務器的注冊信息;

3.COM庫讀取服務器DLL的全路徑并將DLL加載到客戶端的進程空間;

4.COM庫調用在服務器中DllGetClassObject()函數為所請求的組件對象類請求類工廠;

5.服務器創(chuàng)建一個類工廠并將它從DllGetClassObject()返回;

6.COM庫在類工廠中調用CreateInstance()方法創(chuàng)建客戶端程序請求的COM對象;

7.CreateInstance()返回一個接口指針到客戶端程序;

COM服務器注冊

       COM 服務器必須在 Windows 注冊表中正確注冊以后才能正常工作。如果你看一下注冊表中的HKEY_CLASSES_ROOT\CLSID 鍵,就會發(fā)現大把大把子鍵,它們就是在這個計算機上注冊的COM服務器。當某個COM服務器注冊后(通常是用DllRegisterServer()進行注冊),就會以標準的注冊表格式在CLSID鍵下創(chuàng)建一個鍵,它名字為服務器的GUID。下面是一個這樣的例子:

  1. {067DF822-EAB6-11cf-B56E-00A0244D5087}  

 

     大括弧和連字符是必不可少的,字母大小寫均可。

     這個鍵的默認值是人可值別的組件對象類名,使用VC所帶的OLE/COM對象瀏覽器可以察看到它們。在GUID鍵的子鍵中還可以存儲其它信息。需要創(chuàng)建什么子鍵依賴于COM服務器的類型以及COM服務器的使用方法。對于本文例子中這個簡單的進程內服務器,我們值需要一個子鍵:InProcServer32。

InProcServer32鍵包含兩個串:這兩個串的缺省值是服務器DLL的全路徑和線程模型值(ThreadingModel)。線程模型超出了本文所涉及的范圍,我們先接受這個概念,這里我們指的是單線程服務器,用的模式為Apartment(即單線程公寓)。

創(chuàng)建COM對象——類工廠

        回首看一看客戶端的COM,它是如何以自己獨立于語言的方式創(chuàng)建和銷毀COM對象。客戶端調用CoCreateInstance()創(chuàng)建新的COM對象。現在我們來看看它在服務器端是如何工作的。

       你每次實現組件對象類的時候,都要寫一個旁類負責創(chuàng)建第一個組件對象類的實例。這個旁類就叫這個組件對象類的類工廠(class factory),其唯一目的是創(chuàng)建COM對象。之所以要一個類工廠,是因為語言無關的緣故。COM本身并不創(chuàng)建對象,因為它不是獨立于語言的也不是獨立于實現的。

       當某個客戶端想要創(chuàng)建一個COM對象時,COM庫就從COM服務器請求類工廠。然后類工廠創(chuàng)建COM對象并將它返回客戶端。它們的通訊機制由函數DllGetClassObject()來提供。

       術語 “類工廠”和“類對象”實際上是一回事。沒有那個單詞能精確描述類工廠的作用和義,但正是這個工廠創(chuàng)建了COM對象,而不是COM類所為。將“類工廠”理解成“對象工廠”可能會更有助于理解(實際上MFC就是這樣理解的——它的類工廠實現就叫做COleObjectFactory)。但“類工廠”是正式術語,所以本文也這樣用。

      當COM庫調用DllGetClassObject()時,它傳遞客戶端請求的CLSID。服務器負責為所請求的CLSID創(chuàng)建者各類工廠并將它返回。類工廠本身就是一個組件對象類,并且實現IClassFactory接口。如果DllGetClassObject()調用成功,它返回一個IClassFactory指針給COM庫,然后COM庫用IClassFactory接口方法創(chuàng)建客戶端所請求的COM對象實例。一下是IClassFactory接口:

  1. struct IClassFactory : public IUnknown  
  2. {  
  3.     HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppvObject );  
  4.     HRESULT LockServer( BOOL fLock );  
  5. };  

 

其中,CreateInstance()是創(chuàng)建COM對象的方法。LockServer()在必要時讓COM庫增加或減少服務器的引用計數。

一個定制接口的例子

      這個工程是一個能運行的DLL服務器例子,對象由類工廠創(chuàng)建,此DLL服務器在 CSimpleMsgBoxImpl組件對象類中實現了一個接口:ISimpleMsgBox。

接口定義

我們的新接口是ISimpleMsgBox。所有的接口多必須從IUnknown派生。這個接口只有一個方法:DoSimpleMsgBox()。注意它返回標準類型HRESULT。所有的方法都應該返回HRESULT類型,并且所有返回到調用者的其它數據都應該通過指針參數操作。

  1. struct ISimpleMsgBox : public IUnknown  
  2. {  
  3.     // IUnknown 方法  
  4.     ULONG AddRef();  
  5.     ULONG Release();  
  6.     HRESULT QueryInterface( REFIID riid, void** ppv );  
  7.   
  8.     // ISimpleMsgBox方法  
  9.     HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );  
  10. };  
  11.   
  12. struct __declspec(uuid("{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}")) ISimpleMsgBox;  

 

         有__declspec的一行將一個GUID賦值給ISimpleMsgBox,并且以后可以用__uuidof操作符來獲取GUID。這兩個東西都是微軟的C++的擴展。 DoSimpleMsgBox()的第二個參數是BSTR類型。意思是二進制串——即定長序列位的COM表示。BSTRs主要用于Visual Basic 和 Windows Scripting Host之類的腳本客戶端。接下來這個接口由CSimpleMsgBoxImpl C++類來實現。其定義如下:

  1. class CSimpleMsgBoxImpl : public ISimpleMsgBox    
  2. {  
  3. public:  
  4.     CSimpleMsgBoxImpl();  
  5.     virtual ~CSimpleMsgBoxImpl();  
  6.   
  7.     // IUnknown 方法  
  8.     ULONG AddRef();  
  9.     ULONG Release();  
  10.     HRESULT QueryInterface( REFIID riid, void** ppv );  
  11.   
  12.     // ISimpleMsgBox 方法  
  13.     HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );  
  14.   
  15. protected:  
  16.     ULONG m_uRefCount;  
  17. };  
  18.   
  19. class  __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;  

 

當某一客戶端想要創(chuàng)建一個SimpleMsgBox COM對象時,它應該用下面這樣的代碼:

  1. ISimpleMsgBox* pIMsgBox;  
  2. HRESULT hr;  
  3.   
  4. // 組件對象類的CLSID   
  5. hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),  
  6.        NULL,     // 非聚合  
  7.        CLSCTX_INPROC_SERVER, // 進程內服務器  
  8.      __uuidof(ISimpleMsgBox), // 所請求接口的IID   
  9.     (void**) &pIMsgBox );         // 返回的接口指針的地址  

 

類工廠實現

      我們的類工廠SimpleMsgBox是在一個叫做CSimpleMsgBoxClassFactory的C++類中實現的:

  1. class CSimpleMsgBoxClassFactory : public IClassFactory  
  2. {  
  3. public:  
  4.     CSimpleMsgBoxClassFactory();  
  5.     virtual ~CSimpleMsgBoxClassFactory();  
  6.   
  7.     // IUnknown方法  
  8.     ULONG AddRef();  
  9.     ULONG Release();  
  10.     HRESULT QueryInterface( REFIID riid, void** ppv );  
  11.   
  12.     // IClassFactory方法  
  13.     HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppv );  
  14.     HRESULT LockServer( BOOL fLock );  
  15.   
  16. protected:  
  17.     ULONG m_uRefCount;  
  18. };  

 

構造函數、析構函數和IUnknown方法都和前面例子中的一樣,不同的只有IClassFactory的方法,LockServer(),看起來相當更簡單:

  1. HRESULT CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock )  
  2. {  
  3.     fLock ? g_uDllLockCount++ : g_uDllLockCount--;  
  4.     return S_OK;  
  5. }  

 

CreateInstance()是重點。我們說過這個方法負責創(chuàng)建新的CSimpleMsgBoxImpl對象。讓我們進一步探討一下它的原型和參數:

  1. HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,  
  2.                                                     REFIID    riid,  
  3.                                                     void**    ppv );  

 

      第一個參數pUnkOuter只用于聚合的新對象,指向“外部的”COM對象,也就是說,這個“外部”對象將包含此新對象。對象的聚合超出了本文的討論范圍,本文的例子對象也不支持聚合。riid 和 ppv 與在 QueryInterface() 中的用法一樣——它們是客戶端所請求的接口IID和存儲接口指針的指針緩沖。

     下面是CreateInstance()的實現。它從參數的有效性檢查和參數的初始化開始。

  1. HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,  
  2.                                                     REFIID    riid,  
  3.                                                     void**    ppv )  
  4. {  
  5.     // 因為不支持聚合,所以這個參數pUnkOuter必須為NULL.  
  6.     if ( NULL != pUnkOuter )  
  7.         return CLASS_E_NOAGGREGATION;  
  8.   
  9.     //檢查指針ppv是不是void*類型  
  10.     if ( IsBadWritePtr ( ppv, sizeof(void*) ))  
  11.         return E_POINTER;  
  12.   
  13.     *ppv = NULL;  

 

檢查完參數的有效性后,就可以創(chuàng)建一個新的對象了。

  1. CSimpleMsgBoxImpl* pMsgbox;  
  2.   
  3.     // 創(chuàng)建一個新的COM對象  
  4.     pMsgbox = new CSimpleMsgBoxImpl;  
  5.   
  6.     if ( NULL == pMsgbox )  
  7.         return E_OUTOFMEMORY;  

 

       最后,用QI()來查詢客戶端所請求的新對象的接口。如果QI()失敗,則這個對象不可用,必須刪除它。

  1. HRESULT hrRet;  
  2.   
  3.     // 用QI查詢客戶端所請求的對象接口  
  4.     hrRet = pMsgbox->QueryInterface ( riid, ppv );  
  5.   
  6.     // 如果QI失敗,則刪除這個COM對象,因為客戶端不能使用它(客戶端沒有  
  7.     //這個對象的任何接口)  
  8.     if ( FAILED(hrRet) )  
  9.         delete pMsgbox;  
  10.   
  11.     return hrRet;  
  12. }  

 

深入DllGetClassObject()

      現在讓我們深入DllGetClassObject()內部。它的原型是:

  1. HRESULT DllGetClassObject( REFCLSID rclsid, REFIID riid, void** ppv );  

 

       rclsid是客戶端所請求的組件對象類的CLSID。這個函數必須返回指定組件對象類的類工廠。這里的兩個參數: riid 和 ppv 類似QI()的參數。不過在這個函數中,riid指的是COM庫所請求的類工廠接口的IID。通常就是IID_IClassFactory。

因為DllGetClassObject()也創(chuàng)建一個新的COM對象(類工廠),所以代碼與IClassFactory::CreateInstance()十分相似。開始也是進行一些有效性檢查以及初始化。

  1. HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv )  
  2. {  
  3.     // 檢查客戶端所要的CSimpleMsgBoxImpl類工廠  
  4.     if ( !InlineIsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) ))  
  5.         return CLASS_E_CLASSNOTAVAILABLE;  
  6.   
  7.     //檢查指針ppv是不是void*類型  
  8.     if ( IsBadWritePtr ( ppv, sizeof(void*) ))  
  9.         return E_POINTER;  
  10.   
  11.     *ppv = NULL;  

 

      第一個if語句檢查rclsid參數。我們的服務器只有一個組件對象類,所以rclsid必須是CSimpleMsgBoxImpl類的CLSID。__uuidof操作符獲取先前在__declspec(uuid())聲明中指定的CsimpleMsgBoxImpl類的GUID。下一步是創(chuàng)建一個類工廠對象。

  1. CSimpleMsgBoxClassFactory* pFactory;  
  2.   
  3.     // 構造一個新的類工廠對象  
  4.     pFactory = new CSimpleMsgBoxClassFactory;  
  5.   
  6.     if ( NULL == pFactory )  
  7.         return E_OUTOFMEMORY;  

 

     這里的處理與CreateInstance()中所做的有所不同。在CreateInstance()中是調用了QI(),并且如果調用失敗,則刪除COM對象。我們可以把自己假設成一個所創(chuàng)建的COM對象的客戶端,調用AddRef()進行一次引用計數(COUNT = 1)。然后調用QI()。如果QI()調用成功,它將再一次用AddRef()進行引用計數(COUNT = 2)。如果QI()調用失敗。引用計數將保持為原來的值(COUNT = 1)。在QI()調用之后,類工廠對象就使用完了,因此要調用Release()來釋放它。如果QI()調用失敗,這個對象將自我刪除(因為引用計數將為零),所以最終結果是一樣的。

  1. // 調用AddRef()增加一個類工廠引用計數,因為我們正在使用它  
  2. pFactory->AddRef();  
  3.   
  4. HRESULT hrRet;  
  5.   
  6.     // 調用QI()查詢客戶端所要的類工廠接口  
  7.     hrRet = pFactory->QueryInterface ( riid, ppv );  
  8.       
  9.     // 使用完類工廠后調用Release()釋放它  
  10.     pFactory->Release();  
  11.   
  12.     return hrRet;  
  13. }  

 

再談QueryInterface()

       前面討論過QI()的實現,但還是有必要再看一看類工廠的QI(),因為它是一個很現實的例子,其中COM對象實現的不光是IUnknown。首先進行的是對ppv緩沖的有效性檢查以及初始化。

  1. HRESULT CSimpleMsgBoxClassFactory::QueryInterface( REFIID riid, void** ppv )  
  2. {  
  3. HRESULT hrRet = S_OK;  
  4.   
  5.     //檢查指針ppv是不是void*類型  
  6.     if ( IsBadWritePtr ( ppv, sizeof(void*) ))  
  7.         return E_POINTER;  
  8.   
  9.     //標準的QI初始化,將賦值為NULL.  
  10.     *ppv = NULL;  

 

接下來檢查riid,看看它是不是類工廠實現的接口之一:IUnknown 或 IclassFactory。

  1. // 如果客戶端請求一個有效接口,則扶植給 *ppv.  
  2. if ( InlineIsEqualGUID ( riid, IID_IUnknown ))  
  3.     {  
  4.     *ppv = (IUnknown*) this;  
  5.     }  
  6. else if ( InlineIsEqualGUID ( riid, IID_IClassFactory ))  
  7.     {  
  8.     *ppv = (IClassFactory*) this;  
  9.     }  
  10. else  
  11.     {  
  12.     hrRet = E_NOINTERFACE;  
  13.     }  

 

最后,如果riid是有效接口,則調用接口的AddRef(),然后返回。

  1.     //如果返回有效接口指針,則調用AddRef()   
  2.     if ( S_OK == hrRet )  
  3.         {  
  4.         ((IUnknown*) *ppv)->AddRef();  
  5.         }  
  6.   
  7.     return hrRet;  
  8. }  

 

ISimpleMsgBox實現

      最后的也是必不可少的一關是ISimpleMsgBox實現,我們的代碼只實現ISimpleMsgBox的方法DoSimpleMsgBox()。首先用微軟的擴展類_bstr_t將bsMessageText轉換成TCHAR串。

  1. HRESULT CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )  
  2. {  
  3. _bstr_t bsMsg = bsMessageText;  
  4. LPCTSTR szMsg = (TCHAR*) bsMsg;   // 如果需要的話,用_bstr_t將串轉換為ANSI   

 

做完轉換的工作后,顯示信息框,然后返回。

  1.     MessageBox ( hwndParent, szMsg, _T("Simple Message Box"), MB_OK );  
  2.     return S_OK;  
  3. }  

 

使用服務器的客戶端

         我們已經完成了一個超級棒的COM服務器,如何使用它呢? 我們的接口一個定制接口,也就是說它只能被C或C++客戶端使用。(如果在組件對象類中同時實現IDispatch接口,那我們幾乎就可以在任何客戶端環(huán)境中——Visual Basic,Windows Scripting Host,Web頁面,PerlScript等使用COM對象。有關這方面的內容我們留待另外的文章討論)。本文提供了一個使用ISimpleMsgBox的例子程序。這個程序基于用Win32應用程序向導建立的Hello World例子。文件菜單包含兩個測試服務器的命令:如圖所示:

 

Test MsgBox COM Server菜單命令創(chuàng)建CSimpleMsgBoxImpl對象并調用DoSimpleMsgBox()。因為這是個簡單的方法,要寫的代碼不長。 我們先用CoCreateInstance()創(chuàng)建一個COM對象。

  1. void DoMsgBoxTest(HWND hMainWnd)  
  2. {  
  3.     ISimpleMsgBox* pIMsgBox;  
  4.     HRESULT hr;  
  5.   
  6.     hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),  // 組件對象類的CLSID  
  7.                             NULL,                // 非聚合  
  8.                             CLSCTX_INPROC_SERVER,  // 只使用進程內服務器  
  9.                             __uuidof(ISimpleMsgBox), // 所請求接口的IID   
  10.                            (void**) &pIMsgBox );   // 容納接口指針的緩沖  
  11.   
  12.     if ( FAILED(hr) )  
  13.         return;  
  14.   
  15.     // 然后調用DoSimpleMsgBox()方法并釋放接口。  
  16.     pIMsgBox->DoSimpleMsgBox ( hMainWnd, _bstr_t("Hello COM!") );  
  17.     pIMsgBox->Release();  
  18. }  

 

      就這么簡單。代碼中從頭到尾都有TRACE語句,這樣在調試器中運行測試程序就可以看到服務器的每一個方法是如何被調用的。另外一個菜單命令是調用CoFreeUnusedLibraries()函數,從中你能看到服務器DllCanUnloadNow()函數的運行。

其它細節(jié)——-COM宏

       COM代碼中有些宏隱藏了實現細節(jié),并允許在C和C++客戶端使用相同的聲明。本文中沒有使用宏,但在例子代碼中用到了這些宏,所以必須掌握它們的用法。下面是ISimpleMsgBox的聲明:

  1. struct ISimpleMsgBox : public IUnknown  
  2. {  
  3.     // IUnknown 方法  
  4.     STDMETHOD_(ULONG, AddRef)() PURE;  
  5.     STDMETHOD_(ULONG, Release)() PURE;  
  6.     STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE;  
  7.   
  8.     // ISimpleMsgBox 方法  
  9.     STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText) PURE;  
  10. };  

 

      STDMETHOD()包含 virtual 關鍵字,返回類型和調用規(guī)范。STDMETHOD_() 也一樣,除非你指定不同的返回類型。PURE 擴展了C++的“=0”,使此函數成為一個純虛擬函數。 STDMETHOD()和STDMETHOD_()有對應的宏用于方法實現——STDMETHODIMP和STDMETHODIMP_()。例如DoSimpleMsgBox()的實現:

  1. STDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )  
  2. {  
  3.   ...  
  4. }  

 

最后,標準的輸出函數用STDAPI宏聲明,如:

1.STDAPIDllRegisterServer()

       STDAPI包括返回類型和調用規(guī)范。要注意STDAPI不能和__declspec(dllexport)一起使用,因為STDAPI的擴展。輸出必須使用.DEF文件。

服務器注冊以及反注冊

      前面講過服務器實現了DllRegisterServer()和DllUnregisterServer()兩個函數。它們的工作是創(chuàng)建和刪除關于COM服務器的注冊表入口。其代碼都是對注冊表的處理,所以在此不必贅言,只是列出DllRegisterServer()創(chuàng)建的注冊表入口:鍵名 鍵值

  1. HKEY_CLASSES_ROOT   
  2.   
  3. CLSID   
  4.   
  5. {7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}   
  6. Default="SimpleMsgBox class"   
  7. InProcServer32   
  8. Default=[path to DLL]; ThreadingModel="Apartment"   

 

關于例子代碼的注釋

       本文的例子代碼在一個WORKSPACE(工作間)文件中(SimpleComSvr.dsw)同時包含了服務器的源代碼和測試服務器所用的客戶端源代碼。在VC的IDE環(huán)境中可以同時加載它們進行處理。在工作間的同級層次有兩個工程都要用到的頭文件,但每個工程都有自己的子目錄。同級的公共頭文件是:

1.ISimpleMsgBox.h——定義ISimpleMsgBox的頭文件。

     SimpleMsgBoxComDef.h——包含__declspec(uuid())的聲明。這些聲明都在單獨的文件中,因為客戶端需要CSimpleMsgBoxImpl的GUID,不是它的定義。將GUID移到單獨的文件中,使客戶端在存取GUID時不依賴 CSimpleMsgBoxImpl的內部結構。它是接口,ISimpleMsgBox,對客戶端很重要。

正如前面所說的,必須用.DEF文件來從服務器輸出四個標準的輸出函數。下面是例子工程的.DEF文件:

  1. EXPORTS  
  2.     DllRegisterServer   PRIVATE  
  3.     DllUnregisterServer PRIVATE  
  4.     DllGetClassObject   PRIVATE  
  5.     DllCanUnloadNow     PRIVATE  

 

每一行都包含函數名和PRIVATE關鍵字。這個關鍵字的意思是:此函數是輸出函數,但不包含在輸入庫(import lib)中。也就是說客戶端不能直接從代碼中調用這個函數,即使是鏈接了輸入庫也不行。這個關鍵字時必須要用的,否則鏈接器會出錯。

在服務器中設置斷點鏈

      如果你想在服務器代碼中設置斷點,有兩種方法:第一種是將服務器工程(MsgBoxSvr)設置為活動工程,然后開始調試。MSVC將問你調試會話要運行的可執(zhí)行程序。輸入客戶端測試程序的全路徑,你必須事先建立好。第二種方法是將客戶端工程(TestClient)設置為活動工程,配置工程的從屬(dependencies)屬性,以便服務器工程從屬于客戶端工程。這樣如果你改變了服務器的代碼,那么在編譯客戶端工程時會自動重新編譯服務器工程代碼。最后還要做的是當你開始調試客戶端時必須告訴MSVC加載服務器符號(symbols)。下面是設置工程屬性的對話框:Project->Dependencies菜單

 

      為了加載服務器符號,打開TestClient的工程設置(Project->Settings菜單),選擇Debug標簽,并在Category組合框中選擇Additional DLLs。在列表框中單擊New一個入口,然后輸入服務器DLL的全路徑名。如下圖所示:

 

       這樣設置以后,根據實際源代碼的所在位置,DLL的路徑將會做自動調整。

posted on 2012-10-17 22:15 jackdong 閱讀(535) 評論(0)  編輯 收藏 引用 所屬分類: Windows編程
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美精品尤物在线| 日韩一级大片在线| 最新日韩在线视频| 在线观看精品| 亚洲高清中文字幕| 亚洲精品视频在线| 亚洲午夜视频在线| 欧美在线看片a免费观看| 欧美国产亚洲精品久久久8v| 久久裸体艺术| 欧美激情综合五月色丁香小说| 欧美日韩国产亚洲一区| 国产精品美女999| 国产一区二区中文| 亚洲激情成人| 亚洲欧美日韩天堂| 久久青青草原一区二区| 欧美一区91| 久久国产福利| 亚洲在线不卡| 麻豆成人在线播放| 亚洲精品午夜| 午夜视频在线观看一区二区三区| 久久一日本道色综合久久| 欧美日韩免费| 一区在线视频| 亚洲欧美日韩中文在线制服| 麻豆精品在线视频| 这里只有精品视频在线| 久久精品视频播放| 国产精品mm| 亚洲精品社区| 可以免费看不卡的av网站| 一区二区高清在线观看| 欧美 日韩 国产 一区| 国产日韩一区二区三区在线| 一区二区三区国产精华| 美女诱惑黄网站一区| 亚洲午夜激情| 欧美日韩成人在线观看| 亚洲国产精品精华液网站| 久久精品人人做人人爽| 一区二区三区高清在线| 欧美精品在线观看一区二区| 亚洲第一网站| 久久性天堂网| 欧美一区国产一区| 国产精品久久一区主播| 亚洲一区二区黄色| 亚洲国产另类久久精品| 欧美自拍偷拍| 国产亚洲欧美一区二区三区| 欧美一进一出视频| 亚洲一区二区黄| 国产精品九九久久久久久久| 中文亚洲欧美| 99re66热这里只有精品4| 欧美二区在线播放| 欧美一区二区三区播放老司机| 欧美视频二区| 亚洲欧美日韩一区在线| 亚洲午夜一区二区三区| 国产精品亚洲网站| 久久国产视频网| 欧美在线观看www| 狠狠色综合网站久久久久久久| 久久成人18免费网站| 欧美一区视频| 今天的高清视频免费播放成人 | 91久久精品日日躁夜夜躁欧美| 欧美主播一区二区三区美女 久久精品人 | 美日韩精品免费| 亚洲伊人伊色伊影伊综合网 | 久久久福利视频| 小处雏高清一区二区三区| 国产欧美一级| 老牛影视一区二区三区| 可以看av的网站久久看| 99www免费人成精品| 亚洲免费观看在线观看| 国产精品xxxav免费视频| 欧美影院成人| 狂野欧美一区| 亚洲一区二区三区免费视频| 欧美亚洲午夜视频在线观看| 1024精品一区二区三区| 日韩视频在线一区| 国产一区二区三区av电影| 欧美电影打屁股sp| 欧美视频在线观看免费| 久久久久久久久久久一区 | 国产女主播一区二区| 老色批av在线精品| 欧美图区在线视频| 美女视频黄a大片欧美| 欧美人妖在线观看| 久久综合色婷婷| 国产精品a级| 欧美激情小视频| 国产精品一区免费在线观看| 亚洲第一视频| 国产私拍一区| 亚洲精品女av网站| 国产精品一区二区三区久久久 | 亚洲人成在线播放网站岛国| 国产精品爽黄69| 91久久国产综合久久蜜月精品| 国产精品色一区二区三区| 亚洲国产精品电影在线观看| 国产精品入口福利| 亚洲日本视频| 亚洲第一页在线| 性欧美在线看片a免费观看| 亚洲少妇诱惑| 欧美激情亚洲激情| 欧美插天视频在线播放| 国产在线视频欧美一区二区三区| 亚洲天堂激情| 亚洲精品一区二区三区不| 久久久久看片| 久久久噜噜噜久久中文字免| 国产人成精品一区二区三| 亚洲深夜福利| 亚洲欧美日韩高清| 国产精品国产亚洲精品看不卡15 | 欧美精品一区二| 免费成人激情视频| 欧美日韩国产精品| 久久夜色精品一区| 亚洲欧美在线观看| 欧美日韩精品综合| 亚洲国产精品免费| 黄色av日韩| 欧美一级大片在线观看| 亚洲一区中文| 欧美午夜视频在线| 一本色道久久88综合亚洲精品ⅰ | 亚洲欧美视频在线| 欧美日韩一卡二卡| 一区二区三区回区在观看免费视频| 日韩视频一区二区三区| 欧美成年人视频网站| 亚洲成在人线av| 亚洲欧洲美洲综合色网| 欧美国产91| 亚洲最黄网站| 午夜影院日韩| 国产欧美日韩视频| 欧美在线免费观看| 美女日韩在线中文字幕| 亚洲精品一区二区三区99| 欧美日韩一区二区三区在线看| 国产精品99久久99久久久二8| 欧美一级精品大片| 亚洲第一网站| 欧美午夜精品久久久久久超碰| 亚洲欧美日韩爽爽影院| 欧美不卡三区| 亚洲专区一区二区三区| 狠狠色丁香婷婷综合影院| 欧美精品久久99| 午夜性色一区二区三区免费视频| 国产精品扒开腿做爽爽爽软件| 亚洲天堂av在线免费| 久久亚洲综合色一区二区三区| 亚洲欧洲一区二区三区| 国产精品久久久久久久久久久久| 欧美一区二区三区喷汁尤物| 亚洲狠狠婷婷| 久久国产精彩视频| 99精品欧美一区二区蜜桃免费| 国产午夜精品在线| 欧美成人四级电影| 亚洲自拍16p| 亚洲国产精品高清久久久| 香蕉久久夜色精品| 91久久夜色精品国产网站| 国产精品亚洲综合久久| 欧美成人69av| 欧美在线二区| 亚洲视频一区在线| 亚洲高清av| 久久精品国产清高在天天线| 中文在线资源观看网站视频免费不卡 | 亚洲欧美日韩精品综合在线观看| 欧美高清免费| 欧美影院视频| 一区二区三区产品免费精品久久75 | 欧美日本韩国在线| 久久狠狠婷婷| 一本色道久久88综合亚洲精品ⅰ| 美女成人午夜| 欧美一区二区视频在线观看| 一区二区激情| 亚洲人成绝费网站色www| 国模一区二区三区| 国产伦精品一区二区三区高清| 欧美日韩亚洲国产精品| 欧美韩日高清| 欧美国产成人精品|