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

C++ Programmer's Cookbook

{C++ 基礎(chǔ)} {C++ 高級(jí)} {C#界面,C++核心算法} {設(shè)計(jì)模式} {C#基礎(chǔ)}

COM編程入門(2)

COM編程入門

第二部分 深入COM服務(wù)器

文/趙湘寧

下在例子代碼:src    demo
     本文為剛剛接觸COM的程序員提供編程指南,解釋COM服務(wù)器內(nèi)幕以及如何用C++編寫(xiě)自己的接口。
    繼上一篇COM編程入門之后,本文將討論有關(guān)COM服務(wù)器的內(nèi)容,解釋編寫(xiě)自己的COM接口和COM服務(wù)器所需要的步驟和知識(shí),以及詳細(xì)討論當(dāng)COM庫(kù)對(duì)COM服務(wù)器進(jìn)行調(diào)用時(shí),COM服務(wù)器運(yùn)行的內(nèi)部機(jī)制。
    如果你讀過(guò)上一篇文章。應(yīng)該很熟悉COM客戶端是怎么會(huì)事了。本文將討論COM的另一端——COM服務(wù)器。內(nèi)容包括如何用C++編寫(xiě)一個(gè)簡(jiǎn)單的不涉及類庫(kù)的COM服務(wù)器。深入到創(chuàng)建COM服務(wù)器的內(nèi)部過(guò)程,毫無(wú)遮掩地研究那些庫(kù)代碼是充分理解COM服務(wù)器內(nèi)部機(jī)制的最好方法。
    本文假設(shè)你精通C++并掌握了上一篇文章所討論的概念和術(shù)語(yǔ)。在這一部分將包括如下內(nèi)容:
走馬觀花看COM服務(wù)器——描述COM服務(wù)器的基本要求。
服務(wù)器生命其管理——描述COM服務(wù)器如何控制加載時(shí)間。
實(shí)現(xiàn)接口,從IUnknown開(kāi)始——展示如何用C++類編寫(xiě)一個(gè)接口實(shí)現(xiàn)并描述IUnknown之方法的目的。
深入CoCreateInstance()——探究CoCreateInstance()的調(diào)用機(jī)理。
COM服務(wù)器的注冊(cè)——描述完成服務(wù)器注冊(cè)所需要的注冊(cè)表入口。
創(chuàng)建COM對(duì)象——類工廠——描述創(chuàng)建客戶端要使用的COM對(duì)象的過(guò)程。
一個(gè)定制接口的例子——例子代碼示范了上述概念。
一個(gè)使用服務(wù)器的客戶端——舉例說(shuō)明一個(gè)簡(jiǎn)單的客戶端應(yīng)用程序,用它來(lái)測(cè)試COM服務(wù)器。
其它內(nèi)容——有關(guān)源代碼和調(diào)試的注釋。
走馬觀花看COM服務(wù)器
    本文我們將討論最簡(jiǎn)單的一種COM服務(wù)器,進(jìn)程內(nèi)服務(wù)器(in-process)。“進(jìn)程內(nèi)”意思是服務(wù)器被加載到客戶端程序的進(jìn)程空間。進(jìn)程內(nèi)服務(wù)器都是DLLs,并且與客戶端程序同在一臺(tái)計(jì)算機(jī)上。
進(jìn)程內(nèi)服務(wù)器在被COM庫(kù)使用之前必須滿足兩個(gè)條件或標(biāo)準(zhǔn):
1、 必須正確在注冊(cè)表的HKEY_CLASSES_ROOT\CLSID 鍵值下注冊(cè)。
2、 必須輸出DllGetClassObject()函數(shù)。
這是進(jìn)程內(nèi)服務(wù)器運(yùn)行的最小需求。在注冊(cè)表的HKEY_CLASSES_ROOT\CLSID 鍵值下必須創(chuàng)建一個(gè)鍵值,用服務(wù)器的GUID作為鍵名字,這個(gè)鍵值必須包含兩個(gè)鍵值清單,一是服務(wù)器的位置,而是服務(wù)器的線程模型。 COM庫(kù)對(duì)DllGetClassObject()函數(shù)進(jìn)行調(diào)用是在CoCreateInstance() API中完成的。
還有三個(gè)函數(shù)通常也要輸出:
o DllCanUnloadNow():由COM庫(kù)調(diào)用來(lái)檢查是否服務(wù)器被從內(nèi)存中卸載。 
o DllRegisterServer():由類似RegSvr32的安裝實(shí)用程序調(diào)用來(lái)注冊(cè)服務(wù)器。
o DllUnregisterServer():由卸載實(shí)用程序調(diào)用來(lái)刪除由DllRegisterServer()創(chuàng)建的注冊(cè)表入口。
另外,只輸出正確的函數(shù)是不夠的——還必須遵循COM規(guī)范,這樣COM庫(kù)和客戶端程序才能使用服務(wù)器。
服務(wù)器生命其管理
       DLL服務(wù)器的一個(gè)與眾不同的方面是控制它們被加載的時(shí)間。“標(biāo)準(zhǔn)的”DLLs被動(dòng)的并且是在應(yīng)用程序使用它們時(shí)被隨機(jī)加載/或卸載。從技術(shù)上講,DLL服務(wù)器也是被動(dòng)的,因?yàn)椴还茉鯓铀鼈儺叡M還是DLL,但COM庫(kù)提供了一種機(jī)制,它允許某個(gè)服務(wù)器命令COM卸載它。這是通過(guò)輸出函數(shù)DllCanUnloadNow()實(shí)現(xiàn)的。這個(gè)函數(shù)的原型如下:
         HRESULT DllCanUnloadNow();
    當(dāng)客戶應(yīng)用程序調(diào)用COM API CoFreeUnusedLibraries()時(shí),通常出于其空閑處理期間,COM庫(kù)遍歷這個(gè)客戶端應(yīng)用已加載所有的DLL服務(wù)器并通過(guò)調(diào)用它的DllCanUnloadNow()函數(shù)查詢每一個(gè)服務(wù)器。另一方面,如果某個(gè)服務(wù)器確定它不再需要駐留內(nèi)存,它可以返回S_OK讓COM將它卸載。
服務(wù)器通過(guò)簡(jiǎn)單的引用計(jì)數(shù)來(lái)確定它是否能被卸載。下面是DllCanUnloadNow()的實(shí)現(xiàn):
extern UINT g_uDllRefCount;  // 服務(wù)器的引用計(jì)數(shù)
HRESULT DllCanUnloadNow()
{
    return (g_uDllRefCount > 0) ? S_FALSE : S_OK;
}
如何處理引用計(jì)數(shù)將在下一節(jié)涉及到具體代碼時(shí)討論。
實(shí)現(xiàn)接口,從IUnknown開(kāi)始
    有必要回想一下IUnknown派生的每一個(gè)接口。因?yàn)镮Unknown包含了兩個(gè)COM對(duì)象的基本特性——引用計(jì)數(shù)和接口查詢。當(dāng)你編寫(xiě)組件對(duì)象類時(shí)(coclass),還要寫(xiě)一個(gè)滿足自己需要的IUnknown實(shí)現(xiàn)。以實(shí)現(xiàn)IUnknown接口的組件對(duì)象類為例——下面這個(gè)例子可能是你編寫(xiě)的最簡(jiǎn)單的一個(gè)組件對(duì)象類。我們將在一個(gè)叫做CUnknownImpl的C++類中實(shí)現(xiàn)IUnknown。下面是這個(gè)類的聲明:
class CUnknownImpl : public IUnknown
{
public:
    // 構(gòu)造函數(shù)和析構(gòu)器
    CUnknownImpl();
    virtual ~CUnknownImpl();

    // IUnknown 方法
    ULONG AddRef();
    ULONG Release)();
    HRESULT QueryInterface( REFIID riid, void** ppv );

protected:
    UINT m_uRefCount;  // 對(duì)象的引用計(jì)數(shù)
};
構(gòu)造器和析構(gòu)器
構(gòu)造器和析構(gòu)器管理服務(wù)器的引用計(jì)數(shù):
CUnknownImpl::CUnknownImpl()
{
    m_uRefCount = 0;
    g_uDllRefCount++;
}

CUnknownImpl::~CUnknownImpl()
{
    g_uDllRefCount--;
}
    當(dāng)創(chuàng)建新的COM對(duì)象時(shí),構(gòu)造器被調(diào)用,它增加服務(wù)器的引用計(jì)數(shù)以保持這個(gè)服務(wù)器駐留內(nèi)存。同時(shí)它還將對(duì)象的引用計(jì)數(shù)初始化為零。當(dāng)這個(gè)COM對(duì)象被摧毀時(shí),它減少服務(wù)器的引用計(jì)數(shù)。
AddRef()和Release()

