欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
COM編程入門(mén)(二)

COM編程入門(mén)
第二部分——深入COM服務(wù)器


編譯:趙湘寧



  本文為剛剛接觸COM的程序員提供編程指南,解釋COM服務(wù)器內幕以及如何用C++編寫(xiě)自己的接口。繼上一篇COM編程入門(mén)之后,本文將討論有關(guān)COM服務(wù)器的內容,解釋編寫(xiě)自己的COM接口和COM服務(wù)器所需要的步驟和知識,以及詳細討論當COM庫對COM服務(wù)器進(jìn)行調用時(shí),COM服務(wù)器運行的內部機制。
  如果你讀過(guò)上一篇文章。應該很熟悉COM客戶(hù)端是怎么會(huì )事了。本文將討論COM的另一端——COM服務(wù)器。內容包括如何用C++編寫(xiě)一個(gè)簡(jiǎn)單的不涉及類(lèi)庫的COM服務(wù)器。深入到創(chuàng )建COM服務(wù)器的內部過(guò)程,毫無(wú)遮掩地研究那些庫代碼是充分理解COM服務(wù)器內部機制的最好方法。
本文假設你精通C++并掌握了上一篇文章所討論的概念和術(shù)語(yǔ)。在這一部分將包括如下內容:

走馬觀(guān)花看COM服務(wù)器

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

  1. 必須正確在注冊表的HKEY_CLASSES_ROOT\CLSID 鍵值下注冊;
  2. 必須輸出DllGetClassObject()函數;

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

還有三個(gè)函數通常也要輸出:

  • DllCanUnloadNow():由COM庫調用來(lái)檢查是否服務(wù)器被從內存中卸載;
  • DllRegisterServer():由類(lèi)似RegSvr32的安裝實(shí)用程序調用來(lái)注冊服務(wù)器;
  • DllUnregisterServer():由卸載實(shí)用程序調用來(lái)刪除由DllRegisterServer()創(chuàng )建的注冊表入口;

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

服務(wù)器生命其管理

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

HRESULT DllCanUnloadNow();

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

extern UINT g_uDllRefCount;  // 服務(wù)器的引用計數HRESULT DllCanUnloadNow(){    return (g_uDllRefCount > 0) ? S_FALSE : S_OK;}
  如何處理引用計數將在下一節涉及到具體代碼時(shí)討論。

實(shí)現接口,從IUnknown開(kāi)始

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

