分布式組件對象模型DCOM揭秘_第1頁
分布式組件對象模型DCOM揭秘_第2頁
分布式組件對象模型DCOM揭秘_第3頁
分布式組件對象模型DCOM揭秘_第4頁
分布式組件對象模型DCOM揭秘_第5頁
已閱讀5頁,還剩34頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

PAGEPAGE38介紹

對于許多人來說,學(xué)習(xí)COM和DCOM是一件吃力的事情。COM的用處很大,不少微軟的產(chǎn)品和編程者工具都是基于COM,不過,COM是一門頗難掌握的技術(shù),你可能曾經(jīng)想去學(xué)習(xí)它,閱讀過一些書,使用過一些向?qū)У龋贿^還是不太懂。它看來很復(fù)雜,而且還帶有不少的新名詞,例如"marshalling","apartmentthreads","singletonobjects"等,讓你摸不著頭腦。

這篇指南的目的是幫助你快速理解DCOM的基本要素,并且可以很容易地創(chuàng)建COM客戶和服務(wù)器。讀完這幾篇指南后,你將會發(fā)現(xiàn)如果有一個好的開始,學(xué)習(xí)DCOM是一件非常簡單的事情。以下是本指南的目錄:

COM的基本要素--要學(xué)好它,就從這里開始吧

簡單的COM客戶--介紹簡單的COM客戶

簡單的COM服務(wù)器--使用ATL向?qū)斫⒁粋€服務(wù)器

下載工程文件

*****下載BeepClient工程文件(9KB)

*****下載BeepServer工程文件(17KB)

COM的基本要素

首先要弄懂COM是怎樣工作的。為什么這個工作是首要的呢?因為COM使用它自己專有的詞匯。第二個原因是COM包含有不少的新概念。要掌握這些詞匯和概念,最簡單的其中一個方法是將COM對象和普通的C++對象作比較,并且比較它們的相似和不同之處。你還可以將COM的一些概念映射到標(biāo)準(zhǔn)的C++模型中去,這樣就可以用你已經(jīng)熟悉的東西來理解新概念。我們首先介紹一些COM的基本概念,接著,你就可以很容易地理解后面的例子。

一、類和對象

假設(shè)你在C++中創(chuàng)建了一個稱為xxx的簡單類。它有幾個成員函數(shù),稱為MethodA,MethodB和MethodC。每個成員函數(shù)可接收參數(shù),并返回一個結(jié)果。該類的定義如下所示:classxxx{

public:

intMethodA(inta);

intMethodB(floatb);

floatMethodC(floatc);

};在需要使用類的時候,你必須創(chuàng)建該對象的一個實例。實例是真實的對象;類只是定義。每個對象可作為一個變量(本地或者全局)創(chuàng)建,或者可使用new聲明動態(tài)地創(chuàng)建。new聲明可動態(tài)創(chuàng)建變量并返回指向它的一個指針。你可通過該指針來調(diào)用成員函數(shù),例如:

xxx*px;//指向xxx類的指針

px=newxxx;//創(chuàng)建對象

px->MethodA(1);//調(diào)用方法

deletepx;//釋放對象

你要明白到,COM使用相同的面向?qū)ο竽P?。COM擁有與C++對象一樣的類、成員函數(shù)和實例。雖然你從來不會在一個COM對象上調(diào)用new方法,不過你必須在內(nèi)存中創(chuàng)建它。你通過指針來訪問COM對象,在你完成處理后,你必須釋放它們。

寫COM的代碼時,我們將不會使用上面的new和delete。雖然我們將使用C++作為開發(fā)語言,不過我們將要使用全新的語法。COM是通過調(diào)用COMAPI來實現(xiàn)的,這些API提供創(chuàng)建和破壞COM對象的函數(shù)。以下就是一個用pseudo-COM代碼寫的COM程序例子:

ixx*pi//指向toxxxCOM接口的指針

CoCreateInstance(,,,,&pi)//創(chuàng)建接口

pi->MethodA();//調(diào)用方法

pi->Release();//釋放接口

在這個例子中,我們將稱類ixx是一個“接口”。變量pi是指向接口的一個指針。CoCreateInstance方法可創(chuàng)建一個ixx的實例。接口的指針是用來作方法調(diào)用的。Release用來刪除接口。

為了突出該程序的要點,我故意忽略了CoCreateInstance的一些參數(shù)。CoCreateInstance可接收多個參數(shù),每個參數(shù)都需要更深入的探討才可以了解?,F(xiàn)在,我們首先回過頭來看看COM的一些主要方面。二、COM有什么不同

在某種程度上,COM對象要比它們的同胞C++更復(fù)雜,從網(wǎng)絡(luò)應(yīng)用方面考慮,大多數(shù)的復(fù)雜性都是必要的。以下就是在設(shè)計COM時的4個基本要素:

。C++對象通常都運行在同一進(jìn)程空間中。COM對象可跨進(jìn)程和跨計算機(jī)運行

。COM方法可通過網(wǎng)絡(luò)調(diào)用

。在一個進(jìn)程空間中,C++方法的名字必須是唯一的,而COM對象的名字在整個世界中都是唯一的

。COM服務(wù)器可以使用多種不同的語言和在不同的操作系統(tǒng)上編寫,而C++對象通常都使用C++編寫

以下再談一下COM和C++的這些不同對于編程者有何意義。

COM可以跨進(jìn)程運行

在COM中,編程者可在其它的進(jìn)程中或者網(wǎng)絡(luò)中的任何機(jī)器上創(chuàng)建對象。雖然在許多情況下你都無需這樣做,不過,這種可能性意味著你不能通過普通C++的new句法創(chuàng)建一個COM對象,通過本地的程序來調(diào)用它的方法也是不足夠的。

要創(chuàng)建一個COM對象,某些執(zhí)行的實體(一個EXE或者服務(wù))將必須執(zhí)行遠(yuǎn)程的內(nèi)存分配和對象創(chuàng)建。這是一個非常復(fù)雜的任務(wù)。遠(yuǎn)程的含義是指在另一個進(jìn)程內(nèi)或者另一個進(jìn)程上。這個問題是通過稱為COM服務(wù)器的概念來解決的。它必須與客戶端維持緊密的通信。

COM方法可以通過網(wǎng)絡(luò)調(diào)用

如果你可以訪問網(wǎng)絡(luò)上某臺機(jī)器,而你想要使用的某個對象的COM服務(wù)器已經(jīng)被安裝在該機(jī)器上,你就可以在那臺機(jī)器上創(chuàng)建COM對象。當(dāng)然,你必須要有相應(yīng)的權(quán)限,并且那臺機(jī)器上已經(jīng)進(jìn)行了正確的設(shè)置。

由于你的COM對象并不一定在本機(jī)上,因此你需要一個方法來“指向”它,即使它存放在另一臺機(jī)器的內(nèi)存中。在技術(shù)上,沒辦法做到這一點。不過它可以通過一個全新級別的對象來模擬。COM使用的其中一個方法是一個稱為proxy/stub的概念,我們將會在后面更詳細(xì)地討論proxy/stubs。

另一個重要的問題是在COM客戶端和它的COM服務(wù)器間傳送數(shù)據(jù)。數(shù)據(jù)在進(jìn)程、線程之間或者一個網(wǎng)絡(luò)上傳送的時候,它就被稱為“Marshalling”。proxy/stub負(fù)責(zé)為你維護(hù)Marshalling。COM還可以使用類庫和Automationmarshaller來配置接口的某些數(shù)據(jù)類型。Automationmarshaller無需特別為每個COM服務(wù)器建立。

COM對象在世界上必須是唯一的

整個世界?看來有點夸張,不過考慮到Internet是一個世界范圍的網(wǎng)絡(luò),即使在單一某臺計算機(jī)上,COM也必須考慮到這個可能性。唯一是一個問題。在C++的全部類庫中,這個問題是通過編譯器完成的。編譯器可以看到一個程序中每個類的定義,并且匹配它的所有引用,以確保它們嚴(yán)密符合該類。編譯器也要確保每個類的名字是唯一的。在COM中也必須有一個好的方法來得到類似嚴(yán)密的匹配。即使在世界范圍的網(wǎng)絡(luò)上,COM也要確保每個對象的名字是唯一的。這個問題是通過一個稱為GUID的概念來解決的。

COM是語言無關(guān)的

COM服務(wù)器可以用不同的語言和在完全不同的操作系統(tǒng)上編寫。COM對象可以通過遠(yuǎn)程訪問。遠(yuǎn)程是指它可以在一個不同的線程、進(jìn)程或者甚至一個不同的機(jī)器上。另一臺機(jī)器可以運行一個不同的操作系統(tǒng)。這就需要一個好的方法來在網(wǎng)絡(luò)的機(jī)器間傳送參數(shù)。這個問題是通過創(chuàng)建一個新的方法來指定客戶和服務(wù)器間的接口來解決。還有一個稱為MIDL(MicrosoftInterfaceDefinitionLanguage,微軟接口定義語言)的新編譯器。該編譯器可指定服務(wù)器和客戶端接口的一般方法。MIDL定義COM對象、接口、方法和參數(shù)。