這兩個(gè)方法控制COM對(duì)象的生命期。AddRef()很簡(jiǎn)單:
ULONG CUnknownImpl::AddRef()
{
    return ++m_uRefCount;
}

AddRef()只增加對(duì)象的引用計(jì)數(shù)并返回更新的計(jì)數(shù)。
Release()更簡(jiǎn)單:
ULONG CUnknownImpl::Release()
{
ULONG uRet = --m_uRefCount;

    if ( 0 == m_uRefCount )  // 是否釋放了最后的引用?
        delete this;

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

QueryInterface()

       QueryInterface()簡(jiǎn)稱QI(),由客戶端程序調(diào)用這個(gè)函數(shù)從COM對(duì)象請(qǐng)求不同的接口。我們?cè)诶哟a中因?yàn)橹粚?shí)現(xiàn)一個(gè)接口,QI()會(huì)很容易使用。QI()有兩個(gè)參數(shù):一個(gè)是所請(qǐng)求的接口IID,一個(gè)是指針的緩沖大小,如果查詢成功,QI()將接口指針地址存儲(chǔ)在這個(gè)緩沖指針中。
HRESULT CUnknownImpl::QueryInterface ( REFIID riid, void** ppv )
{
HRESULT hrRet = S_OK;

    // 標(biāo)準(zhǔn)QI()初始化 – 置 *ppv 為 NULL.
    *ppv = NULL;

    // 如果客戶端請(qǐng)求提供的接口,給 *ppv.賦值
    if ( IsEqualIID ( riid, IID_IUnknown ))
        {
        *ppv = (IUnknown*) this;
        }
    else
        {
        // 不提供客戶端請(qǐng)求的接口 
        hrRet = E_NOINTERFACE;
        }

    // 如果返回一個(gè)接口指針。 調(diào)用AddRef()增加引用計(jì)數(shù).
    if ( S_OK == hrRet )
        {
        ((IUnknown*) *ppv)->AddRef();
        }

    return hrRet;
}
在QI()中做了三件不同的事情:
1、初始化傳入的指針為NULL[*ppv = NULL;]。
2、檢查riid,確定組件對(duì)象類(coclass)實(shí)現(xiàn)了客戶端所請(qǐng)求接口.
[if ( IsEqualIID ( riid, IID_IUnknown ))]
3、如果確實(shí)實(shí)現(xiàn)勒索請(qǐng)求的接口,則增加COM對(duì)象的引用計(jì)數(shù)。
[((IUnknown*) *ppv)->AddRef();]

AddRef()調(diào)用很關(guān)鍵。
    *ppv = (IUnknown*) this;
    要?jiǎng)?chuàng)建新的COM對(duì)象引用,就必須調(diào)用這個(gè)函數(shù)通知COM對(duì)象這個(gè)新引用成立。在AddRef()調(diào)用中的強(qiáng)制轉(zhuǎn)換IUnknown*看起來(lái)好像多余,但是在QI()中初始化的*ppv有可能不是IUnknown*類型,所以最好是養(yǎng)成習(xí)慣對(duì)之進(jìn)行強(qiáng)行轉(zhuǎn)換。。
上面我們已經(jīng)討論了一些DLL服務(wù)器的內(nèi)部細(xì)節(jié),接下來(lái)讓我們回頭看一看當(dāng)客戶端調(diào)用CoCreateInstance()時(shí)是如何處理服務(wù)器的。

深入CoCreateInstance()

    在本文的第一部分中,我們見(jiàn)過(guò)CoCreateInstance()API,其作用是當(dāng)客戶端請(qǐng)求對(duì)象時(shí),用它來(lái)創(chuàng)建對(duì)象。從客戶端的立場(chǎng)看,它是一個(gè)黑盒子。只要用正確的參數(shù)調(diào)用它即可得到一個(gè)COM對(duì)象。它并沒(méi)有什么魔法,只是在一個(gè)定義良好的過(guò)程中加載COM服務(wù)器,創(chuàng)建請(qǐng)求的COM對(duì)象并返回所要的指針。就這些。

   下面讓我們來(lái)瀏覽一下這個(gè)過(guò)程。這里要涉及到幾個(gè)不太熟悉的術(shù)語(yǔ),但不用著急,后面會(huì)對(duì)它們作詳細(xì)討論。
1、客戶端程序調(diào)用CoCreateInstance(),傳遞組件對(duì)象類的CLSID以及所要接口的IID。
2、COM庫(kù)在HKEY_CLASSES_ROOT\CLSID.鍵值下查找服務(wù)器的CLSID鍵值,這個(gè)鍵值包含服務(wù)器的注冊(cè)信息。
3、COM庫(kù)讀取服務(wù)器DLL的全路徑并將DLL加載到客戶端的進(jìn)程空間。
4、COM庫(kù)調(diào)用在服務(wù)器中DllGetClassObject()函數(shù)為所請(qǐng)求的組件對(duì)象類請(qǐng)求類工廠。
5、服務(wù)器創(chuàng)建一個(gè)類工廠并將它從DllGetClassObject()返回。
6、COM庫(kù)在類工廠中調(diào)用CreateInstance()方法創(chuàng)建客戶端程序請(qǐng)求的COM對(duì)象。
7、CreateInstance()返回一個(gè)接口指針到客戶端程序。
COM服務(wù)器注冊(cè)

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

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

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

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

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

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

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

接口定義

    我們的新接口是ISimpleMsgBox。所有的接口多必須從IUnknown派生。這個(gè)接口只有一個(gè)方法:DoSimpleMsgBox()。注意它返回標(biāo)準(zhǔn)類型HRESULT。所有的方法都應(yīng)該返回HRESULT類型,并且所有返回到調(diào)用者的其它數(shù)據(jù)都應(yīng)該通過(guò)指針參數(shù)操作。
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++的擴(kuò)展。
DoSimpleMsgBox()的第二個(gè)參數(shù)是BSTR類型。意思是二進(jìn)制串——即定長(zhǎng)序列位的COM表示。BSTRs主要用于Visual Basic 和 Windows Scripting Host之類的腳本客戶端。

接下來(lái)這個(gè)接口由CSimpleMsgBoxImpl C++類來(lái)實(shí)現(xiàn)。其定義如下:
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;

當(dāng)某一客戶端想要?jiǎng)?chuàng)建一個(gè)SimpleMsgBox COM對(duì)象時(shí),它應(yīng)該用下面這樣的代碼:

ISimpleMsgBox* pIMsgBox;
HRESULT hr;

// 組件對(duì)象類的CLSID 
hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),                            NULL,                         // 非聚合
       CLSCTX_INPROC_SERVER, // 進(jìn)程內(nèi)服務(wù)器
     __uuidof(ISimpleMsgBox), // 所請(qǐng)求接口的IID 
    (void**) &pIMsgBox );         // 返回的接口指針的地址
類工廠實(shí)現(xiàn)

    我們的類工廠SimpleMsgBox是在一個(gè)叫做CSimpleMsgBoxClassFactory的C++類中實(shí)現(xiàn)的:
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;
};

構(gòu)造函數(shù)、析構(gòu)函數(shù)和IUnknown方法都和前面例子中的一樣,不同的只有IClassFactory的方法,LockServer(),看起來(lái)相當(dāng)更簡(jiǎn)單:

HRESULT CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock )
{
    fLock ? g_uDllLockCount++ : g_uDllLockCount--;
    return S_OK;
}
CreateInstance()是重點(diǎn)。我們說(shuō)過(guò)這個(gè)方法負(fù)責(zé)創(chuàng)建新的CSimpleMsgBoxImpl對(duì)象。讓我們進(jìn)一步探討一下它的原型和參數(shù):
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                                                    REFIID    riid,
                                                    void**    ppv );
    第一個(gè)參數(shù)pUnkOuter只用于聚合的新對(duì)象,指向“外部的”COM對(duì)象,也就是說(shuō),這個(gè)“外部”對(duì)象將包含此新對(duì)象。對(duì)象的聚合超出了本文的討論范圍,本文的例子對(duì)象也不支持聚合。
