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

C++ Coder

HCP高性能計算架構,實現,編譯器指令優化,算法優化, 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. 創建COM對象——類工廠
  10. 一個定制接口的例子
  11. 類工廠實現
  12. 深入DllGetClassObject()
  13. 再談QueryInterface()
  14. 使用服務器的客戶端
  15. 其它細節——-COM宏

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

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

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

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

 

走馬觀花看COM服務器

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

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

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

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

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

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

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

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

另外,只輸出正確的函數是不夠的——還必須遵循COM規范,這樣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. }  

 

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

實現接口,從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. }  

 

當創建新的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對象在堆中創建。如果你在全局粘上創建某個對象,當對象試圖刪除自己時就會出問題。

       現在應該明白了為什么在客戶端應用程序中正確調用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;  

 

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

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

深入CoCreateInstance()

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

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

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

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

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

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

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

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

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

COM服務器注冊

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

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

 

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

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

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

創建COM對象——類工廠

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

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

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

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

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

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

 

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

一個定制接口的例子

      這個工程是一個能運行的DLL服務器例子,對象由類工廠創建,此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;  

 

當某一客戶端想要創建一個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()是重點。我們說過這個方法負責創建新的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;  

 

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

  1. CSimpleMsgBoxImpl* pMsgbox;  
  2.   
  3.     // 創建一個新的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()也創建一個新的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。下一步是創建一個類工廠對象。

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

 

     這里的處理與CreateInstance()中所做的有所不同。在CreateInstance()中是調用了QI(),并且如果調用失敗,則刪除COM對象。我們可以把自己假設成一個所創建的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接口,那我們幾乎就可以在任何客戶端環境中——Visual Basic,Windows Scripting Host,Web頁面,PerlScript等使用COM對象。有關這方面的內容我們留待另外的文章討論)。本文提供了一個使用ISimpleMsgBox的例子程序。這個程序基于用Win32應用程序向導建立的Hello World例子。文件菜單包含兩個測試服務器的命令:如圖所示:

 

Test MsgBox COM Server菜單命令創建CSimpleMsgBoxImpl對象并調用DoSimpleMsgBox()。因為這是個簡單的方法,要寫的代碼不長。 我們先用CoCreateInstance()創建一個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()函數的運行。

其它細節——-COM宏

       COM代碼中有些宏隱藏了實現細節,并允許在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 關鍵字,返回類型和調用規范。STDMETHOD_() 也一樣,除非你指定不同的返回類型。PURE 擴展了C++的“=0”,使此函數成為一個純虛擬函數。 STDMETHOD()和STDMETHOD_()有對應的宏用于方法實現——STDMETHODIMP和STDMETHODIMP_()。例如DoSimpleMsgBox()的實現:

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

 

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

1.STDAPIDllRegisterServer()

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

服務器注冊以及反注冊

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

  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環境中可以同時加載它們進行處理。在工作間的同級層次有兩個工程都要用到的頭文件,但每個工程都有自己的子目錄。同級的公共頭文件是:

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

 

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

 

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