COM詞匯

我們碰到的其中一個問題是要記住兩套術(shù)語。你可能已經(jīng)熟悉C++和一些面向?qū)ο蟮男g(shù)語。以下的表格將COM和傳統(tǒng)術(shù)語間類似的地方列了出來。

概念傳統(tǒng)的(C++/OOP)COM客戶端一個從某個服務(wù)器請求服務(wù)的程序一個調(diào)用COM方法的程序服務(wù)器一個為其它程序服務(wù)的程序一個讓某個COM客戶得到COM對象的程序接口沒有通過COM調(diào)用的一組函數(shù)的一個指示器類一個數(shù)據(jù)類型定義了一組一起使用的方法和數(shù)據(jù)一個對象的定義,用來實現(xiàn)一個或者多個COM接口,“coclass”也是對象一個類的實例化一個coclass的實例化Marshalling沒有在客戶和服務(wù)器端之間移動數(shù)據(jù)

你會發(fā)現(xiàn)接口和Marshalling的概念在C++模型中是沒有的。在C++中,與接口較為相近的是一個DLL的外部定義。在使用一個緊密結(jié)合(進(jìn)程間)的COM服務(wù)器時,DLL所做的許多事情與COM差不多。Marshalling在C++中也是沒有的,如果你要在進(jìn)程或者計算機(jī)之間拷貝數(shù)據(jù)時,你必須使用一些交互進(jìn)程通信的方法來寫代碼,你可以選擇sockets、剪貼板和mailslots。

接口

在上面我們已經(jīng)多次看到“接口”這個詞,在我的一本字典中是這樣定義一個接口的:

“接口:名詞,是兩個物體或者界面的共有分界”。

這是一個普通的定義。在COM中“接口”有非常特別的含義。COM接口是一個全新的概念,在C++中是沒有的。對于許多人來說,接口的概念在開始時都較難理解。一個接口沒有一個有形的存在。它類似一個抽象類,但不完全一樣。

最簡單地說,接口是函數(shù)的集合。在C++,一個類僅允許有一個接口。這個接口的成員函數(shù)都是該類所有的公有成員函數(shù)。用其它話來說,接口是類的公共可見部分。在C++中一個接口和一個類幾乎沒有任何的區(qū)別,以下就是C++類的一個例子:classyyy{

public:

intDoThis();

private:

voidHelper1();

intcount;

intx,y,z;

};某人使用這個類時,他只可訪問到pubilc的成員(這里我們忽略了protected成員和繼承)。他不能調(diào)用Helper1,也不能使用任何的private變量。對于類的使用者來說,它的定義其實是:

classyyy{

intDoThis();

};

類的public子集是外部的“接口”。接口將類的內(nèi)部和使用者隔離開來。

C++類似的部分就只有這么多,COM接口并不是一個C++的類,COM接口和類擁有自己特別的一套規(guī)則和協(xié)定。

COM允許一個coclass(COM類)擁有多個接口,每個接口擁有自己的名字和函數(shù)集。這樣做便可得到更為復(fù)雜和功能更強(qiáng)的對象。這個概念與C++是完全不同的。(可將多個接口想象為兩個類定義的結(jié)合,當(dāng)然,這種結(jié)合在C++中是不允許的)

接口將客戶和服務(wù)器隔離開來

COM最重要的一條規(guī)定是你只可通過接口來訪問一個COM對象。通過接口,客戶端的程序與服務(wù)器的執(zhí)行完全隔離開來。這是非常重要的一點。

客戶端程序?qū)τ趯崿F(xiàn)COM的COM對象或者C++類一無所知。它只能看到接口。接口就象COM對象的一個窗口。接口的設(shè)計者只讓客戶看到設(shè)計者希望展示的部分。圖一展示了客戶是如何通過接口來訪問一個COM對象的。

*****圖一*****圖中一個小圓圈連接一條桿的符號,是表示一個COM接口的通常方法。接口還有許多重要的規(guī)定,對于理解COM的詳細(xì)運作是很重要的,我們將在下面談到。現(xiàn)在我們只集中談接口的主要概念。

COM接口的形象化

這里將以另一種方式來形象化一個接口。在這個部分中,我們將不用任何的C++術(shù)語來介紹一個COM接口。我們將以一個抽象的形式來了解一個接口。想象一下一個“汽車”對象。對于現(xiàn)實中的所有汽車對象,你知道它有一個“駕駛”的接口,可讓你控制汽車向左、向右,或者加速、減速。駕駛接口的成員函數(shù)包括有“左”、“右”、“加速”、“減速”、“向前”和“向后”。不少的汽車安裝了收音機(jī),因此還有一個“收音機(jī)”的接口。收音機(jī)的接口可以是“開”、“關(guān)”、“大聲”、“柔和”、“下一個臺”和“前一個臺”。DrivingRadio

Left()On()

Right()Off()

Slower()Louder()

Faster()Softer()

Forward()NextStation()

Reverse()PrevStation()有許多不同種類的汽車,它們不一定有收音機(jī)。因此它們雖然支持駕駛的接口,但沒有實現(xiàn)收音機(jī)的接口。對于所有擁有收音機(jī)的汽車,收音器的功能都是一樣的。一個人可以駕駛一輛沒有收音機(jī)的汽車,但他不能聽到音樂。對于帶有收音機(jī)的汽車,還擁有收音機(jī)的接口。

對于COM類,COM支持這個同樣形式的模型。一個COM對象可支持一個接口的集合,每個接口都擁有自己的名字。對于你自己創(chuàng)建的COM對象,你可以只使用單一一個COM接口。不過對于許多現(xiàn)有的COM對象,根據(jù)它們支持的特性,可支持多個COM接口。

另一個重要的區(qū)別是駕駛接口并不是汽車。駕駛接口并沒有告訴你任何關(guān)于車的制動裝置、車輪或者引擎等的事情。例如你可使用駕駛接口的加速和減速方法,而不需關(guān)心減速是如何實現(xiàn)的。汽車使用水力或者空氣剎車也是不重要的。

組件的形象化

在你建立一個COM對象時,你會非常關(guān)注接口是如何工作的,對于接口的使用者,卻不用關(guān)心它的實現(xiàn)。就象一輛車的制動一樣,用戶只關(guān)心接口的工作,而無需知道接口后面的細(xì)節(jié)。

隔離接口和實現(xiàn)對于COM是至關(guān)緊要的。通過將它的實現(xiàn)和接口隔離開,我們可以建立組件。組件可被替換和重用。兩者均可簡化和增加對象的可用性。名字的唯一性

COM接口的名字是唯一的。這就是說,編程者如果訪問某個名字的接口,他可以認(rèn)為在實現(xiàn)接口的所有COM對象中,該接口的成員函數(shù)和參數(shù)都將是完全一樣的。因此,在我們上面的例子中,稱為“駕駛”和“收音機(jī)”的接口,在任何實現(xiàn)它們的COM對象中,都將擁有完全一樣的成員函數(shù)。如果你想要改變一個接口的成員函數(shù),你必須用一個新的名字創(chuàng)建一個新接口。

所有接口的源頭--IUnknown

通常介紹COM都是從講述IUnknown接口開始的。IUnknown是所有COM接口的基礎(chǔ)。雖然它挺重要,不過就算你不了解IUnknown,你也可以明白接口的概念。IUnknown的實現(xiàn)被更高級別的抽象隱藏起來,我們也使用這些抽象來建立自己的COM對象。不過,太過關(guān)注IUnknown將會令你感到迷惑。我們將從一個更高的級別來處理它,從而令你更容易理解它的概念。

IUnknown類似C++中的抽象基類。所有的COM接口必須由IUnknown繼承而來。IUnknown處理接口的創(chuàng)建和管理。IUnknown的方法被用來創(chuàng)建、引用計數(shù)和釋放一個COM對象。所有的COM接口都實現(xiàn)這3個方法,它們被COM內(nèi)部使用來管理接口。你可能從來不會自己調(diào)用這3個方法。

一個典型的COM對象

現(xiàn)在我們要將這些新的概念放在一起,并且介紹一個典型的COM對象和一個要訪問該對象的程序。

想象一下,如果你要創(chuàng)建一個最簡單的COM對象。這個對象支持一個單一的接口,并且該接口只含有一個單一的函數(shù)。這個函數(shù)的功能也很簡單--只是發(fā)出beep聲。當(dāng)一個編程者創(chuàng)建該COM對象,并且調(diào)用該對象支持的單一接口中的成員函數(shù)時,COM對象存在的機(jī)器將會發(fā)出beep聲。更進(jìn)一步的是,你要在一臺機(jī)器上運行這個COM對象,但是從網(wǎng)絡(luò)上的另一臺機(jī)器來調(diào)用它。

為了創(chuàng)建這個簡單的COM對象,你必須做以下的事情:

。你必須創(chuàng)建該COM對象,并且給它一個名字。該對象將會在一個相關(guān)的COM服務(wù)器中實現(xiàn)