riid 和ppv 與在QueryInterface()中的用法一樣——它們是客戶端所請(qǐng)求的接口IID和存儲(chǔ)接口指針的指針緩沖。
下面是CreateInstance()的實(shí)現(xiàn)。它從參數(shù)的有效性檢查和參數(shù)的初始化開(kāi)始。
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                                                    REFIID    riid,
                                                    void**    ppv )
{
    // 因?yàn)椴恢С志酆希赃@個(gè)參數(shù)pUnkOuter必須為NULL.
    if ( NULL != pUnkOuter )
        return CLASS_E_NOAGGREGATION;

    //檢查指針ppv是不是void*類型
    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    *ppv = NULL;

檢查完參數(shù)的有效性后,就可以創(chuàng)建一個(gè)新的對(duì)象了。
CSimpleMsgBoxImpl* pMsgbox;

    // 創(chuàng)建一個(gè)新的COM對(duì)象
    pMsgbox = new CSimpleMsgBoxImpl;

    if ( NULL == pMsgbox )
        return E_OUTOFMEMORY;

最后,用QI()來(lái)查詢客戶端所請(qǐng)求的新對(duì)象的接口。如果QI()失敗,則這個(gè)對(duì)象不可用,必須刪除它。

HRESULT hrRet;

    // 用QI查詢客戶端所請(qǐng)求的對(duì)象接口
    hrRet = pMsgbox->QueryInterface ( riid, ppv );

    // 如果QI失敗,則刪除這個(gè)COM對(duì)象,因?yàn)榭蛻舳瞬荒苁褂盟蛻舳藳](méi)有
    //這個(gè)對(duì)象的任何接口)
    if ( FAILED(hrRet) )
        delete pMsgbox;

    return hrRet;
}
深入DllGetClassObject()
   現(xiàn)在讓我們深入DllGetClassObject()內(nèi)部。它的原型是:
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv );
rclsid是客戶端所請(qǐng)求的組件對(duì)象類的CLSID。這個(gè)函數(shù)必須返回指定組件對(duì)象類的類工廠。
這里的兩個(gè)參數(shù): riid 和 ppv類似QI()的參數(shù)。不過(guò)在這個(gè)函數(shù)中,riid指的是COM庫(kù)所請(qǐng)求的類工廠接口的IID。通常就是IID_IClassFactory。
    因?yàn)镈llGetClassObject()也創(chuàng)建一個(gè)新的COM對(duì)象(類工廠),所以代碼與IClassFactory::CreateInstance()十分相似。開(kāi)始也是進(jìn)行一些有效性檢查以及初始化。
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv )
{
    // 檢查客戶端所要的CSimpleMsgBoxImpl類工廠
    if ( !InlineIsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) ))
        return CLASS_E_CLASSNOTAVAILABLE;

    //檢查指針ppv是不是void*類型
    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    *ppv = NULL;
第一個(gè)if語(yǔ)句檢查rclsid參數(shù)。我們的服務(wù)器只有一個(gè)組件對(duì)象類,所以rclsid必須是CSimpleMsgBoxImpl類的CLSID。__uuidof操作符獲取先前在__declspec(uuid())聲明中指定的CsimpleMsgBoxImpl類的GUID。
下一步是創(chuàng)建一個(gè)類工廠對(duì)象。
CSimpleMsgBoxClassFactory* pFactory;

    // 構(gòu)造一個(gè)新的類工廠對(duì)象
    pFactory = new CSimpleMsgBoxClassFactory;

    if ( NULL == pFactory )
        return E_OUTOFMEMORY;
    這里的處理與CreateInstance()中所做的有所不同。在CreateInstance()中是調(diào)用了QI(),并且如果調(diào)用失敗,則刪除COM對(duì)象。
我們可以把自己假設(shè)成一個(gè)所創(chuàng)建的COM對(duì)象的客戶端,調(diào)用AddRef()進(jìn)行一次引用計(jì)數(shù)(COUNT = 1)。然后調(diào)用QI()。如果QI()調(diào)用成功,它將再一次用AddRef()進(jìn)行引用計(jì)數(shù)(COUNT = 2)。如果QI()調(diào)用失敗。引用計(jì)數(shù)將保持為原來(lái)的值(COUNT = 1)。
在QI()調(diào)用之后,類工廠對(duì)象就使用完了,因此要調(diào)用Release()來(lái)釋放它。如果QI()調(diào)用失敗,這個(gè)對(duì)象將自我刪除(因?yàn)橐糜?jì)數(shù)將為零),所以最終結(jié)果是一樣的。
// 調(diào)用AddRef()增加一個(gè)類工廠引用計(jì)數(shù),因?yàn)槲覀冋谑褂盟?
pFactory->AddRef();

HRESULT hrRet;

    // 調(diào)用QI()查詢客戶端所要的類工廠接口
    hrRet = pFactory->QueryInterface ( riid, ppv );
    
    // 使用完類工廠后調(diào)用Release()釋放它
    pFactory->Release();

    return hrRet;
}
再談QueryInterface()

    前面討論過(guò)QI()的實(shí)現(xiàn),但還是有必要再看一看類工廠的QI(),因?yàn)樗且粋€(gè)很現(xiàn)實(shí)的例子,其中COM對(duì)象實(shí)現(xiàn)的不光是IUnknown。首先進(jìn)行的是對(duì)ppv緩沖的有效性檢查以及初始化。
HRESULT CSimpleMsgBoxClassFactory::QueryInterface( REFIID riid, void** ppv )
{
HRESULT hrRet = S_OK;

    //檢查指針ppv是不是void*類型
    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    //標(biāo)準(zhǔn)的QI初始化,將賦值為NULL.
    *ppv = NULL;

接下來(lái)檢查riid,看看它是不是類工廠實(shí)現(xiàn)的接口之一:IUnknown 或 IclassFactory。
    // 如果客戶端請(qǐng)求一個(gè)有效接口,則扶植給 *ppv.
    if ( InlineIsEqualGUID ( riid, IID_IUnknown ))
        {
        *ppv = (IUnknown*) this;
        }
    else if ( InlineIsEqualGUID ( riid, IID_IClassFactory ))
        {
        *ppv = (IClassFactory*) this;
        }
    else
        {
        hrRet = E_NOINTERFACE;
        }

最后,如果riid是有效接口,則調(diào)用接口的AddRef(),然后返回。
    //如果返回有效接口指針,則調(diào)用AddRef() 
    if ( S_OK == hrRet )
        {
        ((IUnknown*) *ppv)->AddRef();
        }

    return hrRet;
}
ISimpleMsgBox實(shí)現(xiàn)

    最后的也是必不可少的一關(guān)是ISimpleMsgBox實(shí)現(xiàn),我們的代碼只實(shí)現(xiàn)ISimpleMsgBox的方法DoSimpleMsgBox()。首先用微軟的擴(kuò)展類_bstr_t將bsMessageText轉(zhuǎn)換成TCHAR串。
HRESULT CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )
{
_bstr_t bsMsg = bsMessageText;
LPCTSTR szMsg = (TCHAR*) bsMsg;   // 如果需要的話,用_bstr_t將串轉(zhuǎn)換為ANSI 
做完轉(zhuǎn)換的工作后,顯示信息框,然后返回。
    MessageBox ( hwndParent, szMsg, _T("Simple Message Box"), MB_OK );
    return S_OK;
}
使用服務(wù)器的客戶端

我們已經(jīng)完成了一個(gè)超級(jí)棒的COM服務(wù)器,如何使用它呢? 我們的接口一個(gè)定制接口,也就是說(shuō)它只能被C或C++客戶端使用。(如果在組件對(duì)象類中同時(shí)實(shí)現(xiàn)IDispatch接口,那我們幾乎就可以在任何客戶端環(huán)境中——Visual Basic,Windows Scripting Host,Web頁(yè)面,PerlScript等使用COM對(duì)象。有關(guān)這方面的內(nèi)容我們留待另外的文章討論)。本文提供了一個(gè)使用ISimpleMsgBox的例子程序。這個(gè)程序基于用Win32應(yīng)用程序向?qū)Ы⒌腍ello World例子。文件菜單包含兩個(gè)測(cè)試服務(wù)器的命令:
如圖所示:
 [Test client screen shot - 12K]
Test MsgBox COM Server菜單命令創(chuàng)建CSimpleMsgBoxImpl對(duì)象并調(diào)用DoSimpleMsgBox()。因?yàn)檫@
是個(gè)簡(jiǎn)單的方法,要寫(xiě)的代碼不長(zhǎng)。

我們先用CoCreateInstance()創(chuàng)建一個(gè)COM對(duì)象。

void DoMsgBoxTest(HWND hMainWnd)
{
ISimpleMsgBox* pIMsgBox;
HRESULT hr;

hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),  // 組件對(duì)象類的CLSID
                            NULL,                // 非聚合
                            CLSCTX_INPROC_SERVER,  // 只使用進(jìn)程內(nèi)服務(wù)器
                            __uuidof(ISimpleMsgBox), // 所請(qǐng)求接口的IID 
                           (void**) &pIMsgBox );   // 容納接口指針的緩沖

    if ( FAILED(hr) )
        return;

然后調(diào)用DoSimpleMsgBox()方法并釋放接口。
    pIMsgBox->DoSimpleMsgBox ( hMainWnd, _bstr_t("Hello COM!") );
    pIMsgBox->Release();
}
就這么簡(jiǎn)單。代碼中從頭到尾都有TRACE語(yǔ)句,這樣在調(diào)試器中運(yùn)行測(cè)試程序就可以看到服務(wù)器的每一個(gè)方法
是如何被調(diào)用的。
另外一個(gè)菜單命令是調(diào)用CoFreeUnusedLibraries()函數(shù),從中你能看到服務(wù)器DllCanUnloadNow()函數(shù)的運(yùn)行。
其它細(xì)節(jié)-COM宏
    COM代碼中有些宏隱藏了實(shí)現(xiàn)細(xì)節(jié),并允許在C和C++客戶端使用相同的聲明。本文中沒(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)鍵字,返回類型和調(diào)用規(guī)范。STDMETHOD_()也一樣,除非你指定不