posted on 2012-10-17 22:15 jackdong 閱讀(528) 評論(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>
            在线成人av.com| 久久久久久久精| 欧美黄色免费| 亚洲成色999久久网站| 黄色成人在线网站| 久久国产精品72免费观看| 久久av免费一区| 欧美一二三区精品| 在线播放不卡| 精品不卡在线| 国产主播一区二区三区四区| 国产精品自拍网站| 国产精品中文在线| 国产女人18毛片水18精品| 国产精品免费视频观看| 国产精品久久久久永久免费观看 | 亚洲美女av在线播放| 久久午夜电影网| 国产偷久久久精品专区| 99在线精品视频| 免费在线欧美视频| 亚洲综合欧美| 亚洲午夜国产成人av电影男同| 亚洲第一精品电影| 国外精品视频| 亚洲国产日韩一级| 亚洲日本中文字幕| 亚洲欧美在线播放| 亚洲欧美视频在线观看视频| 在线一区二区三区四区五区| 久久精品国产亚洲aⅴ| 久久高清福利视频| 米奇777在线欧美播放| 欧美日韩免费观看一区二区三区| 欧美理论视频| 狠狠色丁香婷综合久久| 一区二区日韩精品| 欧美成人亚洲成人| 亚洲欧美视频在线观看| 裸体素人女欧美日韩| 国产精品a久久久久| 亚洲精品在线视频观看| 午夜在线a亚洲v天堂网2018| 麻豆freexxxx性91精品| 亚洲午夜在线| 欧美日韩另类在线| 亚洲狼人综合| 亚洲国产精品精华液2区45| 亚洲影院色在线观看免费| 麻豆久久精品| 在线亚洲欧美专区二区| 噜噜噜噜噜久久久久久91| 国产精品www.| 亚洲人成在线播放网站岛国| 欧美1区视频| 久久久久久久久岛国免费| 国产精品久久久久免费a∨大胸| 国产综合视频| 中文亚洲视频在线| 国产精品日韩精品| 亚洲小视频在线观看| 亚洲一区激情| 国产精品视频最多的网站| 亚欧成人精品| 久久影院午夜论| 在线欧美不卡| 亚洲人成在线观看| 国产精品二区在线观看| 久久黄金**| 欧美电影美腿模特1979在线看 | 韩国女主播一区| 久久精品动漫| 欧美成熟视频| 久久久国产成人精品| 另类av一区二区| 中文高清一区| 午夜一区二区三区在线观看| 亚洲欧洲在线一区| 欧美一区二区三区精品| 一本色道久久综合亚洲精品按摩| 亚洲一区免费| 亚洲欧美一区二区激情| 欧美国产日韩xxxxx| 国产精品一区二区女厕厕| 你懂的视频一区二区| 国产精品亚洲综合| 亚洲黄色小视频| 黄色影院成人| 欧美一区二区在线免费观看| 亚洲校园激情| 欧美日韩高清在线一区| 99综合精品| 在线午夜精品| 欧美乱在线观看| 一区二区精品国产| 亚洲欧美日韩精品久久久久| 欧美日韩国产另类不卡| 免费成人性网站| 在线观看欧美视频| 久久激五月天综合精品| 美女任你摸久久| a91a精品视频在线观看| 欧美日韩精品免费观看| 99精品国产高清一区二区| 99精品视频网| 国产精品久久久久一区二区三区| 香蕉久久一区二区不卡无毒影院| 亚洲女人小视频在线观看| 国产夜色精品一区二区av| 亚洲欧美日韩一区二区| 毛片一区二区| 亚洲自拍另类| 亚洲高清不卡av| 国产精品成人aaaaa网站| 亚洲欧美精品伊人久久| 亚洲精品乱码久久久久久| 亚洲视频网站在线观看| 亚洲激情黄色| 国内一区二区三区| 国产精品一级| 国产精品国产三级国产专播精品人| 亚洲综合视频一区| 亚洲成在线观看| 国产精品成人国产乱一区| 欧美超级免费视 在线| 欧美影视一区| 亚洲视频精品| 亚洲精品综合| 日韩午夜激情av| 欧美国产日本在线| 葵司免费一区二区三区四区五区| 欧美永久精品| 欧美亚洲在线| 亚洲午夜激情| 欧美一区二区三区播放老司机| 亚洲免费观看高清在线观看 | 国产美女精品在线| 国产精品国产自产拍高清av| 欧美日韩国产一区二区三区| 欧美成年人网| 欧美日韩一区在线| 国产午夜亚洲精品羞羞网站| 欧美亚洲成人免费| 国产精品海角社区在线观看| 国产农村妇女精品一区二区 | 一区二区欧美视频| 亚洲一二三区在线观看| 亚洲欧美电影在线观看| 性欧美暴力猛交69hd| 欧美一区二区在线| 欧美制服丝袜第一页| 久久综合伊人77777尤物| 欧美人在线观看| 国产精品二区在线| 亚洲第一中文字幕| 亚洲美女91| 久久免费99精品久久久久久| 久久亚洲私人国产精品va| 久久久久久自在自线| 亚洲国产91精品在线观看| 亚洲视频在线一区| 免费亚洲电影在线观看| 亚洲视频中文| 亚洲综合国产| 在线视频欧美日韩精品| 亚洲精品美女免费| 老司机免费视频久久| 亚洲尤物在线| 国产精品夜夜夜一区二区三区尤| 日韩一区二区免费看| 亚洲福利视频一区二区| 美女视频网站黄色亚洲| 国内成+人亚洲| 欧美一区二区免费视频| 亚洲一二三区在线观看| 国产精品永久| 久热国产精品视频| 欧美成人精品三级在线观看 | 欧美成人精品在线| 亚洲激情图片小说视频| 亚洲黄色三级| 欧美日韩高清免费| 久久国产精品久久国产精品| 欧美在线综合| 久久成人18免费网站| 久久国产色av| 亚洲在线不卡| 欧美三级乱码| 一本色道久久88综合亚洲精品ⅰ| 91久久黄色| 欧美日本精品一区二区三区| 亚洲国产成人久久综合一区| 在线精品国产成人综合| 欧美国产第一页| 在线视频精品一区| 亚洲一区精品视频| 国产色婷婷国产综合在线理论片a| 欧美成人激情视频| 国产亚洲高清视频| 一区二区动漫|