。你需要定義該接口并且給它起一個名字

。你需要定義接口中的函數(shù)并且給它起一個名字

。你將需要安裝COM服務(wù)器

在這個例子中,我將該COM對象稱為Beeper,接口為IBeep,函數(shù)為Beep。你首先要馬上面對的一個問題是命名這些對象,事實上,所有的機(jī)器都支持多個COM服務(wù)器,每個都可包含有一個或者多個COM對象,而每個COM對象都要實現(xiàn)一個或者多個的接口。這些服務(wù)器是經(jīng)由不同的編程者創(chuàng)建的,這樣就有可能選擇一樣的名字。同樣,COM對象有一個或者多個的命名接口,它們同樣是由多個編程者隨意創(chuàng)建的,這樣也有同名的問題。因此,我們必須要想個方法來防止名字的沖突。一個稱為GUID(GloballyUniqueIDentifier,全球唯一標(biāo)識器)的方法可解決這個問題。

如何做到唯一--GUID

要確保一個名字是唯一的,僅有兩個方法:

1。通過一些準(zhǔn)政府組織來登記名字;

2。使用一個特別的算法來產(chǎn)生唯一的數(shù)字,這些數(shù)字可被認(rèn)為在世界范圍內(nèi)是唯一的第一個方法與網(wǎng)絡(luò)上的域名管理一樣。它的問題是你必須付$50來登記一個新的名字,而且要令登記生效,你要等幾個星期。

第二個方法對于開發(fā)者更為方便。如果你可以發(fā)明一個算法,每次人們調(diào)用它都可以產(chǎn)生一個可被認(rèn)為是唯一的名字,那么這個問題就解決了。事實上,這個問題已經(jīng)被開放軟件基金會提到(OpenSoftwareFoundation,OSF)。OSF有一個算法,可將一個網(wǎng)絡(luò)地址、時間(100納秒遞增)和一個計數(shù)器結(jié)合,得到一個128位的唯一數(shù)字。

2的128次方是一個非常大的數(shù)字。通過它,你可以識別由宇宙開始到現(xiàn)在的每個100納秒--而且還會剩下39位。OSF將它稱為UUID,意思是UniversallyUniqueIdentifier,在COM的命名標(biāo)準(zhǔn)上,微軟使用同樣的算法。在COM中微軟將它重命名為GloballyUniqueIdentifier。

GUID的記錄通常采用16進(jìn)制。不過這沒有關(guān)系,一個典型的GUID類似為:

"50709330-F93A-11D0-BCE4-204C4F4F5020"typedefstruct_GUID

{

unsignedlongData1;

unsignedshortData2;

unsignedshortData3;

unsignedcharData4[8];

}GUID;GUID的普通讀音是“gwid”,與“squid”的發(fā)音類似。一些人也讀為“goo-wid”。

GUID通過一個稱為GUIDGEN的程序產(chǎn)生。在GUIDGEN中,你只要按下一個按鈕就可以產(chǎn)生一個新的GUID。你可以認(rèn)為你產(chǎn)生的每個GUID都是唯一的,不管你產(chǎn)生了多少個,或者世界上有多少人產(chǎn)生它。這個認(rèn)定可以成立是基于以下的原因:Internet上的所有機(jī)器都有一個唯一的地址。因此,你的機(jī)器最好是處在網(wǎng)絡(luò)上。不過,即使你沒有網(wǎng)絡(luò)地址,GUIDGEN也將會產(chǎn)生一個,但是這樣就會令唯一性的機(jī)率降低。

COM對象和COM接口都有一個GUID來標(biāo)識自己。因此我們?yōu)樵搶ο筮x用的名字“Beeper”是沒有關(guān)系的。對象是通過它的GUID來命名的。我們將該對象的GUID稱為它的classID。然后我們就可以使用一個#defind或者一個常數(shù)來令Beeper的名字和該GUID相關(guān),這樣我們就無需在代碼中都使用這個128位的值。同樣接口也將擁有一個GUID。要注意的是許多由不同的編程者來創(chuàng)建的不同COM對象將支持同樣的IBeep接口,而它們都將全部使用同樣的GUID來命名它。如果沒有同樣的GUID,COM就認(rèn)為這是一個不同的接口。GUID就是它的名字。

一個COM服務(wù)器

COM服務(wù)器就是實現(xiàn)COM接口和類的程序。COM服務(wù)器有三個基本的配置。

。進(jìn)程中或者DLL服務(wù)器

。Stand-aloneEXE服務(wù)器

?;赪indowsNT的服務(wù)

COM對象都是一樣的,與服務(wù)器的類型無關(guān)。COM接口和coclasses將不會關(guān)心當(dāng)前使用的服務(wù)器類型。對于客戶端程序來說,服務(wù)器的類型幾乎是完全透明的。不過對于寫真正的服務(wù)器端來說,每種配置都將會有明顯的不同:

進(jìn)程中的服務(wù)器是作為動態(tài)連接庫(DLL)實現(xiàn)的。這意味著該服務(wù)器在運行時被動態(tài)地放進(jìn)你的進(jìn)程中

COM服務(wù)器將成為你應(yīng)用中的一部分,而COM操作在應(yīng)用的線程中進(jìn)行。事實上,許多的COM對象都是以這種方式實現(xiàn)的,因為性能很好--一個COM函數(shù)調(diào)用的系統(tǒng)開銷很小,但你可以得到COM所有的設(shè)計和重用的好處

COM自動處理載入和卸下該DLL

一個進(jìn)程外的服務(wù)器令客戶和服務(wù)端的區(qū)分更明顯。該類服務(wù)器作為一個獨立的可執(zhí)行(EXE)程序運行,因此處在一個私有的進(jìn)程空間中。EXE服務(wù)器的啟動和停止在Windows中服務(wù)管理器中進(jìn)行(SCM)。COM接口的調(diào)用通過內(nèi)部的進(jìn)程通信技術(shù)來處理。服務(wù)器可以運行在本地的機(jī)器,或者在一個遠(yuǎn)程的計算機(jī)上。如果服務(wù)器在一個遠(yuǎn)程的計算機(jī)上,我們稱它為“DistributedCOM,分布式的COM”,或者DCOM。

WindowsNT提出了一個服務(wù)的概念。一個服務(wù)是指該程序由WindowsNT自動管理,與桌面的用戶無關(guān)。這意味著服務(wù)可以在啟動時自動開始,并且即使是沒有人登錄到WindowsNT中,也可以自動運行。服務(wù)提供了一個極好的方法來運行COM服務(wù)器應(yīng)用。

還有第四種的服務(wù)器,稱為“surrogate”,這是一個可允許進(jìn)程中的服務(wù)器在遠(yuǎn)程運行的程序。對于要建立一個可通過網(wǎng)絡(luò)訪問的基于DLL的COM服務(wù)器,surrogate是很有用的。

客戶端和服務(wù)器端的交互

在COM中,客戶端程序驅(qū)動所有的事情。服務(wù)器是被動的,只響應(yīng)客戶的請求。這意味著對于客戶的個別方法調(diào)用,COM服務(wù)器以一個同步的方式運作

??蛻舳说某绦騿臃?wù)器

。客戶端請求COM對象和接口

??蛻舳税l(fā)起所有的方法調(diào)用到服務(wù)器

??蛻舳酸尫欧?wù)器的接口,允許服務(wù)器關(guān)閉

這個區(qū)別是重要的。有各種不同的方法可模擬服務(wù)器到客戶的調(diào)用,不過它們都難以實現(xiàn),并且都相當(dāng)復(fù)雜(這個被稱為回叫)。通常沒有客戶的請求,服務(wù)器不做任何的事情。

以下就是COM客戶和服務(wù)器之間的一個典型的交互

客戶請求

請求訪問一個特別的COM接口,特別的COM類和接口(通過GUID)

服務(wù)器響應(yīng)

。啟動服務(wù)器(如果需要)。如果是一個進(jìn)程內(nèi)的服務(wù)器,DLL將被載入??蓤?zhí)行的服務(wù)器將由SCM運行

。創(chuàng)建請求的COM對象

。創(chuàng)建到COM對象的一個接口

。增加激活接口的引用計數(shù)

。返回該接口給客戶

客戶請求

調(diào)用接口的一個方法

服務(wù)器響應(yīng)

執(zhí)行一個COM對象的方法

客戶請求

釋放接口

服務(wù)器響應(yīng)

減少接口引用的數(shù)目

如果引用計數(shù)為0,將會刪除該COM對象

如果沒有活動的連接,關(guān)閉服務(wù)器。某些服務(wù)器不會關(guān)閉自身

如果你要了解COM,你必須使用一個以客戶為中心的方法

三、總結(jié)

我們嘗試從幾個不同的方面來了解COM。C++是COM的原始語言,不過重要的是我們要了解它們的不同。COM有許多類似C++的地方,不過它也有很大的不同。在客戶和服務(wù)器間通信方面,COM提供了一個全新的方式。