同的返回類型。PURE擴(kuò)展了C++的“=0”,使此函數(shù)成為一個(gè)純虛擬函數(shù)。
STDMETHOD()和STDMETHOD_()有對(duì)應(yīng)的宏用于方法實(shí)現(xiàn)——STDMETHODIMP和STDMETHODIMP_()。
例如DoSimpleMsgBox()的實(shí)現(xiàn):
STDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )
{
  ...
}
最后,標(biāo)準(zhǔn)的輸出函數(shù)用STDAPI宏聲明,如:
STDAPI DllRegisterServer()
STDAPI包括返回類型和調(diào)用規(guī)范。要注意STDAPI不能和__declspec(dllexport)一起使用,
因?yàn)镾TDAPI的擴(kuò)展。輸出必須使用.DEF文件。
服務(wù)器注冊(cè)以及反注冊(cè)
     前面講過(guò)服務(wù)器實(shí)現(xiàn)了DllRegisterServer()和DllUnregisterServer()兩個(gè)函數(shù)。它們的工作是創(chuàng)建和
刪除關(guān)于COM服務(wù)器的注冊(cè)表入口。其代碼都是對(duì)注冊(cè)表的處理,所以在此不必贅言,只是列出DllRegisterServer()創(chuàng)建的注冊(cè)表入口:

鍵名

鍵值

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ù)器的源代碼和測(cè)試服
務(wù)器所用的客戶端源代碼。在VC的IDE環(huán)境中可以同時(shí)加載它們進(jìn)行處理。在工作間的同級(jí)層次有兩個(gè)工程都要
用到的頭文件,但每個(gè)工程都有自己的子目錄。
同級(jí)的公共頭文件是:
ISimpleMsgBox.h——定義ISimpleMsgBox的頭文件。
SimpleMsgBoxComDef.h——包含__declspec(uuid())的聲明。這些聲明都在單獨(dú)的文件中,因?yàn)榭蛻?BR>端需要CSimpleMsgBoxImpl的GUID,不是它的定義。將GUID移到單獨(dú)的文件中,使客戶端在存取GUID時(shí)不依賴
CSimpleMsgBoxImpl的內(nèi)部結(jié)構(gòu)。它是接口,ISimpleMsgBox,對(duì)客戶端很重要。
正如前面所說(shuō)的,必須用.DEF文件來(lái)從服務(wù)器輸出四個(gè)標(biāo)準(zhǔn)的輸出函數(shù)。下面是例子工程的.DEF文件:
EXPORTS
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    每一行都包含函數(shù)名和PRIVATE關(guān)鍵字。這個(gè)關(guān)鍵字的意思是:此函數(shù)是輸出函數(shù),但不包含在輸入庫(kù)(import lib)中。也就是說(shuō)客戶端不能直接從代碼中調(diào)用這個(gè)函數(shù),即使是鏈接了輸入庫(kù)也不行。這個(gè)關(guān)鍵字時(shí)必須要用的,否則鏈接器會(huì)出錯(cuò)。

在服務(wù)器中設(shè)置斷點(diǎn)鏈
    如果你想在服務(wù)器代碼中設(shè)置斷點(diǎn),有兩種方法:第一種是將服務(wù)器工程(MsgBoxSvr)設(shè)置為活動(dòng)工程,然后開(kāi)始調(diào)試。MSVC將問(wèn)你調(diào)試會(huì)話要運(yùn)行的可執(zhí)行程序。輸入客戶端測(cè)試程序的全路徑,你必須事先建立好。第二種方法是將客戶端工程(TestClient)設(shè)置為活動(dòng)工程,配置工程的從屬(dependencies)屬性,以便服務(wù)器工程從屬于客戶端工程。這樣如果你改變了服務(wù)器的代碼,那么在編譯客戶端工程時(shí)會(huì)自動(dòng)重新編譯服務(wù)器工程代碼。最后還要做的是當(dāng)你開(kāi)始調(diào)試客戶端時(shí)必須告訴MSVC加載服務(wù)器符號(hào)(symbols)。
下面是設(shè)置工程屬性的對(duì)話框:Project->Dependencies菜單
 [Project dependencies - 7K]
為了加載服務(wù)器符號(hào),打開(kāi)TestClient的工程設(shè)置(Project->Settings菜單),選擇Debug標(biāo)簽,并在Category組合框中選擇Additional DLLs。在列表框中單擊New一個(gè)入口,然后輸入服務(wù)器DLL的全路徑名。如下圖所示:
 [Debug settings - 15K]
這樣設(shè)置以后,根據(jù)實(shí)際源代碼的所在位置,DLL的路徑將會(huì)做自動(dòng)調(diào)整。
(第二部分完)

COM編程入門

第二部分 深入COM服務(wù)器

文/趙湘寧

下在例子代碼:src    demo
     本文為剛剛接觸COM的程序員提供編程指南,解釋COM服務(wù)器內(nèi)幕以及如何用C++編寫(xiě)自己的接口。
    繼上一篇COM編程入門之后,本文將討論有關(guān)COM服務(wù)器的內(nèi)容,解釋編寫(xiě)自己的COM接口和COM服務(wù)器所需要的步驟和知識(shí),以及詳細(xì)討論當(dāng)COM庫(kù)對(duì)COM服務(wù)器進(jìn)行調(diào)用時(shí),COM服務(wù)器運(yùn)行的內(nèi)部機(jī)制。
    如果你讀過(guò)上一篇文章。應(yīng)該很熟悉COM客戶端是怎么會(huì)事了。本文將討論COM的另一端——COM服務(wù)器。內(nèi)容包括如何用C++編寫(xiě)一個(gè)簡(jiǎn)單的不涉及類庫(kù)的COM服務(wù)器。深入到創(chuàng)建COM服務(wù)器的內(nèi)部過(guò)程,毫無(wú)遮掩地研究那些庫(kù)代碼是充分理解COM服務(wù)器內(nèi)部機(jī)制的最好方法。
    本文假設(shè)你精通C++并掌握了上一篇文章所討論的概念和術(shù)語(yǔ)。在這一部分將包括如下內(nèi)容:
走馬觀花看COM服務(wù)器——描述COM服務(wù)器的基本要求。
服務(wù)器生命其管理——描述COM服務(wù)器如何控制加載時(shí)間。
實(shí)現(xiàn)接口,從IUnknown開(kāi)始——展示如何用C++類編寫(xiě)一個(gè)接口實(shí)現(xiàn)并描述IUnknown之方法的目的。
深入CoCreateInstance()——探究CoCreateInstance()的調(diào)用機(jī)理。
COM服務(wù)器的注冊(cè)——描述完成服務(wù)器注冊(cè)所需要的注冊(cè)表入口。
創(chuàng)建COM對(duì)象——類工廠——描述創(chuàng)建客戶端要使用的COM對(duì)象的過(guò)程。
一個(gè)定制接口的例子——例子代碼示范了上述概念。
一個(gè)使用服務(wù)器的客戶端——舉例說(shuō)明一個(gè)簡(jiǎn)單的客戶端應(yīng)用程序,用它來(lái)測(cè)試COM服務(wù)器。
其它內(nèi)容——有關(guān)源代碼和調(diào)試的注釋。
走馬觀花看COM服務(wù)器
    本文我們將討論最簡(jiǎn)單的一種COM服務(wù)器,進(jìn)程內(nèi)服務(wù)器(in-process)。“進(jìn)程內(nèi)”意思是服務(wù)器被加載到客戶端程序的進(jìn)程空間。進(jìn)程內(nèi)服務(wù)器都是DLLs,并且與客戶端程序同在一臺(tái)計(jì)算機(jī)上。