class CUnknownImpl : public IUnknown{public:    // 構造函數和析構器    CUnknownImpl();    virtual ~CUnknownImpl();    // IUnknown 方法    ULONG AddRef();    ULONG Release)();    HRESULT QueryInterface( REFIID riid, void** ppv );protected:    UINT m_uRefCount;  // 對象的引用計數};
構造器和析構器

構造器和析構器管理服務(wù)器的引用計數:

CUnknownImpl::CUnknownImpl(){    m_uRefCount = 0;    g_uDllRefCount++;}CUnknownImpl::~CUnknownImpl(){    g_uDllRefCount--;}
  當創(chuàng )建新的COM對象時(shí),構造器被調用,它增加服務(wù)器的引用計數以保持這個(gè)服務(wù)器駐留內存。同時(shí)它還將對象的引用計數初始化為零。當這個(gè)COM對象被摧毀時(shí),它減少服務(wù)器的引用計數。

AddRef() 和 Release()

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

ULONG CUnknownImpl::AddRef(){    return ++m_uRefCount;}
AddRef()只增加對象的引用計數并返回更新的計數。 Release()更簡(jiǎn)單:
ULONG CUnknownImpl::Release(){ULONG uRet = --m_uRefCount;    if ( 0 == m_uRefCount )  // 是否釋放了最后的引用?        delete this;    return uRet;}
  除了減少對象的引用計數外,如果沒(méi)有另外的明確引用,Release()將摧毀對象。Release()也返回更新的引用計數。注意Release()的實(shí)現假設COM對象在堆中創(chuàng )建。如果你在全局粘上創(chuàng )建某個(gè)對象,當對象試圖刪除自己時(shí)就會(huì )出問(wèn)題。
  現在應該明白了為什么在客戶(hù)端應用程序中正確調用AddRef()和 Release()是如此重要!如果在這了做得不對,你使用的對象會(huì )被很快摧毀,這樣的話(huà)在整個(gè)服務(wù)器中內存會(huì )很快溢出導致應用程序下次存取服務(wù)器代碼時(shí)崩潰。
  如果你編寫(xiě)多線(xiàn)程應用,可能會(huì )想到使用++&替代InterlockedIncrement()和InterlockedDecrement()的線(xiàn)程安全問(wèn)題。++&——用于單線(xiàn)程服務(wù)器很保險,因為即使客戶(hù)端應用是多線(xiàn)程的并從不同的線(xiàn)程中進(jìn)行方法調用,COM庫都會(huì )按順序進(jìn)行服務(wù)器的方法調用。也就是說(shuō),一旦一個(gè)方法調用開(kāi)始,所有其它試圖調用方法的線(xiàn)程都將阻塞,直到第一個(gè)方法返回。COM庫本身確保服務(wù)器一次不會(huì )被一個(gè)以上的線(xiàn)程闖入。

QueryInterface()

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

HRESULT CUnknownImpl::QueryInterface ( REFIID riid, void** ppv ){HRESULT hrRet = S_OK;    // 標準QI()初始化 – 置 *ppv 為 NULL.    *ppv = NULL;    // 如果客戶(hù)端請求提供的接口,給 *ppv.賦值    if ( IsEqualIID ( riid, IID_IUnknown ))        {        *ppv = (IUnknown*) this;        }    else        {        // 不提供客戶(hù)端請求的接口         hrRet = E_NOINTERFACE;        }    // 如果返回一個(gè)接口指針。 調用AddRef()增加引用計數.    if ( S_OK == hrRet )        {        ((IUnknown*) *ppv)->AddRef();        }    return hrRet;}
在QI()中做了三件不同的事情:
  1. 初始化傳入的指針為NULL;
    [*ppv = NULL;]		
  2. 檢查riid,確定組件對象類(lèi)(coclass)實(shí)現了客戶(hù)端所請求接口;
    [if ( IsEqualIID ( riid, IID_IUnknown ))];
  3. 如果確實(shí)實(shí)現勒索請求的接口,則增加COM對象的引用計數。
    [((IUnknown*) *ppv)->AddRef();]AddRef()調用很關(guān)鍵。    *ppv = (IUnknown*) this;
  要創(chuàng )建新的COM對象引用,就必須調用這個(gè)函數通知COM對象這個(gè)新引用成立。在A(yíng)ddRef()調用中的強制轉換IUnknown*看起來(lái)好像多余,但是在QI()中初始化的*ppv有可能不是IUnknown*類(lèi)型,所以最好是養成習慣對之進(jìn)行強行轉換。。
  上面我們已經(jīng)討論了一些DLL服務(wù)器的內部細節,接下來(lái)讓我們回頭看一看當客戶(hù)端調用CoCreateInstance()時(shí)是如何處理服務(wù)器的。

深入CoCreateInstance()

  在本文的第一部分中,我們見(jiàn)過(guò)CoCreateInstance()API,其作用是當客戶(hù)端請求對象時(shí),用它來(lái)創(chuàng )建對象。從客戶(hù)端的立場(chǎng)看,它是一個(gè)黑盒子。只要用正確的參數調用它即可得到一個(gè)COM對象。它并沒(méi)有什么魔法,只是在一個(gè)定義良好的過(guò)程中加載COM服務(wù)器,創(chuàng )建請求的COM對象并返回所要的指針。就這些。
  下面讓我們來(lái)瀏覽一下這個(gè)過(guò)程。這里要涉及到幾個(gè)不太熟悉的術(shù)語(yǔ),但不用著(zhù)急,后面會(huì )對它們作詳細討論。

  1. 客戶(hù)端程序調用CoCreateInstance(),傳遞組件對象類(lèi)的CLSID以及所要接口的IID;
  2. COM庫在HKEY_CLASSES_ROOT\CLSID.鍵值下查找服務(wù)器的CLSID鍵值,這個(gè)鍵值包含服務(wù)器的注冊信息;
  3. COM庫讀取服務(wù)器DLL的全路徑并將DLL加載到客戶(hù)端的進(jìn)程空間;
  4. COM庫調用在服務(wù)器中DllGetClassObject()函數為所請求的組件對象類(lèi)請求類(lèi)工廠(chǎng);
  5. 服務(wù)器創(chuàng )建一個(gè)類(lèi)工廠(chǎng)并將它從DllGetClassObject()返回;
  6. COM庫在類(lèi)工廠(chǎng)中調用CreateInstance()方法創(chuàng )建客戶(hù)端程序請求的COM對象;
  7. CreateInstance()返回一個(gè)接口指針到客戶(hù)端程序;

COM服務(wù)器注冊

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

{067DF822-EAB6-11cf-B56E-00A0244D5087}
大括弧和連字符是必不可少的,字母大小寫(xiě)均可。

  這個(gè)鍵的默認值是人可值別的組件對象類(lèi)名,使用VC所帶的OLE/COM對象瀏覽器可以察看到它們。在GUID鍵的子鍵中還可以存儲其它信息。需要創(chuàng )建什么子鍵依賴(lài)于COM服務(wù)器的類(lèi)型以及COM服務(wù)器的使用方法。對于本文例子中這個(gè)簡(jiǎn)單的進(jìn)程內服務(wù)器,我們值需要一個(gè)子鍵:InProcServer32。
  InProcServer32鍵包含兩個(gè)串:這兩個(gè)串的缺省值是服務(wù)器DLL的全路徑和線(xiàn)程模型值(ThreadingModel)。線(xiàn)程模型超出了本文所涉及的范圍,我們先接受這個(gè)概念,這里我們指的是單線(xiàn)程服務(wù)器,用的模式為Apartment(即單線(xiàn)程公寓)。

創(chuàng )建COM對象——類(lèi)工廠(chǎng)

  回首看一看客戶(hù)端的COM,它是如何以自己獨立于語(yǔ)言的方式創(chuàng )建和銷(xiāo)毀COM對象??蛻?hù)端調用CoCreateInstance()創(chuàng )建新的COM對象?,F在我們來(lái)看看它在服務(wù)器端是如何工作的。
  你每次實(shí)現組件對象類(lèi)的時(shí)候,都要寫(xiě)一個(gè)旁類(lèi)負責創(chuàng )建第一個(gè)組件對象類(lèi)的實(shí)例。這個(gè)旁類(lèi)就叫這個(gè)組件對象類(lèi)的類(lèi)工廠(chǎng)(class factory),其唯一目的是創(chuàng )建COM對象。之所以要一個(gè)類(lèi)工廠(chǎng),是因為語(yǔ)言無(wú)關(guān)的緣故。COM本身并不創(chuàng )建對象,因為它不是獨立于語(yǔ)言的也不是獨立于實(shí)現的。
  當某個(gè)客戶(hù)端想要創(chuàng )建一個(gè)COM對象時(shí),COM庫就從COM服務(wù)器請求類(lèi)工廠(chǎng)。然后類(lèi)工廠(chǎng)創(chuàng )建COM對象并將它返回客戶(hù)端。它們的通訊機制由函數DllGetClassObject()來(lái)提供。
  術(shù)語(yǔ) “類(lèi)工廠(chǎng)”和“類(lèi)對象”實(shí)際上是一回事。沒(méi)有那個(gè)單詞能精確描述類(lèi)工廠(chǎng)的作用和義,但正是這個(gè)工廠(chǎng)創(chuàng )建了COM對象,而不是COM類(lèi)所為。將“類(lèi)工廠(chǎng)”理解成“對象工廠(chǎng)”可能會(huì )更有助于理解(實(shí)際上MFC就是這樣理解的——它的類(lèi)工廠(chǎng)實(shí)現就叫做COleObjectFactory)。但“類(lèi)工廠(chǎng)”是正式術(shù)語(yǔ),所以本文也這樣用。
  當COM庫調用DllGetClassObject()時(shí),它傳遞客戶(hù)端請求的CLSID。服務(wù)器負責為所請求的CLSID創(chuàng )建者各類(lèi)工廠(chǎng)并將它返回。類(lèi)工廠(chǎng)本身就是一個(gè)組件對象類(lèi),并且實(shí)現IClassFactory接口。如果DllGetClassObject()調用成功,它返回一個(gè)IClassFactory指針給COM庫,然后COM庫用IClassFactory接口方法創(chuàng )建客戶(hù)端所請求的COM對象實(shí)例。一下是IClassFactory接口:

struct IClassFactory : public IUnknown{    HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppvObject );    HRESULT LockServer( BOOL fLock );};
其中,CreateInstance()是創(chuàng )建COM對象的方法。LockServer()在必要時(shí)讓COM庫增加或減少服務(wù)器的引用計數。

一個(gè)定制接口的例子

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

接口定義