接口是COM最為重要的概念之一。所有的COM交互都經(jīng)由接口進(jìn)行。由于在C++中,并沒有一個直接與接口對應(yīng)的事物,因此有點難以掌握。我們還介紹了GUID的概念。GUID在COM中是普遍存在的,并且提供了一個極好的方式來在一個大型網(wǎng)絡(luò)中標(biāo)識一個實體。

COM服務(wù)器是傳送COM組件的媒介。所有的事情都集中在傳送COM組件到一個客戶應(yīng)用上。在以下的章節(jié)中,我們將創(chuàng)建一個簡單的客戶和服務(wù)器應(yīng)用來解釋這些概念。理解最簡單的COM客戶

要理解COM的最直接方法是通過一個客戶應(yīng)用來考察它。COM編程的目的是為了讓客戶應(yīng)用可以得到有用的對象。一旦你理解了客戶,要理解服務(wù)端就變得非常的簡單。相反,同時直接考察服務(wù)端和客戶端是容易令人迷惑的;如果你首先學(xué)習(xí)其細(xì)節(jié)的話,就更加復(fù)雜了。因此,我們首先由最簡單的定義開始:COM客戶是一個使用COM來調(diào)用一個COM服務(wù)器上的方法的程序。這種客戶/服務(wù)關(guān)系的一個最簡單直接的例子是一個用戶界面應(yīng)用(客戶)調(diào)用另一個應(yīng)用(服務(wù)端)的方法。如果該用戶界面應(yīng)用使用COM來調(diào)用這些方法,那么根據(jù)定義,這個用戶界面應(yīng)用就是一個COM客戶。

我們不斷強(qiáng)調(diào)以上的內(nèi)容是有理由的,因為COM服務(wù)器和客戶的分別可以是更為復(fù)雜的。許多時候,應(yīng)用客戶也將是一個COM服務(wù)端,而應(yīng)用的服務(wù)器也可是一個COM客戶。一個應(yīng)用同時是COM客戶和服務(wù)器是很常見的。在這一章中,我們將讓這個區(qū)別最簡單化,涉及的只是一個純COM客戶。

客戶端連接的4個步驟

客戶使用COM與一個服務(wù)器通信時,通常要經(jīng)過4個基本的步驟。當(dāng)然,現(xiàn)實中的客戶端做的事情更多,不過即使它非常復(fù)雜,其核心也是這4個步驟。在這部分中我們將以最低級的方式介紹COM--使用簡單的C++調(diào)用。

以下是我們將要進(jìn)行的4個步驟:

1、初始化COM子系統(tǒng),并且在完成時關(guān)閉它;

2、經(jīng)一個服務(wù)器的特有接口查詢COM

3、執(zhí)行接口上的方法

4、釋放該接口

為了簡單,我們將使用一個極為簡單的COM服務(wù)器。我們已經(jīng)假定服務(wù)器已經(jīng)寫了出來,并且有使用說明。

該服務(wù)器擁有一個稱為IBeep的接口。該接口只有一個方法,稱為Beep。Beep接收一個參數(shù):持續(xù)時間。以下我們將寫一個最簡單的COM客戶來連接該服務(wù)器,并且調(diào)用Beep的方法。

以下就是實現(xiàn)這4個步驟的C++代碼。這是一個真正可以工作的COM客戶應(yīng)用。#include"..\BeepServer\BeepServer.h"

//GUIDSdefinedintheserver

constIIDIID_IBeepObj=

{0x89547ECD,0x36F1,0x11D2,

{0x85,0xDA,0xD7,0x43,0xB2,0x32,0x69,0x28}};

constCLSIDCLSID_BeepObj=

{0x89547ECE,0x36F1,0x11D2,

{0x85,0xDA,0xD7,0x43,0xB2,0x32,0x69,0x28}};

intmain(intargc,char*argv[])

{

HRESULThr;//COMerrorcode

IBeepObj*IBeep;//pointertointerface

hr=CoInitialize(0);//initializeCOM

if(SUCCEEDED(hr))//macrotocheckforsuccess

{

hr=CoCreateInstance(

CLSID_BeepObj,//COMclassid

NULL,//outerunknown

CLSCTX_INPROC_SERVER,//serverINFO

IID_IBeepObj,//interfaceid

(void**)&IBeep);//pointertointerface

if(SUCCEEDED(hr))

{

//callmethod

hr=IBeep->Beep(800);

//releaseinterface

hr=IBeep->Release();

}

}

//closeCOM

CoUninitialize();

return0;

}在編譯服務(wù)器時,頭部的“BeepServer.h”會被創(chuàng)建。BeepServer是一個進(jìn)程內(nèi)的COM服務(wù)器,我們將在下一節(jié)再詳細(xì)討論。在編譯該服務(wù)器時,開發(fā)工具包還會自動產(chǎn)生幾個頭文件。這個特別的頭文件定義了接口IBeepObj。編譯服務(wù)器還會在該程序的頂部產(chǎn)生GUID。我們將它從服務(wù)器工程的頂部拷貝了過來。

以下我們將詳細(xì)討論這4個步驟。初始化COM子系統(tǒng):

這是一個簡單的步驟。我們需要使用的COM方法是CoInitialize():

CoInitialize(0);

該函數(shù)接收一個參數(shù),而該參數(shù)通常是一個0,這是它的起源OLE的一個慣例。CoInitialize函數(shù)初始化COM庫。在你做其它的處理之前,你需要調(diào)用這個函數(shù)。在更為專業(yè)的應(yīng)用中,我們將會使用擴(kuò)展的版本--CoInitializeEx。

在完成COM的所有處理后,你要調(diào)用CoUnInitialize()。這個函數(shù)將會卸載COM庫。我通常在自己的MFC應(yīng)用中的InitInstance()和ExitInstance()函數(shù)中包含這些調(diào)用。

大部分的COM函數(shù)返回一個稱為HRESULT的錯誤代碼。這個錯誤的代碼包含了幾個字段,給出了錯誤嚴(yán)格、簡要定義和錯誤的類型。我們使用SUCCEDDED宏,因為COM可以返回幾個不同的成功代碼。只是檢驗普通的成功代碼(S_OK)將是不夠周密的。我們將在后面更為詳細(xì)地討論HRESULT。

通過一個特別的接口查詢COM

COM客戶端感興趣的是它可以調(diào)用的函數(shù),在COM中,你可以通過接口來訪問一套有用的函數(shù)。接口最簡單的形式就是函數(shù)的一個集合。當(dāng)我們得到COM服務(wù)器的一個接口時,我們就得到了一個指向一套函數(shù)的指針。

通過調(diào)用CoCreateInstance()函數(shù),你就可以得到一個接口的指針。這是一個非常強(qiáng)大的函數(shù),它可與COM子系統(tǒng)進(jìn)行交互,并做以下的事情:

查找服務(wù)器

開始、載入或者連接到服務(wù)器

在服務(wù)器端創(chuàng)建一個COM對象

返回指向COM對象接口的一個指針

對于查找和訪問接口,有兩種數(shù)據(jù)類型是很重要的,它們是:CLSID和IID。它們都是GloballyUniqueID's(GUID's)。GUID's用作唯一辨認(rèn)所有的COM類和接口。

為了得到某個特別的類和接口,你需要它的GUID。要得到GUID,有許多方法。通常我們可以由服務(wù)器的頭文件得到CLSID和IID。在我們的例子中,我們在源代碼的開始部分使用#defind語句定義了GUID。通過接口的一般名字來查找GUID也很方便的。

讓我們得到接口指針的函數(shù)是CoCreateInstance。hr=CoCreateInstance(

CLSID_BeepObj,//COMclassid

NULL,//outerunknown

CLSCTX_INPROC_SERVER,//serverINFO

IID_IBeepObj,//interfaceid

(void**)&IBeep);//pointertointer第一個參數(shù)是一個GUID,它可唯一指定客戶端需要使用的COM類。GUID或者CLSID是COM類的標(biāo)識符。世界上的每個COM類都有自己唯一的CLSID。COM將使用該ID來查找可產(chǎn)生請求COM對象的服務(wù)器。一旦連接到服務(wù)器,將會創(chuàng)建該對象。

第二個參數(shù)是一個指針,它指向“outerunknown”。我們不會使用這個參數(shù),因此傳送一個NULL。在涉及到“aggregation”(集合)概念時,outerunknown是很重要的。aggregation可讓一個接口直接調(diào)用另一個COM接口而無需通知客戶端。aggregation和containment是接口用來調(diào)用其它接口的兩個方法。

第三個參數(shù)定義COM類的Context或者CLSCTX。該參數(shù)控制服務(wù)器的范圍。我們可以通過它來控制服務(wù)器是進(jìn)程內(nèi)的服務(wù)器,還是一個EXE或者是在遠(yuǎn)程的計算機(jī)上。CLSCTX是一個位掩碼,因此你可以混合幾個值。這里我們使用的是CLSCTX_INPROC_SERVER--該服務(wù)器將運行在本地的計算機(jī),并且作為一個DLL連接到客戶。由于進(jìn)程內(nèi)的服務(wù)器是最容易實現(xiàn)的,因此我們在這個例子中選用它來講解。