進(jìn)程內(nèi)服務(wù)器在被COM庫(kù)使用之前必須滿足兩個(gè)條件或標(biāo)準(zhǔn):
1、 必須正確在注冊(cè)表的HKEY_CLASSES_ROOT\CLSID 鍵值下注冊(cè)。
2、 必須輸出DllGetClassObject()函數(shù)。
這是進(jìn)程內(nèi)服務(wù)器運(yùn)行的最小需求。在注冊(cè)表的HKEY_CLASSES_ROOT\CLSID 鍵值下必須創(chuàng)建一個(gè)鍵值,用服務(wù)器的GUID作為鍵名字,這個(gè)鍵值必須包含兩個(gè)鍵值清單,一是服務(wù)器的位置,而是服務(wù)器的線程模型。 COM庫(kù)對(duì)DllGetClassObject()函數(shù)進(jìn)行調(diào)用是在CoCreateInstance() API中完成的。
還有三個(gè)函數(shù)通常也要輸出:
o DllCanUnloadNow():由COM庫(kù)調(diào)用來(lái)檢查是否服務(wù)器被從內(nèi)存中卸載。 
o DllRegisterServer():由類似RegSvr32的安裝實(shí)用程序調(diào)用來(lái)注冊(cè)服務(wù)器。
o DllUnregisterServer():由卸載實(shí)用程序調(diào)用來(lái)刪除由DllRegisterServer()創(chuàng)建的注冊(cè)表入口。
另外,只輸出正確的函數(shù)是不夠的——還必須遵循COM規(guī)范,這樣COM庫(kù)和客戶端程序才能使用服務(wù)器。
服務(wù)器生命其管理
       DLL服務(wù)器的一個(gè)與眾不同的方面是控制它們被加載的時(shí)間。“標(biāo)準(zhǔn)的”DLLs被動(dòng)的并且是在應(yīng)用程序使用它們時(shí)被隨機(jī)加載/或卸載。從技術(shù)上講,DLL服務(wù)器也是被動(dòng)的,因?yàn)椴还茉鯓铀鼈儺叡M還是DLL,但COM庫(kù)提供了一種機(jī)制,它允許某個(gè)服務(wù)器命令COM卸載它。這是通過(guò)輸出函數(shù)DllCanUnloadNow()實(shí)現(xiàn)的。這個(gè)函數(shù)的原型如下:
         HRESULT DllCanUnloadNow();
    當(dāng)客戶應(yīng)用程序調(diào)用COM API CoFreeUnusedLibraries()時(shí),通常出于其空閑處理期間,COM庫(kù)遍歷這個(gè)客戶端應(yīng)用已加載所有的DLL服務(wù)器并通過(guò)調(diào)用它的DllCanUnloadNow()函數(shù)查詢每一個(gè)服務(wù)器。另一方面,如果某個(gè)服務(wù)器確定它不再需要駐留內(nèi)存,它可以返回S_OK讓COM將它卸載。
服務(wù)器通過(guò)簡(jiǎn)單的引用計(jì)數(shù)來(lái)確定它是否能被卸載。下面是DllCanUnloadNow()的實(shí)現(xiàn):
extern UINT g_uDllRefCount;  // 服務(wù)器的引用計(jì)數(shù)
HRESULT DllCanUnloadNow()
{
    return (g_uDllRefCount > 0) ? S_FALSE : S_OK;
}
如何處理引用計(jì)數(shù)將在下一節(jié)涉及到具體代碼時(shí)討論。
實(shí)現(xiàn)接口,從IUnknown開(kāi)始
    有必要回想一下IUnknown派生的每一個(gè)接口。因?yàn)镮Unknown包含了兩個(gè)COM對(duì)象的基本特性——引用計(jì)數(shù)和接口查詢。當(dāng)你編寫(xiě)組件對(duì)象類時(shí)(coclass),還要寫(xiě)一個(gè)滿足自己需要的IUnknown實(shí)現(xiàn)。以實(shí)現(xiàn)IUnknown接口的組件對(duì)象類為例——下面這個(gè)例子可能是你編寫(xiě)的最簡(jiǎn)單的一個(gè)組件對(duì)象類。我們將在一個(gè)叫做CUnknownImpl的C++類中實(shí)現(xiàn)IUnknown。下面是這個(gè)類的聲明:
class CUnknownImpl : public IUnknown
{
public:
    // 構(gòu)造函數(shù)和析構(gòu)器
    CUnknownImpl();
    virtual ~CUnknownImpl();

    // IUnknown 方法
    ULONG AddRef();
    ULONG Release)();
    HRESULT QueryInterface( REFIID riid, void** ppv );

protected:
    UINT m_uRefCount;  // 對(duì)象的引用計(jì)數(shù)
};
構(gòu)造器和析構(gòu)器
構(gòu)造器和析構(gòu)器管理服務(wù)器的引用計(jì)數(shù):
CUnknownImpl::CUnknownImpl()
{
    m_uRefCount = 0;
    g_uDllRefCount++;
}

CUnknownImpl::~CUnknownImpl()
{
    g_uDllRefCount--;
}
    當(dāng)創(chuàng)建新的COM對(duì)象時(shí),構(gòu)造器被調(diào)用,它增加服務(wù)器的引用計(jì)數(shù)以保持這個(gè)服務(wù)器駐留內(nèi)存。同時(shí)它還將對(duì)象的引用計(jì)數(shù)初始化為零。當(dāng)這個(gè)COM對(duì)象被摧毀時(shí),它減少服務(wù)器的引用計(jì)數(shù)。
AddRef()和Release()

這兩個(gè)方法控制COM對(duì)象的生命期。AddRef()很簡(jiǎn)單:
ULONG CUnknownImpl::AddRef()
{
    return ++m_uRefCount;
}

AddRef()只增加對(duì)象的引用計(jì)數(shù)并返回更新的計(jì)數(shù)。
Release()更簡(jiǎn)單:
ULONG CUnknownImpl::Release()
{
ULONG uRet = --m_uRefCount;

    if ( 0 == m_uRefCount )  // 是否釋放了最后的引用?
        delete this;

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

QueryInterface()

       QueryInterface()簡(jiǎn)稱QI(),由客戶端程序調(diào)用這個(gè)函數(shù)從COM對(duì)象請(qǐng)求不同的接口。我們?cè)诶哟a中因?yàn)橹粚?shí)現(xiàn)一個(gè)接口,QI()會(huì)很容易使用。QI()有兩個(gè)參數(shù):一個(gè)是所請(qǐng)求的接口IID,一個(gè)是指針的緩沖大小,如果查詢成功,QI()將接口指針地址存儲(chǔ)在這個(gè)緩沖指針中。
HRESULT CUnknownImpl::QueryInterface ( REFIID riid, void** ppv )
{
HRESULT hrRet = S_OK;

    // 標(biāo)準(zhǔn)QI()初始化 – 置 *ppv 為 NULL.
    *ppv = NULL;

    // 如果客戶端請(qǐng)求提供的接口,給 *ppv.賦值
    if ( IsEqualIID ( riid, IID_IUnknown ))
        {
        *ppv = (IUnknown*) this;
        }
    else
        {
        // 不提供客戶端請(qǐng)求的接口 
        hrRet = E_NOINTERFACE;
        }

    // 如果返回一個(gè)接口指針。 調(diào)用AddRef()增加引用計(jì)數(shù).
    if ( S_OK == hrRet )
        {
        ((IUnknown*) *ppv)->AddRef();
        }

    return hrRet;
}
在QI()中做了三件不同的事情:
1、初始化傳入的指針為NULL[*ppv = NULL;]。
2、檢查riid,確定組件對(duì)象類(coclass)實(shí)現(xiàn)了客戶端所請(qǐng)求接口.
[if ( IsEqualIID ( riid, IID_IUnknown ))]
3、如果確實(shí)實(shí)現(xiàn)勒索請(qǐng)求的接口,則增加COM對(duì)象的引用計(jì)數(shù)。
[((IUnknown*) *ppv)->AddRef();]

AddRef()調(diào)用很關(guān)鍵。
    *ppv = (IUnknown*) this;
    要?jiǎng)?chuàng)建新的COM對(duì)象引用,就必須調(diào)用這個(gè)函數(shù)通知COM對(duì)象這個(gè)新引用成立。在AddRef()調(diào)用中的強(qiáng)制轉(zhuǎn)換IUnknown*看起來(lái)好像多余,但是在QI()中初始化的*ppv有可能不是IUnknown*類型,所以最好是養(yǎng)成習(xí)慣對(duì)之進(jìn)行強(qiáng)行轉(zhuǎn)換。。
上面我們已經(jīng)討論了一些DLL服務(wù)器的內(nèi)部細(xì)節(jié),接下來(lái)讓我們回頭看一看當(dāng)客戶端調(diào)用CoCreateInstance()時(shí)是如何處理服務(wù)器的。

深入CoCreateInstance()

    在本文的第一部分中,我們見(jiàn)過(guò)CoCreateInstance()API,其作用是當(dāng)客戶端請(qǐng)求對(duì)象時(shí),用它來(lái)創(chuàng)建對(duì)象。從客戶端的立場(chǎng)看,它是一個(gè)黑盒子。只要用正確的參數(shù)調(diào)用它即可得到一個(gè)COM對(duì)象。它并沒(méi)有什么魔法,只是在一個(gè)定義良好的過(guò)程中加載COM服務(wù)器,創(chuàng)建請(qǐng)求的COM對(duì)象并返回所要的指針。就這些。

   下面讓我們來(lái)瀏覽一下這個(gè)過(guò)程。這里要涉及到幾個(gè)不太熟悉的術(shù)語(yǔ),但不用著急,后面會(huì)對(duì)它們作詳細(xì)討論。