  我們的新接口是ISimpleMsgBox。所有的接口多必須從IUnknown派生。這個(gè)接口只有一個(gè)方法:DoSimpleMsgBox()。注意它返回標準類(lèi)型HRESULT。所有的方法都應該返回HRESULT類(lèi)型,并且所有返回到調用者的其它數據都應該通過(guò)指針參數操作。
struct ISimpleMsgBox : public IUnknown{    // IUnknown 方法    ULONG AddRef();    ULONG Release();    HRESULT QueryInterface( REFIID riid, void** ppv );    // ISimpleMsgBox方法    HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );};struct __declspec(uuid("{7D51904D-1645-4a8c-BDE0-0F4A44FC38C4}")) ISimpleMsgBox;
  有__declspec的一行將一個(gè)GUID賦值給ISimpleMsgBox,并且以后可以用__uuidof操作符來(lái)獲取GUID。這兩個(gè)東西都是微軟的C++的擴展。 DoSimpleMsgBox()的第二個(gè)參數是BSTR類(lèi)型。意思是二進(jìn)制串——即定長(cháng)序列位的COM表示。BSTRs主要用于Visual Basic 和 Windows Scripting Host之類(lèi)的腳本客戶(hù)端。接下來(lái)這個(gè)接口由CSimpleMsgBoxImpl C++類(lèi)來(lái)實(shí)現。其定義如下:
class CSimpleMsgBoxImpl : public ISimpleMsgBox  {public:	CSimpleMsgBoxImpl();	virtual ~CSimpleMsgBoxImpl();    // IUnknown 方法    ULONG AddRef();    ULONG Release();    HRESULT QueryInterface( REFIID riid, void** ppv );    // ISimpleMsgBox 方法    HRESULT DoSimpleMsgBox( HWND hwndParent, BSTR bsMessageText );protected:    ULONG m_uRefCount;};class  __declspec(uuid("{7D51904E-1645-4a8c-BDE0-0F4A44FC38C4}")) CSimpleMsgBoxImpl;
當某一客戶(hù)端想要創(chuàng )建一個(gè)SimpleMsgBox COM對象時(shí),它應該用下面這樣的代碼:
ISimpleMsgBox* pIMsgBox;HRESULT hr;// 組件對象類(lèi)的CLSID hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),       NULL,     // 非聚合       CLSCTX_INPROC_SERVER, // 進(jìn)程內服務(wù)器     __uuidof(ISimpleMsgBox), // 所請求接口的IID     (void**) &pIMsgBox );         // 返回的接口指針的地址
類(lèi)工廠(chǎng)實(shí)現

我們的類(lèi)工廠(chǎng)SimpleMsgBox是在一個(gè)叫做CSimpleMsgBoxClassFactory的C++類(lèi)中實(shí)現的:

class CSimpleMsgBoxClassFactory : public IClassFactory{public:    CSimpleMsgBoxClassFactory();    virtual ~CSimpleMsgBoxClassFactory();    // IUnknown方法    ULONG AddRef();    ULONG Release();    HRESULT QueryInterface( REFIID riid, void** ppv );    // IClassFactory方法    HRESULT CreateInstance( IUnknown* pUnkOuter, REFIID riid, void** ppv );    HRESULT LockServer( BOOL fLock );protected:    ULONG m_uRefCount;};
構造函數、析構函數和IUnknown方法都和前面例子中的一樣,不同的只有IClassFactory的方法,LockServer(),看起來(lái)相當更簡(jiǎn)單:
HRESULT CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock ){    fLock ? g_uDllLockCount++ : g_uDllLockCount--;    return S_OK;}
CreateInstance()是重點(diǎn)。我們說(shuō)過(guò)這個(gè)方法負責創(chuàng )建新的CSimpleMsgBoxImpl對象。讓我們進(jìn)一步探討一下它的原型和參數:
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,                                                    REFIID    riid,                                                    void**    ppv );
  第一個(gè)參數pUnkOuter只用于聚合的新對象,指向“外部的”COM對象,也就是說(shuō),這個(gè)“外部”對象將包含此新對象。對象的聚合超出了本文的討論范圍,本文的例子對象也不支持聚合。riid 和 ppv 與在 QueryInterface() 中的用法一樣——它們是客戶(hù)端所請求的接口IID和存儲接口指針的指針緩沖。

下面是CreateInstance()的實(shí)現。它從參數的有效性檢查和參數的初始化開(kāi)始。
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,                                                    REFIID    riid,                                                    void**    ppv ){    // 因為不支持聚合,所以這個(gè)參數pUnkOuter必須為NULL.    if ( NULL != pUnkOuter )        return CLASS_E_NOAGGREGATION;    //檢查指針ppv是不是void*類(lèi)型    if ( IsBadWritePtr ( ppv, sizeof(void*) ))        return E_POINTER;    *ppv = NULL;
檢查完參數的有效性后,就可以創(chuàng )建一個(gè)新的對象了。
CSimpleMsgBoxImpl* pMsgbox;    // 創(chuàng  )建一個(gè)新的COM對象    pMsgbox = new CSimpleMsgBoxImpl;    if ( NULL == pMsgbox )        return E_OUTOFMEMORY;
最后,用QI()來(lái)查詢(xún)客戶(hù)端所請求的新對象的接口。如果QI()失敗,則這個(gè)對象不可用,必須刪除它。
HRESULT hrRet;    // 用QI查詢(xún)客戶(hù)端所請求的對象接口    hrRet = pMsgbox->QueryInterface ( riid, ppv );    // 如果QI失敗,則刪除這個(gè)COM對象,因為客戶(hù)端不能使用它(客戶(hù)端沒(méi)有    //這個(gè)對象的任何接口)    if ( FAILED(hrRet) )        delete pMsgbox;    return hrRet;}
深入DllGetClassObject()

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

HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv );
  rclsid是客戶(hù)端所請求的組件對象類(lèi)的CLSID。這個(gè)函數必須返回指定組件對象類(lèi)的類(lèi)工廠(chǎng)。這里的兩個(gè)參數: riid 和 ppv 類(lèi)似QI()的參數。不過(guò)在這個(gè)函數中,riid指的是COM庫所請求的類(lèi)工廠(chǎng)接口的IID。通常就是IID_IClassFactory。
  因為DllGetClassObject()也創(chuàng )建一個(gè)新的COM對象(類(lèi)工廠(chǎng)),所以代碼與IClassFactory::CreateInstance()十分相似。開(kāi)始也是進(jìn)行一些有效性檢查以及初始化。
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv ){    // 檢查客戶(hù)端所要的CSimpleMsgBoxImpl類(lèi)工廠(chǎng)    if ( !InlineIsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) ))        return CLASS_E_CLASSNOTAVAILABLE;    //檢查指針ppv是不是void*類(lèi)型    if ( IsBadWritePtr ( ppv, sizeof(void*) ))        return E_POINTER;    *ppv = NULL;
  第一個(gè)if語(yǔ)句檢查rclsid參數。我們的服務(wù)器只有一個(gè)組件對象類(lèi),所以rclsid必須是CSimpleMsgBoxImpl類(lèi)的CLSID。__uuidof操作符獲取先前在__declspec(uuid())聲明中指定的CsimpleMsgBoxImpl類(lèi)的GUID。下一步是創(chuàng )建一個(gè)類(lèi)工廠(chǎng)對象。
CSimpleMsgBoxClassFactory* pFactory;    // 構造一個(gè)新的類(lèi)工廠(chǎng)對象    pFactory = new CSimpleMsgBoxClassFactory;    if ( NULL == pFactory )        return E_OUTOFMEMORY;
  這里的處理與CreateInstance()中所做的有所不同。在CreateInstance()中是調用了QI(),并且如果調用失敗,則刪除COM對象。我們可以把自己假設成一個(gè)所創(chuàng )建的COM對象的客戶(hù)端,調用AddRef()進(jìn)行一次引用計數(COUNT = 1)。然后調用QI()。如果QI()調用成功,它將再一次用AddRef()進(jìn)行引用計數(COUNT = 2)。如果QI()調用失敗。引用計數將保持為原來(lái)的值(COUNT = 1)。在QI()調用之后,類(lèi)工廠(chǎng)對象就使用完了,因此要調用Release()來(lái)釋放它。如果QI()調用失敗,這個(gè)對象將自我刪除(因為引用計數將為零),所以最終結果是一樣的。
// 調用AddRef()增加一個(gè)類(lèi)工廠(chǎng)引用計數,因為我們正在使用它pFactory->AddRef();HRESULT hrRet;    // 調用QI()查詢(xún)客戶(hù)端所要的類(lèi)工廠(chǎng)接口    hrRet = pFactory->QueryInterface ( riid, ppv );        // 使用完類(lèi)工廠(chǎng)后調用Release()釋放它    pFactory->Release();    return hrRet;}
再談QueryInterface()

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

HRESULT CSimpleMsgBoxClassFactory::QueryInterface( REFIID riid, void** ppv ){HRESULT hrRet = S_OK;    //檢查指針ppv是不是void*類(lèi)型    if ( IsBadWritePtr ( ppv, sizeof(void*) ))        return E_POINTER;    //標準的QI初始化,將賦值為NULL.    *ppv = NULL;
接下來(lái)檢查riid,看看它是不是類(lèi)工廠(chǎng)實(shí)現的接口之一:IUnknown 或 IclassFactory。
    // 如果客戶(hù)端請求一個(gè)有效接口,則扶植給 *ppv.    if ( InlineIsEqualGUID ( riid, IID_IUnknown ))        {        *ppv = (IUnknown*) this;        }    else if ( InlineIsEqualGUID ( riid, IID_IClassFactory ))        {        *ppv = (IClassFactory*) this;        }    else        {        hrRet = E_NOINTERFACE;        }
最后,如果riid是有效接口,則調用接口的AddRef(),然后返回。
    //如果返回有效接口指針,則調用AddRef()     if ( S_OK == hrRet )        {        ((IUnknown*) *ppv)->AddRef();        }    return hrRet;}
ISimpleMsgBox實(shí)現

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

HRESULT CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText ){_bstr_t bsMsg = bsMessageText;LPCTSTR szMsg = (TCHAR*) bsMsg;   // 如果需要的話(huà),用_bstr_t將串轉換為ANSI 
做完轉換的工作后,顯示信息框,然后返回。
    MessageBox ( hwndParent, szMsg, _T("Simple Message Box"), MB_OK );    return S_OK;}
使用服務(wù)器的客戶(hù)端

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

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

void DoMsgBoxTest(HWND hMainWnd){	ISimpleMsgBox* pIMsgBox;	HRESULT hr;	hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),  // 組件對象類(lèi)的CLSID                            NULL,                // 非聚合                            CLSCTX_INPROC_SERVER,  // 只使用進(jìn)程內服務(wù)器                            __uuidof(ISimpleMsgBox), // 所請求接口的IID                            (void**) &pIMsgBox );   // 容納接口指針的緩沖    if ( FAILED(hr) )        return;    // 然后調用DoSimpleMsgBox()方法并釋放接口。    pIMsgBox->DoSimpleMsgBox ( hMainWnd, _bstr_t("Hello COM!") );    pIMsgBox->Release();}
  就這么簡(jiǎn)單。代碼中從頭到尾都有TRACE語(yǔ)句,這樣在調試器中運行測試程序就可以看到服務(wù)器的每一個(gè)方法是如何被調用的。另外一個(gè)菜單命令是調用CoFreeUnusedLibraries()函數,
  從中你能看到服務(wù)器DllCanUnloadNow()函數的運行。

其它細節——-COM宏

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

struct ISimpleMsgBox : public IUnknown{    // IUnknown 方法    STDMETHOD_(ULONG, AddRef)() PURE;    STDMETHOD_(ULONG, Release)() PURE;    STDMETHOD(QueryInterface)(REFIID riid, void** ppv) PURE;    // ISimpleMsgBox 方法    STDMETHOD(DoSimpleMsgBox)(HWND hwndParent, BSTR bsMessageText) PURE;};
  STDMETHOD()包含 virtual 關(guān)鍵字,返回類(lèi)型和調用規范。STDMETHOD_() 也一樣,除非你指定不同的返回類(lèi)型。PURE 擴展了C++的“=0”,使此函數成為一個(gè)純虛擬函數。 STDMETHOD()和STDMETHOD_()有對應的宏用于方法實(shí)現——STDMETHODIMP和STDMETHODIMP_()。例如DoSimpleMsgBox()的實(shí)現:
STDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText ){  ...}
最后,標準的輸出函數用STDAPI宏聲明,如:
STDAPI DllRegisterServer()
  STDAPI包括返回類(lèi)型和調用規范。要注意STDAPI不能和__declspec(dllexport)一起使用,因為STDAPI的擴展。輸出必須使用.DEF文件。

服務(wù)器注冊以及反注冊

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

HKEY_CLASSES_ROOT CLSID {7D51904E-1645-4a8c-BDE0-0F4A44FC38C4} Default="SimpleMsgBox class" InProcServer32 Default=[path to DLL]; ThreadingModel="Apartment" 
關(guān)于例子代碼的注釋

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

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

正如前面所說(shuō)的,必須用.DEF文件來(lái)從服務(wù)器輸出四個(gè)標準的輸出函數。下面是例子工程的.DEF文件:
EXPORTS    DllRegisterServer   PRIVATE    DllUnregisterServer PRIVATE    DllGetClassObject   PRIVATE    DllCanUnloadNow     PRIVATE
  每一行都包含函數名和PRIVATE關(guān)鍵字。這個(gè)關(guān)鍵字的意思是:此函數是輸出函數,但不包含在輸入庫(import lib)中。也就是說(shuō)客戶(hù)端不能直接從代碼中調用這個(gè)函數,即使是鏈接了輸入庫也不行。這個(gè)關(guān)鍵字時(shí)必須要用的,否則鏈接器會(huì )出錯。

在服務(wù)器中設置斷點(diǎn)鏈

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

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

這樣設置以后,根據實(shí)際源代碼的所在位置,DLL的路徑將會(huì )做自動(dòng)調整。

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Thinking in Java | CHAPTER 1
遠程接口設計經(jīng)驗分享
基于Maven部署的分布式服務(wù)介紹
COM入門(mén)第一部分
Cef功能開(kāi)發(fā)經(jīng)驗總結
[筆記](méi)COM組件初識
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久