通常客戶端都不用關(guān)心服務(wù)器是如何實現(xiàn)的。這時它將使用CLSCTX_SERVER的值,該服務(wù)器可以是一個本地的或者是進(jìn)程內(nèi)的。

接著是接口的標(biāo)識符或者IID。這是另一個GUID--用來標(biāo)識我們請求的接口。我們請求的IID必須是存在的,即被由CLSID指定的COM類支持。再次,IID的值通常由一個頭文件提供,或者使用接口名查找出來。

最后的參數(shù)是指向一個接口的指針。CoCreateInstance()將創(chuàng)建所請求的類對象和接口,并且返回一個指向接口的指針。這個參數(shù)也是CoCreateInstance調(diào)用的目的。然后我們就可以使用該接口指針來調(diào)用服務(wù)器上方法。

執(zhí)行接口上的一個方法

CoCreateInstance()使用COM來創(chuàng)建一個指向IBeep接口的指針。我們可以假設(shè)接口是指向一個普通C++類的指針,不過事實上并不是。實際上,該接口指針指向一個稱為VTABLE的結(jié)構(gòu),它是一個函數(shù)地址表。我們可以使用->操作符來訪問接口指針。

由于我們的例子使用一個進(jìn)程內(nèi)的服務(wù)器,它將作為一個DLL載入到我們的程序中。忽略接口對象的細(xì)節(jié),得到該接口的目的是用來調(diào)用服務(wù)器上的一個方法。

hr=IBeep->Beep(800);

Beep()在服務(wù)器上執(zhí)行--它令計算機(jī)發(fā)出Beep聲。有許多簡單的方法可讓一部計算機(jī)發(fā)出beep聲。如果我們擁有一個遠(yuǎn)程的服務(wù)器,它運行在另一臺計算機(jī)上,該機(jī)器將發(fā)出beep聲。

接口的方法通常都帶有參數(shù)。這些參數(shù)必須是屬于COM支持的類型之一。有不少的規(guī)定來控制接口支持的參數(shù)。我們將在MIDL的部分更詳細(xì)地討論這個問題,MIDL是COM的接口定義工具。

釋放接口

C++的一個規(guī)則是所有分配的事物都應(yīng)該反分配。由于我們并不是使用new來創(chuàng)建接口,因此我們不能使用delete來刪除它。所有的COM接口都擁有一個稱為Release()的方法來斷開對象,并且刪除它。釋放一個接口是很重要的,因為它可允許服務(wù)器來清除它。如果你使用CoCreateInstance來創(chuàng)建一個接口,你將需要調(diào)用Release()。

總結(jié)

在這一節(jié)中我們講解了一個簡單的COM客戶。COM是一個客戶驅(qū)動的系統(tǒng)。所有都是為了令客戶更容易得到組件對象。相信該客戶程序的簡單性會給你留下一個深刻的印象。這里定義的4個步驟可讓你使用大量的組件和大范圍的應(yīng)用。

其中的一些步驟是基本的,例如CoInitialize()和CoUninitialize()。其中的一些初次看來沒有太多的作用。不過從更高的級別來看,懂得這些也是重要的。我們將在以后的例子中進(jìn)一步談及。

VisualC++Version5和6通過使用“智能指針”和#import令客戶端的程序更加簡化。在這個例子中我們使用的是一個低級的C++格式,以便更好地解釋這個概念。我們將在后面的部分討論智能指針和import。

在下一部分中,我們將建立一個簡單的進(jìn)程內(nèi)服務(wù)器去管理IBeep接口。我們將在后面的章節(jié)繼續(xù)深入討論接口和激活的細(xì)節(jié)。理解簡單的DCOM服務(wù)器

以上我們主要講解了如何通過一個客戶應(yīng)用使用COM。對于客戶來說,COM的編程技巧是相當(dāng)簡單的??蛻舳说膽?yīng)用向COM子系統(tǒng)請求一個特定的組件,服務(wù)器端將其傳送過來。

實際上,對于后臺的組件管理工作,還需要寫很多的代碼。真正的對象實現(xiàn)需要使用復(fù)雜的系統(tǒng)組件和標(biāo)準(zhǔn)的應(yīng)用模塊。就算是使用MFC,也是很復(fù)雜的。大多數(shù)的專業(yè)編程者都不會花時間來研究這個過程。自從COM的標(biāo)準(zhǔn)發(fā)布以來,很快就令我們明白到讓開發(fā)者來自己寫這些代碼是不現(xiàn)實的。

當(dāng)你查看實現(xiàn)COM的真正代碼時,你會發(fā)現(xiàn)其中大部分都是重復(fù)的。對于這類復(fù)雜的問題,傳統(tǒng)C++的解決之道是創(chuàng)建一個COM類庫。實際上,MFCOLE類提供了大部分的COM特性。

不過對于COM組件來說,MFC和OLE并不是一個好的選擇,有幾個理由。隨著ActiveX和微軟Internet策略的推出,COM對象應(yīng)該要非常的緊湊和快速。ActiveX需要COM對象可以經(jīng)過網(wǎng)絡(luò)相當(dāng)快地被復(fù)制。如果你使用MFC較多,就會發(fā)現(xiàn)它實在太大了(特別是在靜態(tài)鏈接時)。通過網(wǎng)絡(luò)來傳送巨大的MFC對象是不現(xiàn)實的。

或許通過MFC/OLE方法來實現(xiàn)COM組件的最大問題是復(fù)雜性。OLE編程是復(fù)雜的,并且大部分的編程者都不會在上面走得很遠(yuǎn)。有大量關(guān)于OLE的書,這都說明它是非常難以掌握的。

由于OLE的開發(fā)有不少的難度,因此微軟創(chuàng)建了一個稱為ATL(ActiveTemplateLibrary)新工具。對于COM編程來說,ATL是當(dāng)前最實用的工具。實際上,如果你對其背后的東西沒有興趣,使用ATL向?qū)砭帉慍OM服務(wù)器是相當(dāng)簡單的。

這里介紹的例子都是通過ATL和ATL應(yīng)用向?qū)韯?chuàng)建的。這一節(jié)我們將講解如何建立一個基于ATL的服務(wù)器,并對向?qū)Мa(chǎn)生的代碼給出了一個摘要。關(guān)于代碼

有一點你要花時間去習(xí)慣,編寫ATL服務(wù)器和傳統(tǒng)的編程是不一樣的。COM服務(wù)器其實是幾個獨立組件的協(xié)作構(gòu)成的,包括有:

。你的應(yīng)用

。COM子系統(tǒng)

。ATL模板類

?!癐DL”代碼和MIDL產(chǎn)生的“C”頭文件和程序

。系統(tǒng)寄存器(注冊表)

要將一個基于ATL的COM應(yīng)用作為一個整體看是挺困難的。即使你知道它正在做什么,還有很大一塊應(yīng)用你是看不到的。真正服務(wù)器中的大部分邏輯都深入隱藏在ATL的頭文件中。你將不會找到一個單一的用來管理和控制服務(wù)器的main()函數(shù)。你只找到一個用來調(diào)用基本ATL對象的瘦外殼。

在以下的部分中,我們將把所有這些令服務(wù)器運作的部分放在一起。首先我們會使用ATLCOM應(yīng)用向?qū)韯?chuàng)建服務(wù)器。第二步我們將加入一個COM對象和一個方法。我們將寫一個進(jìn)程內(nèi)的服務(wù)器,因為它是最容易實現(xiàn)的COM服務(wù)器之一。一個進(jìn)程內(nèi)的服務(wù)器也不用建立一個proxy和stub對象。

建立一個基于DLL(進(jìn)程內(nèi))的COM服務(wù)器

一個進(jìn)程內(nèi)的服務(wù)器就是一個會在運行時載入到你程序中的COM類。換句話說,就是一個動態(tài)鏈接庫(DLL)中的COM對象。用傳統(tǒng)的觀點來看,一個DLL并不是一個真正的服務(wù)器,因為它會直接載入到客戶的地址空間中。如果你熟悉DLL,你已經(jīng)知道了許多關(guān)于COM對象如何載入和映射到調(diào)用程序的知識。

通常在調(diào)用LoadLibrary()時,DLL就會被載入。在COM中,你無需顯式調(diào)用LoadLibrary()。在客戶端的程序調(diào)用CoCreateInstance()時,所有的處理都會自動啟動。CoCreateInstance需要的其中一個參數(shù)是你要使用的COM類的GUID。當(dāng)服務(wù)器在編譯時創(chuàng)建時,它就會登記了所有它支持的COM對象。當(dāng)客戶端需要該對象時,COM找到服務(wù)器DLL,并且自動裝載它。一旦載入,DLL就擁有了創(chuàng)建COM對象的類庫。

CoCreateInstance()返回一個指向COM對象的指針,它是用來調(diào)用方法的(再這里的例子中,這個方法就是被稱為Beep()的方法)。COM的一個便利之處是DLL可以在不需要的時候被自動卸載。在對象被釋放和CoUninitialize()被調(diào)用后,F(xiàn)reeLibrary()將會被調(diào)用來卸載服務(wù)器DLL。