1、客戶端程序調(diào)用CoCreateInstance(),傳遞組件對(duì)象類的CLSID以及所要接口的IID。
2、COM庫(kù)在HKEY_CLASSES_ROOT\CLSID.鍵值下查找服務(wù)器的CLSID鍵值,這個(gè)鍵值包含服務(wù)器的注冊(cè)信息。
3、COM庫(kù)讀取服務(wù)器DLL的全路徑并將DLL加載到客戶端的進(jìn)程空間。
4、COM庫(kù)調(diào)用在服務(wù)器中DllGetClassObject()函數(shù)為所請(qǐng)求的組件對(duì)象類請(qǐng)求類工廠。
5、服務(wù)器創(chuàng)建一個(gè)類工廠并將它從DllGetClassObject()返回。
6、COM庫(kù)在類工廠中調(diào)用CreateInstance()方法創(chuàng)建客戶端程序請(qǐng)求的COM對(duì)象。
7、CreateInstance()返回一個(gè)接口指針到客戶端程序。
COM服務(wù)器注冊(cè)

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

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

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

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

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

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

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

接口定義

    我們的新接口是ISimpleMsgBox。所有的接口多必須從IUnknown派生。這個(gè)接口只有一個(gè)方法:DoSimpleMsgBox()。注意它返回標(biāo)準(zhǔn)類型HRESULT。所有的方法都應(yīng)該返回HRESULT類型,并且所有返回到調(diào)用者的其它數(shù)據(jù)都應(yīng)該通過(guò)指針參數(shù)操作。
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++的擴(kuò)展。
DoSimpleMsgBox()的第二個(gè)參數(shù)是BSTR類型。意思是二進(jìn)制串——即定長(zhǎng)序列位的COM表示。BSTRs主要用于Visual Basic 和 Windows Scripting Host之類的腳本客戶端。

接下來(lái)這個(gè)接口由CSimpleMsgBoxImpl C++類來(lái)實(shí)現(xiàn)。其定義如下:
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;

當(dāng)某一客戶端想要?jiǎng)?chuàng)建一個(gè)SimpleMsgBox COM對(duì)象時(shí),它應(yīng)該用下面這樣的代碼:

ISimpleMsgBox* pIMsgBox;
HRESULT hr;

// 組件對(duì)象類的CLSID 
hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),                            NULL,                         // 非聚合
       CLSCTX_INPROC_SERVER, // 進(jìn)程內(nèi)服務(wù)器
     __uuidof(ISimpleMsgBox), // 所請(qǐng)求接口的IID 
    (void**) &pIMsgBox );         // 返回的接口指針的地址
類工廠實(shí)現(xiàn)

    我們的類工廠SimpleMsgBox是在一個(gè)叫做CSimpleMsgBoxClassFactory的C++類中實(shí)現(xiàn)的:
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;
};

構(gòu)造函數(shù)、析構(gòu)函數(shù)和IUnknown方法都和前面例子中的一樣,不同的只有IClassFactory的方法,LockServer(),看起來(lái)相當(dāng)更簡(jiǎn)單:

HRESULT CSimpleMsgBoxClassFactory::LockServer ( BOOL fLock )
{
    fLock ? g_uDllLockCount++ : g_uDllLockCount--;
    return S_OK;
}
CreateInstance()是重點(diǎn)。我們說(shuō)過(guò)這個(gè)方法負(fù)責(zé)創(chuàng)建新的CSimpleMsgBoxImpl對(duì)象。讓我們進(jìn)一步探討一下它的原型和參數(shù):
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                                                    REFIID    riid,
                                                    void**    ppv );
    第一個(gè)參數(shù)pUnkOuter只用于聚合的新對(duì)象,指向“外部的”COM對(duì)象,也就是說(shuō),這個(gè)“外部”對(duì)象將包含此新對(duì)象。對(duì)象的聚合超出了本文的討論范圍,本文的例子對(duì)象也不支持聚合。
riid 和ppv 與在QueryInterface()中的用法一樣——它們是客戶端所請(qǐng)求的接口IID和存儲(chǔ)接口指針的指針緩沖。
下面是CreateInstance()的實(shí)現(xiàn)。它從參數(shù)的有效性檢查和參數(shù)的初始化開(kāi)始。
HRESULT CSimpleMsgBoxClassFactory::CreateInstance ( IUnknown* pUnkOuter,
                                                    REFIID    riid,
                                                    void**    ppv )
{
    // 因?yàn)椴恢С志酆希赃@個(gè)參數(shù)pUnkOuter必須為NULL.
    if ( NULL != pUnkOuter )
        return CLASS_E_NOAGGREGATION;

    //檢查指針ppv是不是void*類型
    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    *ppv = NULL;

檢查完參數(shù)的有效性后,就可以創(chuàng)建一個(gè)新的對(duì)象了。
CSimpleMsgBoxImpl* pMsgbox;

    // 創(chuàng)建一個(gè)新的COM對(duì)象
    pMsgbox = new CSimpleMsgBoxImpl;

    if ( NULL == pMsgbox )
        return E_OUTOFMEMORY;

最后,用QI()來(lái)查詢客戶端所請(qǐng)求的新對(duì)象的接口。如果QI()失敗,則這個(gè)對(duì)象不可用,必須刪除它。

HRESULT hrRet;

    // 用QI查詢客戶端所請(qǐng)求的對(duì)象接口
    hrRet = pMsgbox->QueryInterface ( riid, ppv );

    // 如果QI失敗,則刪除這個(gè)COM對(duì)象,因?yàn)榭蛻舳瞬荒苁褂盟蛻舳藳](méi)有
    //這個(gè)對(duì)象的任何接口)
    if ( FAILED(hrRet) )
        delete pMsgbox;

    return hrRet;
}
深入DllGetClassObject()
   現(xiàn)在讓我們深入DllGetClassObject()內(nèi)部。它的原型是:
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv );
rclsid是客戶端所請(qǐng)求的組件對(duì)象類的CLSID。這個(gè)函數(shù)必須返回指定組件對(duì)象類的類工廠。
這里的兩個(gè)參數(shù): riid 和 ppv類似QI()的參數(shù)。不過(guò)在這個(gè)函數(shù)中,riid指的是COM庫(kù)所請(qǐng)求的類工廠接口的IID。通常就是IID_IClassFactory。
    因?yàn)镈llGetClassObject()也創(chuàng)建一個(gè)新的COM對(duì)象(類工廠),所以代碼與IClassFactory::CreateInstance()十分相似。開(kāi)始也是進(jìn)行一些有效性檢查以及初始化。
HRESULT DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv )
{
    // 檢查客戶端所要的CSimpleMsgBoxImpl類工廠
    if ( !InlineIsEqualGUID ( rclsid, __uuidof(CSimpleMsgBoxImpl) ))
        return CLASS_E_CLASSNOTAVAILABLE;

    //檢查指針ppv是不是void*類型
    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    *ppv = NULL;
第一個(gè)if語(yǔ)句檢查rclsid參數(shù)。我們的服務(wù)器只有一個(gè)組件對(duì)象類,所以rclsid必須是CSimpleMsgBoxImpl類的CLSID。__uuidof操作符獲取先前在__declspec(uuid())聲明中指定的CsimpleMsgBoxImpl類的GUID。
下一步是創(chuàng)建一個(gè)類工廠對(duì)象。
CSimpleMsgBoxClassFactory* pFactory;

    // 構(gòu)造一個(gè)新的類工廠對(duì)象
    pFactory = new CSimpleMsgBoxClassFactory;

    if ( NULL == pFactory )
        return E_OUTOFMEMORY;
    這里的處理與CreateInstance()中所做的有所不同。在CreateInstance()中是調(diào)用了QI(),并且如果調(diào)用失敗,則刪除COM對(duì)象。
我們可以把自己假設(shè)成一個(gè)所創(chuàng)建的COM對(duì)象的客戶端,調(diào)用AddRef()進(jìn)行一次引用計(jì)數(shù)(COUNT = 1)。然后調(diào)用QI()。如果QI()調(diào)用成功,它將再一次用AddRef()進(jìn)行引用計(jì)數(shù)(COUNT = 2)。如果QI()調(diào)用失敗。引用計(jì)數(shù)將保持為原來(lái)的值(COUNT = 1)。
在QI()調(diào)用之后,類工廠對(duì)象就使用完了,因此要調(diào)用Release()來(lái)釋放它。如果QI()調(diào)用失敗,這個(gè)對(duì)象將自我刪除(因?yàn)橐糜?jì)數(shù)將為零),所以最終結(jié)果是一樣的。
// 調(diào)用AddRef()增加一個(gè)類工廠引用計(jì)數(shù),因?yàn)槲覀冋谑褂盟?
pFactory->AddRef();