如果你對以上的都不熟悉也不要緊。要使用COM,你不需要知道關(guān)于DLL的任何知識。你所要做的是調(diào)用CoCreateInstance()。COM的其中一個好處是它隱藏了這些細(xì)節(jié),因此你無需擔(dān)心此類問題。

進(jìn)程內(nèi)的COM服務(wù)器有優(yōu)點也有缺點。如果動態(tài)鏈接是你的系統(tǒng)設(shè)計中的重要一環(huán),那么你將發(fā)現(xiàn)COM可提供一個極好的方式來管理DLL。一些有經(jīng)驗的編程者會將所有他們的DLL都寫成為進(jìn)程內(nèi)的COM服務(wù)器。COM處理所有涉及載入、卸載的雜事,而輸出DLL函數(shù)和COM函數(shù)調(diào)用只有很少的系統(tǒng)開銷。

我們選擇一個進(jìn)程內(nèi)服務(wù)器的主要理由就更簡單了:它可令例子更加簡單。我們不必關(guān)心如何啟動遠(yuǎn)程的服務(wù)器(EXE或者服務(wù)),因為我們的服務(wù)器將會在需要的時候自動載入。我們也無需建立一個proxy/stubDLL來做marshalling的工作。

缺點是,由于進(jìn)程內(nèi)的服務(wù)器與我們的客戶綁定很緊密,因此COM許多重要的“分布”特性沒有展現(xiàn)出來。一個DLL服務(wù)器和它的客戶共享內(nèi)存,而一個分布的服務(wù)器將令客戶端更加隔離開來。在一個分布的客戶和服務(wù)器間傳送數(shù)據(jù)的處理被稱為marshaling。marshaling在COM上的利用是受到限制的,而在進(jìn)程內(nèi)的服務(wù)器中,我們無需關(guān)心這些。使用ATL向?qū)?chuàng)建服務(wù)器

為了讓你理解COM的基本規(guī)則,我們將創(chuàng)建一個非常簡單的COM服務(wù)器。該服務(wù)器只有一個方法--Beep()。這個方法只是發(fā)出Beep的聲音--一個不是很有用的方法。我們將要做的是設(shè)置該服務(wù)器所有部分。一旦該體系設(shè)置完畢,要加入其它有用的方法就變得非常簡單了。

使用ATLAppWizard是一個可快速產(chǎn)生一個COM服務(wù)器的簡單方法。該向?qū)Э勺屛覀冞x擇所有基本的選項,并且將產(chǎn)生我們需要的大部分代碼。以下就是一個產(chǎn)生服務(wù)器的詳細(xì)步驟。在這個程序中,我們將稱該服務(wù)器為BeepServer。所有的COM服務(wù)器都至少要有一個接口,而我們的接口將會被稱為IBeepObj。你可以為你的COM接口取任意的名字,不過如果你想遵循標(biāo)準(zhǔn)的命名傳統(tǒng),你最好使用“I”的前綴來命名它。

注意:很多人在這時可能還會分不清COM“對象”、“類”和“接口”定義之間的區(qū)別。特別是對于C++的編程者,這些術(shù)語開始都不太令人舒服。不過,當(dāng)你了解這些例子后,你的混淆就會減少。在大部分的微軟文檔中,COM類都用“coclass”來表示,以將COM類和普通的C++類區(qū)分開來。

以下就是使用VisualC++version6來創(chuàng)建一個新的COM服務(wù)器的步驟(它與版本5中的幾乎一樣):

1。首先,創(chuàng)建一個新的“ATLCOMAppWizard”項目。由主菜單中選擇File/New。

2。在“New”的對話框中選擇“Projects”標(biāo)簽頁。在項目類型中選擇“ATLCOMAppWizard”。選擇以下的選項并且按下OK。

。項目的名字:BeepServer

。創(chuàng)建新的Workspace

。Location:你的工作目錄

圖一

3。在第一個的AppWizard對話框中我們將創(chuàng)建一個基于DLL(進(jìn)程內(nèi))的服務(wù)器。輸入以下的設(shè)置:

。動態(tài)鏈接庫

。不允許合并proxy/stub代碼

。不支持MFC

圖二

4。按下完成

AppWizard創(chuàng)建一個基于DLL的COM服務(wù)器,并且?guī)в兴斜匾奈募?。雖然該服務(wù)器將可編譯和運行,但它只是一個空殼。為了令它做我們想做的事情,我們將需要一個COM接口和支持該接口的類。我們也必須寫接口中的方法。

加入一個COM對象和一個方法

現(xiàn)在我們繼續(xù)COM對象的定義,包括接口和方法。類的名字是BeepObj,它擁有一個稱為IBeepObj的接口:

1。查看“ClassView”的標(biāo)簽頁。在開始的時候它的列表中僅有唯一一個項目。右擊“BeepServerClasses”項

2。選擇“NewATLObject...”。這個步驟也可通過主菜單來完成。在彈出的菜單項中選擇“NewATLObject”。

圖三

3.在對象向?qū)У膶υ捒蛑羞x擇“Objects”。選擇“SimpleObject”并且按Next。

圖四

4。選擇Names的標(biāo)簽頁。輸入對象的名字:BeepObj。其余所有的選擇都會自動填入標(biāo)準(zhǔn)的名字

圖五

5。按下“Attributes”標(biāo)簽頁并且選擇ApartmentThreading,CustomInterface,NoAggregation。很明顯,在aggregation在這個服務(wù)器中并沒有用到。

圖六

6。按下OK,這將創(chuàng)建Com對象

為服務(wù)器加入一個方法

我們現(xiàn)在已經(jīng)創(chuàng)建了一個空的COM對象。不過,它還是一個無用的對象,因為它并不做任何的事情。我們將創(chuàng)建一個稱為Beep()的簡單方法,它可令系統(tǒng)發(fā)出一次beep聲。我們的COM方法將調(diào)用Win32API函數(shù):Beep()。

1。打開“ClassView”標(biāo)簽。選擇IBeepObj的接口。該接口有由一個類似匙的小圖標(biāo)代表

圖七

2。右擊IBeepObj的接口。由菜單中選擇“AddMethod”。

3。在“AddMethodtoInterface”對話框中,輸入以下的信息并且按下OK。加入“Beep”的方法并且給它一個單一的[in]參數(shù)作為持續(xù)時間。這將是發(fā)beep音的長度,以毫秒計。

圖八

4.“AddMethod”已經(jīng)創(chuàng)建了我們定義方法的MIDL定義。該定義以IDL編寫,并且定義該方法到MIDL編譯器。如果你想看IDL的代碼,雙擊“CalssView”標(biāo)簽頁中的“IBeepObj”接口。這將打開和顯示BeepServer.IDL文件的內(nèi)容。我們沒有必要改變這個文件,我們的接口定義如下所示:interfaceIBeepObj:IUnknown

{

[helpstring("methodBeep")]

HRESULTBeep([in]LONGduration);

};IDL的句法與C++類似。這一行和C++的函數(shù)原型相當(dāng)。我們將在以后談?wù)揑DL的句法。

5?,F(xiàn)在我們將要寫該方法的C++代碼。AppWizard已經(jīng)為我們的C++函數(shù)寫入一個空殼,并且將它加入到頭文件的類定義中(BeepServer.H)。

打開BeepObj.CPP的源文件。找到//TODO:行并且加入到APIBeep函數(shù)的調(diào)用。修改Beep()方法為如下:STDMETHODIMPCBeepObj::Beep(LONGduration)

{

//TODO:Addyourimplementationcodehere

::Beep(550,duration);

returnS_OK;

}6。保存文件,并且編譯該項目

我們已經(jīng)擁有一個完整的COM服務(wù)器了。當(dāng)項目結(jié)束編譯時,你應(yīng)該會看到如下的信息:Configuration:BeepServer-Win32Debug

CreatingTypeLibrary...

Microsoft(R)MIDLCompilerVersion5.01.0158

Copyright(c)MicrosoftCorp1991-1997.Allrightsreserved.

ProcessingD:\UnderCOM\BeepServer\BeepServer.idl

BeepServer.idl

ProcessingC:\ProgramFiles\MicrosoftVisualStudio\VC98\INCLUDE\oaidl.idl

oaidl.idl

.

.

Compilingresources...

Compiling...

StdAfx.cpp

Compiling...

BeepServer.cpp

BeepObj.cpp

GeneratingCode...

Linking...

CreatinglibraryDebug/BeepServer.libandobjectDebug/BeepServer.exp

Performingregistration

BeepServer.dll-0error(s),0warning(s)這意味著開發(fā)工具已經(jīng)完成了以下的步驟:

。執(zhí)行MIDL編譯器來產(chǎn)生代碼和類庫

。編譯源文件

。鏈接項目來創(chuàng)建BeepServer.DLL

。注冊COM組件

。使用RegSvr32注冊DLL,以便在需要的時候自動下載

看看我們產(chǎn)生的項目吧。在我們按下按鈕的時候,AppWizard已經(jīng)產(chǎn)生了文件。如果你觀看“FileView”標(biāo)簽,可看到已經(jīng)產(chǎn)生了以下的文件源文件描述BeepServer.dswProjectworkspaceBeepServer.dsp項目文件BeepServer.plg項目的日志文件,包含了項目建立時的詳細(xì)錯誤信息BeepServer.cppDLL主程序,DLL輸出的實現(xiàn)BeepServer.hMIDL產(chǎn)生文件包含了接口的定義BeepServer.def聲明標(biāo)準(zhǔn)的DLL模塊參數(shù):DllCanUnloadNow,DllGetClassObject,DllUnregisterServerBeepServer.idlBeepServer.dll的IDL源。IDL文件定義了所有的COM組件BeepServer.rc資源文件。這里主要的資源是IDR_BEEPDLLOBJ,它定義了注冊表的腳本,用來將COM的信息載入到寄存器中。

Resource.h微軟DeveloperStudio產(chǎn)生的包含文件StdAfx.cpp預(yù)編譯頭的源Stdafx.h標(biāo)準(zhǔn)的頭BeepServer.tlb由MIDL產(chǎn)生的類庫。該文件是COM接口和對象的二進(jìn)制描述。作為連接一個客戶的一個可選方法,TypeLib是非常有用的BeepObj.cppCBeepObj的實現(xiàn)。該文件包含了所有真正的C++代碼,用來實現(xiàn)COMBeepObj對象中的所有方法。BeepObj.hBeepObjCOM對象的定義BeepObj.rgs注冊表的腳本,用來在注冊表中登記COM組件。在服務(wù)器工程被建立時,注冊是自動進(jìn)行的BeepServer_i.c包含了IID和CLSID的真正定義,這個文件通常被包含在cpp代碼中。

還有另外幾個proxy/stub文件,由MIDL產(chǎn)生。只是幾分鐘,我們就創(chuàng)建了一個完整的COM服務(wù)器應(yīng)用。在沒有向?qū)У娜兆永?,寫一個服務(wù)器將需要數(shù)個小時。向?qū)У娜秉c是我們有了一大塊沒有完全弄懂的代碼。在下面的部分我們將更為詳細(xì)地查看產(chǎn)生的模塊,然后是整個的應(yīng)用。

總結(jié)

整個服務(wù)器的代碼幾乎都是完全由ATL向?qū)Мa(chǎn)生的。我們使用的是一個基于DLL的服務(wù)器。不過這個過程對于所有的服務(wù)器類型幾乎都是一樣的。使用這個架構(gòu),我們可以很快的開發(fā)出一個服務(wù)器應(yīng)用,因為你不需知道許多的細(xì)節(jié)就可以開始工作。

在以下的章節(jié)中,我們將查看進(jìn)程內(nèi)服務(wù)器和ATL的代碼。我們已經(jīng)討論了DCOM的基本要點,了解了如何創(chuàng)建一個簡單的DCOM服務(wù)器和一個相關(guān)的客戶端。你也可以看到這個基本的過程是非常簡單的ATL向?qū)幚砹朔?wù)器端的大部分細(xì)節(jié),要激活服務(wù)器,你只需要在客戶端寫10行左右的代碼就可以了。

接下來我們將討論兩個相關(guān)的主題。首先是創(chuàng)建你自己的COM客戶和服務(wù)器,結(jié)合第一部分我們所學(xué)到的,讓你了解要在自己的代碼中集成一個DCOM服務(wù)器,確實需要做哪些事情。然后我們將快速地看一下由ATL向?qū)Мa(chǎn)生的代碼。

本文的最后將會講解要創(chuàng)建一個分布式的COM服務(wù)器,你需要經(jīng)過的步驟。所謂分布式的COM服務(wù)器,是指該服務(wù)器可以處在網(wǎng)絡(luò)的別處,并且可通過網(wǎng)絡(luò)非常簡單和透明地激活。

創(chuàng)建自己的COM客戶和服務(wù)器

在第一部分的DCOM介紹中,你可以看到要創(chuàng)建COM客戶和服務(wù)器是非常簡單的。只要在客戶和服務(wù)器端寫入幾行代碼就可以產(chǎn)生一個完整的COM應(yīng)用。你現(xiàn)在明白到為什么許多的開發(fā)者在創(chuàng)建一個DLL時會使用COM了--因為僅需要大概5分鐘,就可以設(shè)置好一個進(jìn)程內(nèi)的COMDLL,并且令它工作。

本部分的目的是討論如何創(chuàng)建自己的COM服務(wù)器,并且在你創(chuàng)建的真正應(yīng)用中使用它們。你也會記得,第一部分介紹的客戶端代碼是非常少的。我們將介紹要創(chuàng)建服務(wù)器需要進(jìn)行的基本步驟,然后看看要正確地激活服務(wù)器,你需要在客戶端寫入哪些代碼。

服務(wù)器端

ATL向?qū)Я頒OM服務(wù)器的創(chuàng)建變得非常的簡單。創(chuàng)建一個COMcoclass的第一步是要分離出一個或者多個的功能函數(shù),你要從一個應(yīng)用的代碼主體中分離出這些功能函數(shù)。至于分離出來的目的,可以是多樣的,你可能是想讓該函數(shù)可以跨越多個應(yīng)用重新使用,也可能是讓一個隊伍的編程者更容易地分離出各個獨立的工作組,或者是讓代碼的開發(fā)和維護(hù)變得更加的簡單。不論是出于什么原因,定義功能是第一步。

有一點可能令定義這些邊界變得更為簡單,這就是COM服務(wù)器的運作和一個普通的C++類是幾乎一樣的。象一個類,你實例化一個COM類,然后可以開始調(diào)用它的方法。COM的實例化和方法調(diào)用的句法和C++是有點不同的,不過它們的想法是一樣的。如果一個服務(wù)器僅有一個接口,它事實上的用法就相當(dāng)于一個類。(不過在訪問對象時,你仍然需要遵守COM的規(guī)定)

一旦你已經(jīng)定義了功能和訪問它的方法,就可以建立自己的服務(wù)器。在第一部分中,我們已經(jīng)知道,要創(chuàng)建一個服務(wù)器,有4個基本的步驟:

1。使用ATL向?qū)韯?chuàng)建你的COM服務(wù)器的外殼。你選擇該服務(wù)器是一個DLL、一個EXE或者是一個服務(wù)。

2。在服務(wù)器的外殼中創(chuàng)建一個新的COM對象。你將要選擇線程的模式,這將會創(chuàng)建可裝入方法的接口。

3。在你的對象中加入方法,并且聲明它們的參數(shù)

4。為你的方法寫代碼

上面的這些步驟已經(jīng)在第一部分中的“理解一個簡單的COM服務(wù)器”中詳細(xì)介紹過了。

經(jīng)過第一部分的介紹后,一個常見的問題是關(guān)于線程模式,也就是COM對象的獨立線程(apartment-threade)和自由線程(free-threaded)之間的區(qū)別?要理解它們之間的區(qū)別的最簡單方法是將獨立線程看成為單線程,而將自由線程想象為多線程。

在獨立線程中,多個服務(wù)器客戶的方法調(diào)用在服務(wù)器端的COM對象中被串行化,也就是說,每個獨立的方法調(diào)用完成后,才會開始下一個的方法調(diào)用。因此獨立線程的COM對象天生就是線程安全的,而自由線程的COM對象可同時在COM對象上有多個的方法調(diào)用執(zhí)行。每個客戶的方法調(diào)用都在一個不同的線程中運行。因此,在一個自由線程的COM對象中,你必須要注意多線程的問題,例如同步。

開始的時候你將更趨向于使用獨立的線程,因為它更加簡單,不過以后最好轉(zhuǎn)向到自由線程,因為它有著更多的優(yōu)點。

客戶端

第一部分介紹的客戶端程序非常清楚和緊湊。不過,它包含很少的錯誤檢測代碼,因此要在一個真正的程序中應(yīng)用是不足夠的。讓我們再次看一下這些代碼,它非常簡單,因此可讓你清楚地看到要創(chuàng)建一個客戶端的必要步驟。voidmain()