HRESULT hrRet;

    // 調(diào)用QI()查詢客戶端所要的類工廠接口
    hrRet = pFactory->QueryInterface ( riid, ppv );
    
    // 使用完類工廠后調(diào)用Release()釋放它
    pFactory->Release();

    return hrRet;
}
再談QueryInterface()

    前面討論過(guò)QI()的實(shí)現(xiàn),但還是有必要再看一看類工廠的QI(),因?yàn)樗且粋€(gè)很現(xiàn)實(shí)的例子,其中COM對(duì)象實(shí)現(xiàn)的不光是IUnknown。首先進(jìn)行的是對(duì)ppv緩沖的有效性檢查以及初始化。
HRESULT CSimpleMsgBoxClassFactory::QueryInterface( REFIID riid, void** ppv )
{
HRESULT hrRet = S_OK;

    //檢查指針ppv是不是void*類型
    if ( IsBadWritePtr ( ppv, sizeof(void*) ))
        return E_POINTER;

    //標(biāo)準(zhǔn)的QI初始化,將賦值為NULL.
    *ppv = NULL;

接下來(lái)檢查riid,看看它是不是類工廠實(shí)現(xiàn)的接口之一:IUnknown 或 IclassFactory。
    // 如果客戶端請(qǐng)求一個(gè)有效接口,則扶植給 *ppv.
    if ( InlineIsEqualGUID ( riid, IID_IUnknown ))
        {
        *ppv = (IUnknown*) this;
        }
    else if ( InlineIsEqualGUID ( riid, IID_IClassFactory ))
        {
        *ppv = (IClassFactory*) this;
        }
    else
        {
        hrRet = E_NOINTERFACE;
        }

最后,如果riid是有效接口,則調(diào)用接口的AddRef(),然后返回。
    //如果返回有效接口指針,則調(diào)用AddRef() 
    if ( S_OK == hrRet )
        {
        ((IUnknown*) *ppv)->AddRef();
        }

    return hrRet;
}
ISimpleMsgBox實(shí)現(xiàn)

    最后的也是必不可少的一關(guān)是ISimpleMsgBox實(shí)現(xiàn),我們的代碼只實(shí)現(xiàn)ISimpleMsgBox的方法DoSimpleMsgBox()。首先用微軟的擴(kuò)展類_bstr_t將bsMessageText轉(zhuǎn)換成TCHAR串。
HRESULT CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )
{
_bstr_t bsMsg = bsMessageText;
LPCTSTR szMsg = (TCHAR*) bsMsg;   // 如果需要的話,用_bstr_t將串轉(zhuǎn)換為ANSI 
做完轉(zhuǎn)換的工作后,顯示信息框,然后返回。
    MessageBox ( hwndParent, szMsg, _T("Simple Message Box"), MB_OK );
    return S_OK;
}
使用服務(wù)器的客戶端

我們已經(jīng)完成了一個(gè)超級(jí)棒的COM服務(wù)器,如何使用它呢? 我們的接口一個(gè)定制接口,也就是說(shuō)它只能被C或C++客戶端使用。(如果在組件對(duì)象類中同時(shí)實(shí)現(xiàn)IDispatch接口,那我們幾乎就可以在任何客戶端環(huán)境中——Visual Basic,Windows Scripting Host,Web頁(yè)面,PerlScript等使用COM對(duì)象。有關(guān)這方面的內(nèi)容我們留待另外的文章討論)。本文提供了一個(gè)使用ISimpleMsgBox的例子程序。這個(gè)程序基于用Win32應(yīng)用程序向?qū)Ы⒌腍ello World例子。文件菜單包含兩個(gè)測(cè)試服務(wù)器的命令:
如圖所示:
 [Test client screen shot - 12K]
Test MsgBox COM Server菜單命令創(chuàng)建CSimpleMsgBoxImpl對(duì)象并調(diào)用DoSimpleMsgBox()。因?yàn)檫@
是個(gè)簡(jiǎn)單的方法,要寫(xiě)的代碼不長(zhǎng)。

我們先用CoCreateInstance()創(chuàng)建一個(gè)COM對(duì)象。

void DoMsgBoxTest(HWND hMainWnd)
{
ISimpleMsgBox* pIMsgBox;
HRESULT hr;

hr = CoCreateInstance ( __uuidof(CSimpleMsgBoxImpl),  // 組件對(duì)象類的CLSID
                            NULL,                // 非聚合
                            CLSCTX_INPROC_SERVER,  // 只使用進(jìn)程內(nèi)服務(wù)器
                            __uuidof(ISimpleMsgBox), // 所請(qǐng)求接口的IID 
                           (void**) &pIMsgBox );   // 容納接口指針的緩沖

    if ( FAILED(hr) )
        return;

然后調(diào)用DoSimpleMsgBox()方法并釋放接口。
    pIMsgBox->DoSimpleMsgBox ( hMainWnd, _bstr_t("Hello COM!") );
    pIMsgBox->Release();
}
就這么簡(jiǎn)單。代碼中從頭到尾都有TRACE語(yǔ)句,這樣在調(diào)試器中運(yùn)行測(cè)試程序就可以看到服務(wù)器的每一個(gè)方法
是如何被調(diào)用的。
另外一個(gè)菜單命令是調(diào)用CoFreeUnusedLibraries()函數(shù),從中你能看到服務(wù)器DllCanUnloadNow()函數(shù)的運(yùn)行。
其它細(xì)節(jié)-COM宏
    COM代碼中有些宏隱藏了實(shí)現(xiàn)細(xì)節(jié),并允許在C和C++客戶端使用相同的聲明。本文中沒(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)鍵字,返回類型和調(diào)用規(guī)范。STDMETHOD_()也一樣,除非你指定不
同的返回類型。PURE擴(kuò)展了C++的“=0”,使此函數(shù)成為一個(gè)純虛擬函數(shù)。
STDMETHOD()和STDMETHOD_()有對(duì)應(yīng)的宏用于方法實(shí)現(xiàn)——STDMETHODIMP和STDMETHODIMP_()。
例如DoSimpleMsgBox()的實(shí)現(xiàn):
STDMETHODIMP CSimpleMsgBoxImpl::DoSimpleMsgBox ( HWND hwndParent, BSTR bsMessageText )
{
  ...
}
最后,標(biāo)準(zhǔn)的輸出函數(shù)用STDAPI宏聲明,如:
STDAPI DllRegisterServer()
STDAPI包括返回類型和調(diào)用規(guī)范。要注意STDAPI不能和__declspec(dllexport)一起使用,
因?yàn)镾TDAPI的擴(kuò)展。輸出必須使用.DEF文件。
服務(wù)器注冊(cè)以及反注冊(cè)
     前面講過(guò)服務(wù)器實(shí)現(xiàn)了DllRegisterServer()和DllUnregisterServer()兩個(gè)函數(shù)。它們的工作是創(chuàng)建和
刪除關(guān)于COM服務(wù)器的注冊(cè)表入口。其代碼都是對(duì)注冊(cè)表的處理,所以在此不必贅言,只是列出DllRegisterServer()創(chuàng)建的注冊(cè)表入口:

鍵名

鍵值

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ù)器的源代碼和測(cè)試服
務(wù)器所用的客戶端源代碼。在VC的IDE環(huán)境中可以同時(shí)加載它們進(jìn)行處理。在工作間的同級(jí)層次有兩個(gè)工程都要
用到的頭文件,但每個(gè)工程都有自己的子目錄。
同級(jí)的公共頭文件是:
ISimpleMsgBox.h——定義ISimpleMsgBox的頭文件。
SimpleMsgBoxComDef.h——包含__declspec(uuid())的聲明。這些聲明都在單獨(dú)的文件中,因?yàn)榭蛻?BR>端需要CSimpleMsgBoxImpl的GUID,不是它的定義。將GUID移到單獨(dú)的文件中,使客戶端在存取GUID時(shí)不依賴
CSimpleMsgBoxImpl的內(nèi)部結(jié)構(gòu)。它是接口,ISimpleMsgBox,對(duì)客戶端很重要。
正如前面所說(shuō)的,必須用.DEF文件來(lái)從服務(wù)器輸出四個(gè)標(biāo)準(zhǔn)的輸出函數(shù)。下面是例子工程的.DEF文件:
EXPORTS
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    每一行都包含函數(shù)名和PRIVATE關(guān)鍵字。這個(gè)關(guān)鍵字的意思是:此函數(shù)是輸出函數(shù),但不包含在輸入庫(kù)(import lib)中。也就是說(shuō)客戶端不能直接從代碼中調(diào)用這個(gè)函數(shù),即使是鏈接了輸入庫(kù)也不行。這個(gè)關(guān)鍵字時(shí)必須要用的,否則鏈接器會(huì)出錯(cuò)。