{

HRESULThr;//COMerrorcode

IBeepDllObj*IBeep;//pointertointerface

hr=CoInitialize(0);//initializeCOM

if(SUCCEEDED(hr))//macrotocheckforsuccess

{

hr=CoCreateInstance(

clsid,//COMclassid

NULL,//outerunknown

CLSCTX_INPROC_SERVER,//serverINFO

iid,//interfaceid

(void**)&IBeep);//pointertointerface

if(SUCCEEDED(hr))

{

hr=IBeep-Beep(800);//callmethod

hr=IBeep-Release();//releaseinterface

}

CoUninitialize();//closeCOM

}CoInitialize和CoCreateInstance的調(diào)用初始化COM,并且得到指向一個接口的指針。然后你就可以調(diào)用接口的方法。在完成方法調(diào)用后,你就可釋放接口并且調(diào)用CoUninitialize。整個步驟就完成了。

不過,在一個COM客戶嘗試啟動一個COM服務(wù)器時,可出現(xiàn)各種不同的錯誤。一些常見的問題包括有:

.客戶端不能啟動COM

.客戶端不能查找到請求的服務(wù)器

.客戶端能查找到請求的服務(wù)器,但是不能正確地啟動

.客戶端不能找到請求的接口

.客戶端不能找到請求的方法

.客戶端可以找到請求的方法,但在調(diào)用時失敗

.客戶端不能正確地清除

為了跟蹤這些潛在的問題,你必須在每一步作檢查,具體是查看HRESULT的值。以上的代碼有作檢查,不過在出錯的時候并沒提示。以下的函數(shù)補(bǔ)救了這個不足://Thisfunctiondisplaysdetailed

//informationcontainedinanHRESULT.

BOOLShowStatus(HRESULThr)

{

//constructa_com_errorusingtheHRESULT

_com_errore(hr);

//Thehrasadecimalnumber

cout<<"hrasdecimal:"<<hr<<endl;

//showthe1st16bits(SCODE)

cout<<"SCODE:"<<HRESULT_CODE(hr)<<endl;

//Showfacilitycodeasadecimalnumber

cout<<"Facility:"<<HRESULT_FACILITY(hr)<<endl;

//Showtheseveritybit

cout<<"Severity:"<<HRESULT_SEVERITY(hr)<<endl;

//Usethe_com_errorobjecttoformatamessagestring.

//Thisismucheasierthenusing::FormatMessage

cout<<"Messagestring:"<<e.ErrorMessage()<<endl;

returnTRUE;

}該函數(shù)拆除了HRESULT的值,并且打印出它的所有成員,包括有極為有用的英語錯誤信息值ErrorMessage。你可以在任何的時候調(diào)用它:

//displayHRESULTonscreen

ShowStatus(hr);

要完整了解一個簡單的COM程序可產(chǎn)生的不同錯誤模式,以下的客戶端解釋代碼使用了一個MFC對話框應(yīng)用,可讓你控制一些可能的錯誤信息,并且看到該信息如何影響HRESULT的值。客戶端的運行如圖1所示。

圖一

左邊的單選按鈕可讓你選擇各種的錯誤,包括有缺少CoInitialize函數(shù),一個錯誤的classID,以及一個錯誤的接口ID。如果你按下運行的按鈕,在右邊你將可看到客戶端的不同函數(shù)返回的不同錯誤對HRESULT的值的影響。

在你使用該例子中的客戶端代碼時,你將會發(fā)現(xiàn)該版本要比我們原來使用的標(biāo)準(zhǔn)客戶端代碼強(qiáng)壯。它還允許通過DCOM作遠(yuǎn)程連接。例如,它使用CoInitializeSecurity函數(shù)來設(shè)置默認(rèn)的安全性,它還使用CoCreateInstanceEx函數(shù)以便另一臺機(jī)器上的遠(yuǎn)程服務(wù)器可以被調(diào)用。研究一下這些代碼,在文檔中查找這兩個函數(shù),你將會驚奇地發(fā)現(xiàn)要理解它是非常簡單的,你終于對COM有了一些了解。理解ATL產(chǎn)生的代碼

我們服務(wù)器端DLL的源代碼是由ATL產(chǎn)生的。對于許多人來說,可以完全不用了解ATL創(chuàng)建的代碼。不過,對于一些喜歡尋根究底的人來說,這是不可以接受的。這里就介紹一下由ATL產(chǎn)生的代碼。

服務(wù)端的DLL代碼由三種不同類型的文件組成

首先,是傳統(tǒng)的C++源文件和頭文件。在開始時,所有這些代碼是由ATL向?qū)Мa(chǎn)生的

Beep方法是通過使用“AddMethod”對話框加入的,它修改了MIDL接口的定義。MIDL的源代碼是一個IDL文件--在這個例子中它是BeepServer.IDL。MIDL編譯器將使用該文件來創(chuàng)建幾個輸出文件。這些文件負(fù)責(zé)了大部分實現(xiàn)服務(wù)器的工作。當(dāng)我們?yōu)镃OM對象加入方法時,我們也將在IDL加入一些東西。

第三組的源文件是自動產(chǎn)生的MIDL輸出文件,是由MIDL編譯器產(chǎn)生的。這些文件是源代碼文件,不過由于它們是由MIDL編譯器通過IDL源代碼自動產(chǎn)生的,因此不能被向?qū)Щ蛘唛_發(fā)者直接修改。你可以將它們稱為“第二產(chǎn)生文件”--向?qū)?chuàng)建了一個IDL文件,而MIDL編譯器由該IDL文件創(chuàng)建了源代碼文件。由MIDL創(chuàng)建的文件包括有:

BeepServer.RGS-服務(wù)器的注冊腳本

BeepServer.h-該文件包括了COM組件的定義

BeepServer_i.c-COM組件的GUID結(jié)構(gòu)

Proxy/Stubfiles-包括了"C"源代碼、DLL定義,以及Proxy和Stub的makefile(.mk)

ATL向?qū)н€創(chuàng)建了一個應(yīng)用的“資源”。如果你查看項目的資源,你將會發(fā)現(xiàn)它處在“REGISTRY”下。該資源包含了BeepServer.RGS中定義的注冊腳本。資源的名字是IDR_BEEPOBJ。

我們將在以下的部分看一下這些不同的組件

主C++模塊

我們在運行ATLCOMAppwizard時,我們選擇創(chuàng)建一個基于DLL的服務(wù)器,并且選擇不使用MFC。向?qū)У牡谝粋€選擇屏幕決定了服務(wù)器的整體配置。

AppWizard創(chuàng)建了一個標(biāo)準(zhǔn)的DLL模塊。該類的標(biāo)準(zhǔn)DLL并沒有一個WinMain應(yīng)用循環(huán),不過它有一個DllMain函數(shù)用作在載入時初始化該DLL:CComModule_Module;

BEGIN_OBJECT_MAP(ObjectMap)

OBJECT_ENTRY(CLSID_BeepObj,CBeepObj)

END_OBJECT_MAP()

////////////////////////////////////////

//DLLEntryPoint

extern"C"

BOOLWINAPIDllMain(HINSTANCEhInstance,

DWORDdwReason,LPVOID/*lpReserved*/)

{

if(dwReason==DLL_PROCESS_ATTACH)

{

_Module.Init(ObjectMap,hInstance);

DisableThreadLibraryCalls(hInstance);

}

elseif(dwReason==DLL_PROCESS_DETACH)

_Module.Term();

returnTRUE;//ok

}DllMain函數(shù)的真正工作是檢查有沒有一個客戶連上該DLL,然后會做一些初始化的工作。一眼看去,并沒有一個明顯的指示這是一個COM應(yīng)用。

我們新服務(wù)器的COM部分被封裝到ATL類CComModule中。CComModule是ATL服務(wù)器的基類。它包含了所有用作登記和運行服務(wù)器、開始和維護(hù)COM對象的COM邏輯。CComModule被定義在頭文件“altbase.h”中。該代碼用以下的行聲明一個全局的CComMoudule對象:

CComModule_Module;

這個單一的對象包含了許多用作我們應(yīng)用的COM服務(wù)器功能。它在程序執(zhí)行開始時的創(chuàng)建和初始化設(shè)置了一連串的事件動作。

ATL需要你的服務(wù)器命名它的全局CComModule對象“_Module”。使用你自己的類來覆蓋CComModule是可以的,不過你不能改變它的名字。

如果我們選擇創(chuàng)建一個可執(zhí)行的服務(wù)器,或者一個帶MFC的DLL,代碼將會是完全不同。這時還會有一個基于CComModule的全局對象,不過程序的入口將會是WinMain()。選擇一個基于MFC的DLL將會創(chuàng)建一個基于CWinApp的主對象。

對象映射

CComModule通過在前面部分看到的對象映射連接到我們的COM對象(CBeepObj)。一個對象映射定義了該服務(wù)器控制的所有COM對象的一個數(shù)組。對象映射在代碼中使用OBJECT_MAP宏定義。以下就是我們DLL的對象映射:BEGIN_OBJECT_MAP(ObjectMap)

OBJECT_ENTRY(CLSID_BeepObj,CBeepObj)

END_OBJECT_MAP()OBJECT_ENTRY宏通過一個C++類與對象的CLSID關(guān)聯(lián)。一個服務(wù)器中包含有多于一個的COM對象是很常見的。這時,對于每個對象,都將會有一個OBJECT_ENTRY。

輸出文件

我們這個進(jìn)程內(nèi)的DLL和大部分的DLL一樣,都擁有一個輸出文件。輸出文件將被客戶端用來連接到我們DLL中的外部函數(shù)。這些定義都放在BeepServer.def文件中:;BeepServer.def:Declaresthemoduleparameters.

LIBRARY"BeepServer.DLL"

EXPORTS

DllCanUnloadNow@1PRIVATE

DllGetClassObject@

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論