在服務(wù)器中設(shè)置斷點(diǎn)鏈
    如果你想在服務(wù)器代碼中設(shè)置斷點(diǎn),有兩種方法:第一種是將服務(wù)器工程(MsgBoxSvr)設(shè)置為活動(dòng)工程,然后開(kāi)始調(diào)試。MSVC將問(wèn)你調(diào)試會(huì)話要運(yùn)行的可執(zhí)行程序。輸入客戶端測(cè)試程序的全路徑,你必須事先建立好。第二種方法是將客戶端工程(TestClient)設(shè)置為活動(dòng)工程,配置工程的從屬(dependencies)屬性,以便服務(wù)器工程從屬于客戶端工程。這樣如果你改變了服務(wù)器的代碼,那么在編譯客戶端工程時(shí)會(huì)自動(dòng)重新編譯服務(wù)器工程代碼。最后還要做的是當(dāng)你開(kāi)始調(diào)試客戶端時(shí)必須告訴MSVC加載服務(wù)器符號(hào)(symbols)。
下面是設(shè)置工程屬性的對(duì)話框:Project->Dependencies菜單
 [Project dependencies - 7K]
為了加載服務(wù)器符號(hào),打開(kāi)TestClient的工程設(shè)置(Project->Settings菜單),選擇Debug標(biāo)簽,并在Category組合框中選擇Additional DLLs。在列表框中單擊New一個(gè)入口,然后輸入服務(wù)器DLL的全路徑名。如下圖所示:
 [Debug settings - 15K]
這樣設(shè)置以后,根據(jù)實(shí)際源代碼的所在位置,DLL的路徑將會(huì)做自動(dòng)調(diào)整。
(第二部分完)
v

posted on 2005-12-23 13:09 夢(mèng)在天涯 閱讀(1889) 評(píng)論(0)  編輯 收藏 引用 所屬分類: COM/ATL

公告

EMail:itech001#126.com

導(dǎo)航

統(tǒng)計(jì)

  • 隨筆 - 461
  • 文章 - 4
  • 評(píng)論 - 746
  • 引用 - 0

常用鏈接

隨筆分類

隨筆檔案

收藏夾

Blogs

c#(csharp)

C++(cpp)

Enlish

Forums(bbs)

My self

Often go

Useful Webs

Xml/Uml/html

搜索

  •  

積分與排名

  • 積分 - 1816190
  • 排名 - 5

最新評(píng)論

閱讀排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
              久久久久久9| 国产精品稀缺呦系列在线| 亚洲一区二区三区精品视频| 一区二区三区国产精华| 国产精品系列在线| 久久久久久久久综合| 久久免费偷拍视频| 夜夜嗨av一区二区三区四季av| 日韩亚洲视频| 国产精品尤物| 欧美大片在线看| 欧美日韩精品欧美日韩精品| 亚洲自拍偷拍麻豆| 久久狠狠久久综合桃花| 亚洲精品免费网站| 一区二区av| 国内精品亚洲| 亚洲国产中文字幕在线观看| 欧美国产日韩视频| 午夜精品影院在线观看| 久久亚洲二区| 在线亚洲欧美| 久久激情五月激情| 一本久道综合久久精品| 亚洲欧美国产日韩天堂区| 亚洲国产日韩一区| 一本色道久久综合狠狠躁篇怎么玩 | 国产精品久久久久aaaa| 久热爱精品视频线路一| 欧美日韩国产在线看| 久久精品视频免费播放| 欧美激情无毛| 久久精品视频在线播放| 欧美精品一区在线发布| 久久国产精品毛片| 欧美精品少妇一区二区三区| 久久精品国产免费| 欧美巨乳波霸| 欧美在线视频观看免费网站| 欧美国产丝袜视频| 久久九九电影| 欧美色图五月天| 欧美wwwwww| 国产精品午夜在线| 亚洲激情专区| 激情成人在线视频| 夜夜嗨av色综合久久久综合网 | 欧美在线日韩| 亚洲午夜小视频| 蜜臀久久久99精品久久久久久| 亚洲欧美日韩成人| 欧美高清不卡| 久久中文字幕导航| 国产精品日产欧美久久久久| 亚洲国产一区二区a毛片| 国产曰批免费观看久久久| 亚洲九九九在线观看| 亚洲高清不卡在线| 欧美一级在线亚洲天堂| 一区二区三区视频在线观看| 久久综合五月| 久久视频国产精品免费视频在线| 国产精品扒开腿做爽爽爽软件| 亚洲二区在线视频| 一区二区三区在线视频观看| 亚洲男人影院| 亚洲专区国产精品| 欧美大片在线观看一区二区| 久久这里只有| 国产色产综合产在线视频| 在线亚洲精品| 一区二区三区四区五区视频| 牛人盗摄一区二区三区视频| 久久综合影视| 国产真实久久| 亚洲欧美资源在线| 亚洲一区二区在线看| 欧美精品在线观看| 亚洲高清不卡在线| 亚洲东热激情| 久久深夜福利免费观看| 久久一区亚洲| 国产一区二区三区最好精华液| 亚洲你懂的在线视频| 午夜精品一区二区三区四区| 欧美午夜一区| 99伊人成综合| 一区二区三区.www| 欧美人与性动交cc0o| 91久久午夜| 日韩亚洲欧美一区二区三区| 欧美不卡高清| 亚洲激情电影在线| 亚洲久久一区| 欧美看片网站| 亚洲精品日韩激情在线电影| 亚洲另类一区二区| 欧美激情欧美狂野欧美精品| 欧美激情视频网站| 亚洲欧洲精品一区二区三区不卡| 久久国产精品一区二区三区四区| 久久精品天堂| 国自产拍偷拍福利精品免费一| 午夜精品福利一区二区蜜股av| 欧美一区二区三区免费在线看| 国产精品你懂的在线| 亚洲夜晚福利在线观看| 午夜久久一区| 国产日韩欧美不卡| 欧美主播一区二区三区美女 久久精品人| 久久狠狠亚洲综合| 狠狠爱成人网| 久久久久久综合| 欧美电影专区| 99视频精品全国免费| 欧美视频精品在线| 亚洲一区在线免费| 久久久成人网| 一区二区三区在线免费播放| 久久青青草原一区二区| 国产精品久久久久久久久动漫| 亚洲一区二区黄| 久久久99免费视频| 久久久伊人欧美| 好吊色欧美一区二区三区视频| 性色一区二区三区| 美女网站在线免费欧美精品| 亚洲国产高潮在线观看| 久久久久在线观看| 久久久成人网| 欲色影视综合吧| 欧美freesex8一10精品| 最新国产の精品合集bt伙计| 亚洲一品av免费观看| 国产精品红桃| 久久精品一本| 亚洲人成亚洲人成在线观看| 宅男噜噜噜66国产日韩在线观看| 欧美日韩精品在线播放| 一区二区三区视频在线观看| 欧美专区日韩视频| 亚洲第一伊人| 欧美午夜精品久久久久久久| 欧美亚洲视频一区二区| 欧美xxxx在线观看| 正在播放日韩| 国产亚洲一级| 欧美精品二区| 亚洲欧美区自拍先锋| 欧美激情va永久在线播放| 亚洲一区二区三区在线看| 韩国三级电影久久久久久| 蜜桃久久精品一区二区| 亚洲无限乱码一二三四麻| 久久综合电影| 一区二区三区视频在线| 国内精品国语自产拍在线观看| 欧美国产高潮xxxx1819| 亚洲欧美自拍偷拍| 亚洲国产精品激情在线观看| 性高湖久久久久久久久| 亚洲国产小视频在线观看| 国产精品第一页第二页第三页| 久久久爽爽爽美女图片| 一区二区三区 在线观看视频 | 亚洲一区二区三区在线播放| 国产综合在线视频| 欧美日韩视频在线第一区| 久久黄色影院| 国产精品99久久久久久久久久久久| 久久久久久久网| 中文在线一区| 狠狠色香婷婷久久亚洲精品| 欧美日韩在线观看一区二区三区| 久久精品国产免费观看| 一区二区免费在线播放| 欧美激情中文字幕一区二区 | 国产精品久久中文| 免费不卡在线观看av| 亚洲欧美国产高清| 亚洲精品国产精品国产自| 久久夜色精品国产欧美乱极品| 亚洲一级片在线观看| 黄色综合网站| 国产精品日韩在线| 欧美精品色网| 久久夜色精品国产亚洲aⅴ| 亚洲永久免费视频| 亚洲国产精品美女| 久久嫩草精品久久久久| 亚洲五月六月| 亚洲精品人人| 在线观看欧美亚洲| 国产视频一区在线观看一区免费| 欧美日韩国产丝袜另类| 免费人成网站在线观看欧美高清 | 日韩视频一区二区三区在线播放| 国产主播一区二区三区四区| 欧美小视频在线| 另类综合日韩欧美亚洲|