版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
PAGEPAGE38介紹
對(duì)于許多人來(lái)說(shuō),學(xué)習(xí)COM和DCOM是一件吃力的事情。COM的用處很大,不少微軟的產(chǎn)品和編程者工具都是基于COM,不過(guò),COM是一門(mén)頗難掌握的技術(shù),你可能曾經(jīng)想去學(xué)習(xí)它,閱讀過(guò)一些書(shū),使用過(guò)一些向?qū)У?,不過(guò)還是不太懂。它看來(lái)很復(fù)雜,而且還帶有不少的新名詞,例如"marshalling","apartmentthreads","singletonobjects"等,讓你摸不著頭腦。
這篇指南的目的是幫助你快速理解DCOM的基本要素,并且可以很容易地創(chuàng)建COM客戶(hù)和服務(wù)器。讀完這幾篇指南后,你將會(huì)發(fā)現(xiàn)如果有一個(gè)好的開(kāi)始,學(xué)習(xí)DCOM是一件非常簡(jiǎn)單的事情。以下是本指南的目錄:
COM的基本要素--要學(xué)好它,就從這里開(kāi)始吧
簡(jiǎn)單的COM客戶(hù)--介紹簡(jiǎn)單的COM客戶(hù)
簡(jiǎn)單的COM服務(wù)器--使用ATL向?qū)?lái)建立一個(gè)服務(wù)器
下載工程文件
*****下載BeepClient工程文件(9KB)
*****下載BeepServer工程文件(17KB)
COM的基本要素
首先要弄懂COM是怎樣工作的。為什么這個(gè)工作是首要的呢?因?yàn)镃OM使用它自己專(zhuān)有的詞匯。第二個(gè)原因是COM包含有不少的新概念。要掌握這些詞匯和概念,最簡(jiǎn)單的其中一個(gè)方法是將COM對(duì)象和普通的C++對(duì)象作比較,并且比較它們的相似和不同之處。你還可以將COM的一些概念映射到標(biāo)準(zhǔn)的C++模型中去,這樣就可以用你已經(jīng)熟悉的東西來(lái)理解新概念。我們首先介紹一些COM的基本概念,接著,你就可以很容易地理解后面的例子。
一、類(lèi)和對(duì)象
假設(shè)你在C++中創(chuàng)建了一個(gè)稱(chēng)為xxx的簡(jiǎn)單類(lèi)。它有幾個(gè)成員函數(shù),稱(chēng)為MethodA,MethodB和MethodC。每個(gè)成員函數(shù)可接收參數(shù),并返回一個(gè)結(jié)果。該類(lèi)的定義如下所示:classxxx{
public:
intMethodA(inta);
intMethodB(floatb);
floatMethodC(floatc);
};在需要使用類(lèi)的時(shí)候,你必須創(chuàng)建該對(duì)象的一個(gè)實(shí)例。實(shí)例是真實(shí)的對(duì)象;類(lèi)只是定義。每個(gè)對(duì)象可作為一個(gè)變量(本地或者全局)創(chuàng)建,或者可使用new聲明動(dòng)態(tài)地創(chuàng)建。new聲明可動(dòng)態(tài)創(chuàng)建變量并返回指向它的一個(gè)指針。你可通過(guò)該指針來(lái)調(diào)用成員函數(shù),例如:
xxx*px;//指向xxx類(lèi)的指針
px=newxxx;//創(chuàng)建對(duì)象
px->MethodA(1);//調(diào)用方法
deletepx;//釋放對(duì)象
你要明白到,COM使用相同的面向?qū)ο竽P汀OM擁有與C++對(duì)象一樣的類(lèi)、成員函數(shù)和實(shí)例。雖然你從來(lái)不會(huì)在一個(gè)COM對(duì)象上調(diào)用new方法,不過(guò)你必須在內(nèi)存中創(chuàng)建它。你通過(guò)指針來(lái)訪(fǎng)問(wèn)COM對(duì)象,在你完成處理后,你必須釋放它們。
寫(xiě)COM的代碼時(shí),我們將不會(huì)使用上面的new和delete。雖然我們將使用C++作為開(kāi)發(fā)語(yǔ)言,不過(guò)我們將要使用全新的語(yǔ)法。COM是通過(guò)調(diào)用COMAPI來(lái)實(shí)現(xiàn)的,這些API提供創(chuàng)建和破壞COM對(duì)象的函數(shù)。以下就是一個(gè)用pseudo-COM代碼寫(xiě)的COM程序例子:
ixx*pi//指向toxxxCOM接口的指針
CoCreateInstance(,,,,&pi)//創(chuàng)建接口
pi->MethodA();//調(diào)用方法
pi->Release();//釋放接口
在這個(gè)例子中,我們將稱(chēng)類(lèi)ixx是一個(gè)“接口”。變量pi是指向接口的一個(gè)指針。CoCreateInstance方法可創(chuàng)建一個(gè)ixx的實(shí)例。接口的指針是用來(lái)作方法調(diào)用的。Release用來(lái)刪除接口。
為了突出該程序的要點(diǎn),我故意忽略了CoCreateInstance的一些參數(shù)。CoCreateInstance可接收多個(gè)參數(shù),每個(gè)參數(shù)都需要更深入的探討才可以了解?,F(xiàn)在,我們首先回過(guò)頭來(lái)看看COM的一些主要方面。二、COM有什么不同
在某種程度上,COM對(duì)象要比它們的同胞C++更復(fù)雜,從網(wǎng)絡(luò)應(yīng)用方面考慮,大多數(shù)的復(fù)雜性都是必要的。以下就是在設(shè)計(jì)COM時(shí)的4個(gè)基本要素:
。C++對(duì)象通常都運(yùn)行在同一進(jìn)程空間中。COM對(duì)象可跨進(jìn)程和跨計(jì)算機(jī)運(yùn)行
。COM方法可通過(guò)網(wǎng)絡(luò)調(diào)用
。在一個(gè)進(jìn)程空間中,C++方法的名字必須是唯一的,而COM對(duì)象的名字在整個(gè)世界中都是唯一的
。COM服務(wù)器可以使用多種不同的語(yǔ)言和在不同的操作系統(tǒng)上編寫(xiě),而C++對(duì)象通常都使用C++編寫(xiě)
以下再談一下COM和C++的這些不同對(duì)于編程者有何意義。
COM可以跨進(jìn)程運(yùn)行
在COM中,編程者可在其它的進(jìn)程中或者網(wǎng)絡(luò)中的任何機(jī)器上創(chuàng)建對(duì)象。雖然在許多情況下你都無(wú)需這樣做,不過(guò),這種可能性意味著你不能通過(guò)普通C++的new句法創(chuàng)建一個(gè)COM對(duì)象,通過(guò)本地的程序來(lái)調(diào)用它的方法也是不足夠的。
要?jiǎng)?chuàng)建一個(gè)COM對(duì)象,某些執(zhí)行的實(shí)體(一個(gè)EXE或者服務(wù))將必須執(zhí)行遠(yuǎn)程的內(nèi)存分配和對(duì)象創(chuàng)建。這是一個(gè)非常復(fù)雜的任務(wù)。遠(yuǎn)程的含義是指在另一個(gè)進(jìn)程內(nèi)或者另一個(gè)進(jìn)程上。這個(gè)問(wèn)題是通過(guò)稱(chēng)為COM服務(wù)器的概念來(lái)解決的。它必須與客戶(hù)端維持緊密的通信。
COM方法可以通過(guò)網(wǎng)絡(luò)調(diào)用
如果你可以訪(fǎng)問(wèn)網(wǎng)絡(luò)上某臺(tái)機(jī)器,而你想要使用的某個(gè)對(duì)象的COM服務(wù)器已經(jīng)被安裝在該機(jī)器上,你就可以在那臺(tái)機(jī)器上創(chuàng)建COM對(duì)象。當(dāng)然,你必須要有相應(yīng)的權(quán)限,并且那臺(tái)機(jī)器上已經(jīng)進(jìn)行了正確的設(shè)置。
由于你的COM對(duì)象并不一定在本機(jī)上,因此你需要一個(gè)方法來(lái)“指向”它,即使它存放在另一臺(tái)機(jī)器的內(nèi)存中。在技術(shù)上,沒(méi)辦法做到這一點(diǎn)。不過(guò)它可以通過(guò)一個(gè)全新級(jí)別的對(duì)象來(lái)模擬。COM使用的其中一個(gè)方法是一個(gè)稱(chēng)為proxy/stub的概念,我們將會(huì)在后面更詳細(xì)地討論proxy/stubs。
另一個(gè)重要的問(wèn)題是在COM客戶(hù)端和它的COM服務(wù)器間傳送數(shù)據(jù)。數(shù)據(jù)在進(jìn)程、線(xiàn)程之間或者一個(gè)網(wǎng)絡(luò)上傳送的時(shí)候,它就被稱(chēng)為“Marshalling”。proxy/stub負(fù)責(zé)為你維護(hù)Marshalling。COM還可以使用類(lèi)庫(kù)和Automationmarshaller來(lái)配置接口的某些數(shù)據(jù)類(lèi)型。Automationmarshaller無(wú)需特別為每個(gè)COM服務(wù)器建立。
COM對(duì)象在世界上必須是唯一的
整個(gè)世界?看來(lái)有點(diǎn)夸張,不過(guò)考慮到Internet是一個(gè)世界范圍的網(wǎng)絡(luò),即使在單一某臺(tái)計(jì)算機(jī)上,COM也必須考慮到這個(gè)可能性。唯一是一個(gè)問(wèn)題。在C++的全部類(lèi)庫(kù)中,這個(gè)問(wèn)題是通過(guò)編譯器完成的。編譯器可以看到一個(gè)程序中每個(gè)類(lèi)的定義,并且匹配它的所有引用,以確保它們嚴(yán)密符合該類(lèi)。編譯器也要確保每個(gè)類(lèi)的名字是唯一的。在COM中也必須有一個(gè)好的方法來(lái)得到類(lèi)似嚴(yán)密的匹配。即使在世界范圍的網(wǎng)絡(luò)上,COM也要確保每個(gè)對(duì)象的名字是唯一的。這個(gè)問(wèn)題是通過(guò)一個(gè)稱(chēng)為GUID的概念來(lái)解決的。
COM是語(yǔ)言無(wú)關(guān)的
COM服務(wù)器可以用不同的語(yǔ)言和在完全不同的操作系統(tǒng)上編寫(xiě)。COM對(duì)象可以通過(guò)遠(yuǎn)程訪(fǎng)問(wèn)。遠(yuǎn)程是指它可以在一個(gè)不同的線(xiàn)程、進(jìn)程或者甚至一個(gè)不同的機(jī)器上。另一臺(tái)機(jī)器可以運(yùn)行一個(gè)不同的操作系統(tǒng)。這就需要一個(gè)好的方法來(lái)在網(wǎng)絡(luò)的機(jī)器間傳送參數(shù)。這個(gè)問(wèn)題是通過(guò)創(chuàng)建一個(gè)新的方法來(lái)指定客戶(hù)和服務(wù)器間的接口來(lái)解決。還有一個(gè)稱(chēng)為MIDL(MicrosoftInterfaceDefinitionLanguage,微軟接口定義語(yǔ)言)的新編譯器。該編譯器可指定服務(wù)器和客戶(hù)端接口的一般方法。MIDL定義COM對(duì)象、接口、方法和參數(shù)。
COM詞匯
我們碰到的其中一個(gè)問(wèn)題是要記住兩套術(shù)語(yǔ)。你可能已經(jīng)熟悉C++和一些面向?qū)ο蟮男g(shù)語(yǔ)。以下的表格將COM和傳統(tǒng)術(shù)語(yǔ)間類(lèi)似的地方列了出來(lái)。
概念傳統(tǒng)的(C++/OOP)COM客戶(hù)端一個(gè)從某個(gè)服務(wù)器請(qǐng)求服務(wù)的程序一個(gè)調(diào)用COM方法的程序服務(wù)器一個(gè)為其它程序服務(wù)的程序一個(gè)讓某個(gè)COM客戶(hù)得到COM對(duì)象的程序接口沒(méi)有通過(guò)COM調(diào)用的一組函數(shù)的一個(gè)指示器類(lèi)一個(gè)數(shù)據(jù)類(lèi)型定義了一組一起使用的方法和數(shù)據(jù)一個(gè)對(duì)象的定義,用來(lái)實(shí)現(xiàn)一個(gè)或者多個(gè)COM接口,“coclass”也是對(duì)象一個(gè)類(lèi)的實(shí)例化一個(gè)coclass的實(shí)例化Marshalling沒(méi)有在客戶(hù)和服務(wù)器端之間移動(dòng)數(shù)據(jù)
你會(huì)發(fā)現(xiàn)接口和Marshalling的概念在C++模型中是沒(méi)有的。在C++中,與接口較為相近的是一個(gè)DLL的外部定義。在使用一個(gè)緊密結(jié)合(進(jìn)程間)的COM服務(wù)器時(shí),DLL所做的許多事情與COM差不多。Marshalling在C++中也是沒(méi)有的,如果你要在進(jìn)程或者計(jì)算機(jī)之間拷貝數(shù)據(jù)時(shí),你必須使用一些交互進(jìn)程通信的方法來(lái)寫(xiě)代碼,你可以選擇sockets、剪貼板和mailslots。
接口
在上面我們已經(jīng)多次看到“接口”這個(gè)詞,在我的一本字典中是這樣定義一個(gè)接口的:
“接口:名詞,是兩個(gè)物體或者界面的共有分界”。
這是一個(gè)普通的定義。在COM中“接口”有非常特別的含義。COM接口是一個(gè)全新的概念,在C++中是沒(méi)有的。對(duì)于許多人來(lái)說(shuō),接口的概念在開(kāi)始時(shí)都較難理解。一個(gè)接口沒(méi)有一個(gè)有形的存在。它類(lèi)似一個(gè)抽象類(lèi),但不完全一樣。
最簡(jiǎn)單地說(shuō),接口是函數(shù)的集合。在C++,一個(gè)類(lèi)僅允許有一個(gè)接口。這個(gè)接口的成員函數(shù)都是該類(lèi)所有的公有成員函數(shù)。用其它話(huà)來(lái)說(shuō),接口是類(lèi)的公共可見(jiàn)部分。在C++中一個(gè)接口和一個(gè)類(lèi)幾乎沒(méi)有任何的區(qū)別,以下就是C++類(lèi)的一個(gè)例子:classyyy{
public:
intDoThis();
private:
voidHelper1();
intcount;
intx,y,z;
};某人使用這個(gè)類(lèi)時(shí),他只可訪(fǎng)問(wèn)到pubilc的成員(這里我們忽略了protected成員和繼承)。他不能調(diào)用Helper1,也不能使用任何的private變量。對(duì)于類(lèi)的使用者來(lái)說(shuō),它的定義其實(shí)是:
classyyy{
intDoThis();
};
類(lèi)的public子集是外部的“接口”。接口將類(lèi)的內(nèi)部和使用者隔離開(kāi)來(lái)。
C++類(lèi)似的部分就只有這么多,COM接口并不是一個(gè)C++的類(lèi),COM接口和類(lèi)擁有自己特別的一套規(guī)則和協(xié)定。
COM允許一個(gè)coclass(COM類(lèi))擁有多個(gè)接口,每個(gè)接口擁有自己的名字和函數(shù)集。這樣做便可得到更為復(fù)雜和功能更強(qiáng)的對(duì)象。這個(gè)概念與C++是完全不同的。(可將多個(gè)接口想象為兩個(gè)類(lèi)定義的結(jié)合,當(dāng)然,這種結(jié)合在C++中是不允許的)
接口將客戶(hù)和服務(wù)器隔離開(kāi)來(lái)
COM最重要的一條規(guī)定是你只可通過(guò)接口來(lái)訪(fǎng)問(wèn)一個(gè)COM對(duì)象。通過(guò)接口,客戶(hù)端的程序與服務(wù)器的執(zhí)行完全隔離開(kāi)來(lái)。這是非常重要的一點(diǎn)。
客戶(hù)端程序?qū)τ趯?shí)現(xiàn)COM的COM對(duì)象或者C++類(lèi)一無(wú)所知。它只能看到接口。接口就象COM對(duì)象的一個(gè)窗口。接口的設(shè)計(jì)者只讓客戶(hù)看到設(shè)計(jì)者希望展示的部分。圖一展示了客戶(hù)是如何通過(guò)接口來(lái)訪(fǎng)問(wèn)一個(gè)COM對(duì)象的。
*****圖一*****圖中一個(gè)小圓圈連接一條桿的符號(hào),是表示一個(gè)COM接口的通常方法。接口還有許多重要的規(guī)定,對(duì)于理解COM的詳細(xì)運(yùn)作是很重要的,我們將在下面談到?,F(xiàn)在我們只集中談接口的主要概念。
COM接口的形象化
這里將以另一種方式來(lái)形象化一個(gè)接口。在這個(gè)部分中,我們將不用任何的C++術(shù)語(yǔ)來(lái)介紹一個(gè)COM接口。我們將以一個(gè)抽象的形式來(lái)了解一個(gè)接口。想象一下一個(gè)“汽車(chē)”對(duì)象。對(duì)于現(xiàn)實(shí)中的所有汽車(chē)對(duì)象,你知道它有一個(gè)“駕駛”的接口,可讓你控制汽車(chē)向左、向右,或者加速、減速。駕駛接口的成員函數(shù)包括有“左”、“右”、“加速”、“減速”、“向前”和“向后”。不少的汽車(chē)安裝了收音機(jī),因此還有一個(gè)“收音機(jī)”的接口。收音機(jī)的接口可以是“開(kāi)”、“關(guān)”、“大聲”、“柔和”、“下一個(gè)臺(tái)”和“前一個(gè)臺(tái)”。DrivingRadio
Left()On()
Right()Off()
Slower()Louder()
Faster()Softer()
Forward()NextStation()
Reverse()PrevStation()有許多不同種類(lèi)的汽車(chē),它們不一定有收音機(jī)。因此它們雖然支持駕駛的接口,但沒(méi)有實(shí)現(xiàn)收音機(jī)的接口。對(duì)于所有擁有收音機(jī)的汽車(chē),收音器的功能都是一樣的。一個(gè)人可以駕駛一輛沒(méi)有收音機(jī)的汽車(chē),但他不能聽(tīng)到音樂(lè)。對(duì)于帶有收音機(jī)的汽車(chē),還擁有收音機(jī)的接口。
對(duì)于COM類(lèi),COM支持這個(gè)同樣形式的模型。一個(gè)COM對(duì)象可支持一個(gè)接口的集合,每個(gè)接口都擁有自己的名字。對(duì)于你自己創(chuàng)建的COM對(duì)象,你可以只使用單一一個(gè)COM接口。不過(guò)對(duì)于許多現(xiàn)有的COM對(duì)象,根據(jù)它們支持的特性,可支持多個(gè)COM接口。
另一個(gè)重要的區(qū)別是駕駛接口并不是汽車(chē)。駕駛接口并沒(méi)有告訴你任何關(guān)于車(chē)的制動(dòng)裝置、車(chē)輪或者引擎等的事情。例如你可使用駕駛接口的加速和減速方法,而不需關(guān)心減速是如何實(shí)現(xiàn)的。汽車(chē)使用水力或者空氣剎車(chē)也是不重要的。
組件的形象化
在你建立一個(gè)COM對(duì)象時(shí),你會(huì)非常關(guān)注接口是如何工作的,對(duì)于接口的使用者,卻不用關(guān)心它的實(shí)現(xiàn)。就象一輛車(chē)的制動(dòng)一樣,用戶(hù)只關(guān)心接口的工作,而無(wú)需知道接口后面的細(xì)節(jié)。
隔離接口和實(shí)現(xiàn)對(duì)于COM是至關(guān)緊要的。通過(guò)將它的實(shí)現(xiàn)和接口隔離開(kāi),我們可以建立組件。組件可被替換和重用。兩者均可簡(jiǎn)化和增加對(duì)象的可用性。名字的唯一性
COM接口的名字是唯一的。這就是說(shuō),編程者如果訪(fǎng)問(wèn)某個(gè)名字的接口,他可以認(rèn)為在實(shí)現(xiàn)接口的所有COM對(duì)象中,該接口的成員函數(shù)和參數(shù)都將是完全一樣的。因此,在我們上面的例子中,稱(chēng)為“駕駛”和“收音機(jī)”的接口,在任何實(shí)現(xiàn)它們的COM對(duì)象中,都將擁有完全一樣的成員函數(shù)。如果你想要改變一個(gè)接口的成員函數(shù),你必須用一個(gè)新的名字創(chuàng)建一個(gè)新接口。
所有接口的源頭--IUnknown
通常介紹COM都是從講述IUnknown接口開(kāi)始的。IUnknown是所有COM接口的基礎(chǔ)。雖然它挺重要,不過(guò)就算你不了解IUnknown,你也可以明白接口的概念。IUnknown的實(shí)現(xiàn)被更高級(jí)別的抽象隱藏起來(lái),我們也使用這些抽象來(lái)建立自己的COM對(duì)象。不過(guò),太過(guò)關(guān)注IUnknown將會(huì)令你感到迷惑。我們將從一個(gè)更高的級(jí)別來(lái)處理它,從而令你更容易理解它的概念。
IUnknown類(lèi)似C++中的抽象基類(lèi)。所有的COM接口必須由IUnknown繼承而來(lái)。IUnknown處理接口的創(chuàng)建和管理。IUnknown的方法被用來(lái)創(chuàng)建、引用計(jì)數(shù)和釋放一個(gè)COM對(duì)象。所有的COM接口都實(shí)現(xiàn)這3個(gè)方法,它們被COM內(nèi)部使用來(lái)管理接口。你可能從來(lái)不會(huì)自己調(diào)用這3個(gè)方法。
一個(gè)典型的COM對(duì)象
現(xiàn)在我們要將這些新的概念放在一起,并且介紹一個(gè)典型的COM對(duì)象和一個(gè)要訪(fǎng)問(wèn)該對(duì)象的程序。
想象一下,如果你要?jiǎng)?chuàng)建一個(gè)最簡(jiǎn)單的COM對(duì)象。這個(gè)對(duì)象支持一個(gè)單一的接口,并且該接口只含有一個(gè)單一的函數(shù)。這個(gè)函數(shù)的功能也很簡(jiǎn)單--只是發(fā)出beep聲。當(dāng)一個(gè)編程者創(chuàng)建該COM對(duì)象,并且調(diào)用該對(duì)象支持的單一接口中的成員函數(shù)時(shí),COM對(duì)象存在的機(jī)器將會(huì)發(fā)出beep聲。更進(jìn)一步的是,你要在一臺(tái)機(jī)器上運(yùn)行這個(gè)COM對(duì)象,但是從網(wǎng)絡(luò)上的另一臺(tái)機(jī)器來(lái)調(diào)用它。
為了創(chuàng)建這個(gè)簡(jiǎn)單的COM對(duì)象,你必須做以下的事情:
。你必須創(chuàng)建該COM對(duì)象,并且給它一個(gè)名字。該對(duì)象將會(huì)在一個(gè)相關(guān)的COM服務(wù)器中實(shí)現(xiàn)
。你需要定義該接口并且給它起一個(gè)名字
。你需要定義接口中的函數(shù)并且給它起一個(gè)名字
。你將需要安裝COM服務(wù)器
在這個(gè)例子中,我將該COM對(duì)象稱(chēng)為Beeper,接口為IBeep,函數(shù)為Beep。你首先要馬上面對(duì)的一個(gè)問(wèn)題是命名這些對(duì)象,事實(shí)上,所有的機(jī)器都支持多個(gè)COM服務(wù)器,每個(gè)都可包含有一個(gè)或者多個(gè)COM對(duì)象,而每個(gè)COM對(duì)象都要實(shí)現(xiàn)一個(gè)或者多個(gè)的接口。這些服務(wù)器是經(jīng)由不同的編程者創(chuàng)建的,這樣就有可能選擇一樣的名字。同樣,COM對(duì)象有一個(gè)或者多個(gè)的命名接口,它們同樣是由多個(gè)編程者隨意創(chuàng)建的,這樣也有同名的問(wèn)題。因此,我們必須要想個(gè)方法來(lái)防止名字的沖突。一個(gè)稱(chēng)為GUID(GloballyUniqueIDentifier,全球唯一標(biāo)識(shí)器)的方法可解決這個(gè)問(wèn)題。
如何做到唯一--GUID
要確保一個(gè)名字是唯一的,僅有兩個(gè)方法:
1。通過(guò)一些準(zhǔn)政府組織來(lái)登記名字;
2。使用一個(gè)特別的算法來(lái)產(chǎn)生唯一的數(shù)字,這些數(shù)字可被認(rèn)為在世界范圍內(nèi)是唯一的第一個(gè)方法與網(wǎng)絡(luò)上的域名管理一樣。它的問(wèn)題是你必須付$50來(lái)登記一個(gè)新的名字,而且要令登記生效,你要等幾個(gè)星期。
第二個(gè)方法對(duì)于開(kāi)發(fā)者更為方便。如果你可以發(fā)明一個(gè)算法,每次人們調(diào)用它都可以產(chǎn)生一個(gè)可被認(rèn)為是唯一的名字,那么這個(gè)問(wèn)題就解決了。事實(shí)上,這個(gè)問(wèn)題已經(jīng)被開(kāi)放軟件基金會(huì)提到(OpenSoftwareFoundation,OSF)。OSF有一個(gè)算法,可將一個(gè)網(wǎng)絡(luò)地址、時(shí)間(100納秒遞增)和一個(gè)計(jì)數(shù)器結(jié)合,得到一個(gè)128位的唯一數(shù)字。
2的128次方是一個(gè)非常大的數(shù)字。通過(guò)它,你可以識(shí)別由宇宙開(kāi)始到現(xiàn)在的每個(gè)100納秒--而且還會(huì)剩下39位。OSF將它稱(chēng)為UUID,意思是UniversallyUniqueIdentifier,在COM的命名標(biāo)準(zhǔn)上,微軟使用同樣的算法。在COM中微軟將它重命名為GloballyUniqueIdentifier。
GUID的記錄通常采用16進(jìn)制。不過(guò)這沒(méi)有關(guān)系,一個(gè)典型的GUID類(lèi)似為:
"50709330-F93A-11D0-BCE4-204C4F4F5020"typedefstruct_GUID
{
unsignedlongData1;
unsignedshortData2;
unsignedshortData3;
unsignedcharData4[8];
}GUID;GUID的普通讀音是“gwid”,與“squid”的發(fā)音類(lèi)似。一些人也讀為“goo-wid”。
GUID通過(guò)一個(gè)稱(chēng)為GUIDGEN的程序產(chǎn)生。在GUIDGEN中,你只要按下一個(gè)按鈕就可以產(chǎn)生一個(gè)新的GUID。你可以認(rèn)為你產(chǎn)生的每個(gè)GUID都是唯一的,不管你產(chǎn)生了多少個(gè),或者世界上有多少人產(chǎn)生它。這個(gè)認(rèn)定可以成立是基于以下的原因:Internet上的所有機(jī)器都有一個(gè)唯一的地址。因此,你的機(jī)器最好是處在網(wǎng)絡(luò)上。不過(guò),即使你沒(méi)有網(wǎng)絡(luò)地址,GUIDGEN也將會(huì)產(chǎn)生一個(gè),但是這樣就會(huì)令唯一性的機(jī)率降低。
COM對(duì)象和COM接口都有一個(gè)GUID來(lái)標(biāo)識(shí)自己。因此我們?yōu)樵搶?duì)象選用的名字“Beeper”是沒(méi)有關(guān)系的。對(duì)象是通過(guò)它的GUID來(lái)命名的。我們將該對(duì)象的GUID稱(chēng)為它的classID。然后我們就可以使用一個(gè)#defind或者一個(gè)常數(shù)來(lái)令Beeper的名字和該GUID相關(guān),這樣我們就無(wú)需在代碼中都使用這個(gè)128位的值。同樣接口也將擁有一個(gè)GUID。要注意的是許多由不同的編程者來(lái)創(chuàng)建的不同COM對(duì)象將支持同樣的IBeep接口,而它們都將全部使用同樣的GUID來(lái)命名它。如果沒(méi)有同樣的GUID,COM就認(rèn)為這是一個(gè)不同的接口。GUID就是它的名字。
一個(gè)COM服務(wù)器
COM服務(wù)器就是實(shí)現(xiàn)COM接口和類(lèi)的程序。COM服務(wù)器有三個(gè)基本的配置。
。進(jìn)程中或者DLL服務(wù)器
。Stand-aloneEXE服務(wù)器
?;赪indowsNT的服務(wù)
COM對(duì)象都是一樣的,與服務(wù)器的類(lèi)型無(wú)關(guān)。COM接口和coclasses將不會(huì)關(guān)心當(dāng)前使用的服務(wù)器類(lèi)型。對(duì)于客戶(hù)端程序來(lái)說(shuō),服務(wù)器的類(lèi)型幾乎是完全透明的。不過(guò)對(duì)于寫(xiě)真正的服務(wù)器端來(lái)說(shuō),每種配置都將會(huì)有明顯的不同:
進(jìn)程中的服務(wù)器是作為動(dòng)態(tài)連接庫(kù)(DLL)實(shí)現(xiàn)的。這意味著該服務(wù)器在運(yùn)行時(shí)被動(dòng)態(tài)地放進(jìn)你的進(jìn)程中
COM服務(wù)器將成為你應(yīng)用中的一部分,而COM操作在應(yīng)用的線(xiàn)程中進(jìn)行。事實(shí)上,許多的COM對(duì)象都是以這種方式實(shí)現(xiàn)的,因?yàn)樾阅芎芎?-一個(gè)COM函數(shù)調(diào)用的系統(tǒng)開(kāi)銷(xiāo)很小,但你可以得到COM所有的設(shè)計(jì)和重用的好處
COM自動(dòng)處理載入和卸下該DLL
一個(gè)進(jìn)程外的服務(wù)器令客戶(hù)和服務(wù)端的區(qū)分更明顯。該類(lèi)服務(wù)器作為一個(gè)獨(dú)立的可執(zhí)行(EXE)程序運(yùn)行,因此處在一個(gè)私有的進(jìn)程空間中。EXE服務(wù)器的啟動(dòng)和停止在Windows中服務(wù)管理器中進(jìn)行(SCM)。COM接口的調(diào)用通過(guò)內(nèi)部的進(jìn)程通信技術(shù)來(lái)處理。服務(wù)器可以運(yùn)行在本地的機(jī)器,或者在一個(gè)遠(yuǎn)程的計(jì)算機(jī)上。如果服務(wù)器在一個(gè)遠(yuǎn)程的計(jì)算機(jī)上,我們稱(chēng)它為“DistributedCOM,分布式的COM”,或者DCOM。
WindowsNT提出了一個(gè)服務(wù)的概念。一個(gè)服務(wù)是指該程序由WindowsNT自動(dòng)管理,與桌面的用戶(hù)無(wú)關(guān)。這意味著服務(wù)可以在啟動(dòng)時(shí)自動(dòng)開(kāi)始,并且即使是沒(méi)有人登錄到WindowsNT中,也可以自動(dòng)運(yùn)行。服務(wù)提供了一個(gè)極好的方法來(lái)運(yùn)行COM服務(wù)器應(yīng)用。
還有第四種的服務(wù)器,稱(chēng)為“surrogate”,這是一個(gè)可允許進(jìn)程中的服務(wù)器在遠(yuǎn)程運(yùn)行的程序。對(duì)于要建立一個(gè)可通過(guò)網(wǎng)絡(luò)訪(fǎng)問(wèn)的基于DLL的COM服務(wù)器,surrogate是很有用的。
客戶(hù)端和服務(wù)器端的交互
在COM中,客戶(hù)端程序驅(qū)動(dòng)所有的事情。服務(wù)器是被動(dòng)的,只響應(yīng)客戶(hù)的請(qǐng)求。這意味著對(duì)于客戶(hù)的個(gè)別方法調(diào)用,COM服務(wù)器以一個(gè)同步的方式運(yùn)作
??蛻?hù)端的程序啟動(dòng)服務(wù)器
??蛻?hù)端請(qǐng)求COM對(duì)象和接口
??蛻?hù)端發(fā)起所有的方法調(diào)用到服務(wù)器
??蛻?hù)端釋放服務(wù)器的接口,允許服務(wù)器關(guān)閉
這個(gè)區(qū)別是重要的。有各種不同的方法可模擬服務(wù)器到客戶(hù)的調(diào)用,不過(guò)它們都難以實(shí)現(xiàn),并且都相當(dāng)復(fù)雜(這個(gè)被稱(chēng)為回叫)。通常沒(méi)有客戶(hù)的請(qǐng)求,服務(wù)器不做任何的事情。
以下就是COM客戶(hù)和服務(wù)器之間的一個(gè)典型的交互
客戶(hù)請(qǐng)求
請(qǐng)求訪(fǎng)問(wèn)一個(gè)特別的COM接口,特別的COM類(lèi)和接口(通過(guò)GUID)
服務(wù)器響應(yīng)
。啟動(dòng)服務(wù)器(如果需要)。如果是一個(gè)進(jìn)程內(nèi)的服務(wù)器,DLL將被載入??蓤?zhí)行的服務(wù)器將由SCM運(yùn)行
。創(chuàng)建請(qǐng)求的COM對(duì)象
。創(chuàng)建到COM對(duì)象的一個(gè)接口
。增加激活接口的引用計(jì)數(shù)
。返回該接口給客戶(hù)
客戶(hù)請(qǐng)求
調(diào)用接口的一個(gè)方法
服務(wù)器響應(yīng)
執(zhí)行一個(gè)COM對(duì)象的方法
客戶(hù)請(qǐng)求
釋放接口
服務(wù)器響應(yīng)
減少接口引用的數(shù)目
如果引用計(jì)數(shù)為0,將會(huì)刪除該COM對(duì)象
如果沒(méi)有活動(dòng)的連接,關(guān)閉服務(wù)器。某些服務(wù)器不會(huì)關(guān)閉自身
如果你要了解COM,你必須使用一個(gè)以客戶(hù)為中心的方法
三、總結(jié)
我們嘗試從幾個(gè)不同的方面來(lái)了解COM。C++是COM的原始語(yǔ)言,不過(guò)重要的是我們要了解它們的不同。COM有許多類(lèi)似C++的地方,不過(guò)它也有很大的不同。在客戶(hù)和服務(wù)器間通信方面,COM提供了一個(gè)全新的方式。
接口是COM最為重要的概念之一。所有的COM交互都經(jīng)由接口進(jìn)行。由于在C++中,并沒(méi)有一個(gè)直接與接口對(duì)應(yīng)的事物,因此有點(diǎn)難以掌握。我們還介紹了GUID的概念。GUID在COM中是普遍存在的,并且提供了一個(gè)極好的方式來(lái)在一個(gè)大型網(wǎng)絡(luò)中標(biāo)識(shí)一個(gè)實(shí)體。
COM服務(wù)器是傳送COM組件的媒介。所有的事情都集中在傳送COM組件到一個(gè)客戶(hù)應(yīng)用上。在以下的章節(jié)中,我們將創(chuàng)建一個(gè)簡(jiǎn)單的客戶(hù)和服務(wù)器應(yīng)用來(lái)解釋這些概念。理解最簡(jiǎn)單的COM客戶(hù)
要理解COM的最直接方法是通過(guò)一個(gè)客戶(hù)應(yīng)用來(lái)考察它。COM編程的目的是為了讓客戶(hù)應(yīng)用可以得到有用的對(duì)象。一旦你理解了客戶(hù),要理解服務(wù)端就變得非常的簡(jiǎn)單。相反,同時(shí)直接考察服務(wù)端和客戶(hù)端是容易令人迷惑的;如果你首先學(xué)習(xí)其細(xì)節(jié)的話(huà),就更加復(fù)雜了。因此,我們首先由最簡(jiǎn)單的定義開(kāi)始:COM客戶(hù)是一個(gè)使用COM來(lái)調(diào)用一個(gè)COM服務(wù)器上的方法的程序。這種客戶(hù)/服務(wù)關(guān)系的一個(gè)最簡(jiǎn)單直接的例子是一個(gè)用戶(hù)界面應(yīng)用(客戶(hù))調(diào)用另一個(gè)應(yīng)用(服務(wù)端)的方法。如果該用戶(hù)界面應(yīng)用使用COM來(lái)調(diào)用這些方法,那么根據(jù)定義,這個(gè)用戶(hù)界面應(yīng)用就是一個(gè)COM客戶(hù)。
我們不斷強(qiáng)調(diào)以上的內(nèi)容是有理由的,因?yàn)镃OM服務(wù)器和客戶(hù)的分別可以是更為復(fù)雜的。許多時(shí)候,應(yīng)用客戶(hù)也將是一個(gè)COM服務(wù)端,而應(yīng)用的服務(wù)器也可是一個(gè)COM客戶(hù)。一個(gè)應(yīng)用同時(shí)是COM客戶(hù)和服務(wù)器是很常見(jiàn)的。在這一章中,我們將讓這個(gè)區(qū)別最簡(jiǎn)單化,涉及的只是一個(gè)純COM客戶(hù)。
客戶(hù)端連接的4個(gè)步驟
客戶(hù)使用COM與一個(gè)服務(wù)器通信時(shí),通常要經(jīng)過(guò)4個(gè)基本的步驟。當(dāng)然,現(xiàn)實(shí)中的客戶(hù)端做的事情更多,不過(guò)即使它非常復(fù)雜,其核心也是這4個(gè)步驟。在這部分中我們將以最低級(jí)的方式介紹COM--使用簡(jiǎn)單的C++調(diào)用。
以下是我們將要進(jìn)行的4個(gè)步驟:
1、初始化COM子系統(tǒng),并且在完成時(shí)關(guān)閉它;
2、經(jīng)一個(gè)服務(wù)器的特有接口查詢(xún)COM
3、執(zhí)行接口上的方法
4、釋放該接口
為了簡(jiǎn)單,我們將使用一個(gè)極為簡(jiǎn)單的COM服務(wù)器。我們已經(jīng)假定服務(wù)器已經(jīng)寫(xiě)了出來(lái),并且有使用說(shuō)明。
該服務(wù)器擁有一個(gè)稱(chēng)為IBeep的接口。該接口只有一個(gè)方法,稱(chēng)為Beep。Beep接收一個(gè)參數(shù):持續(xù)時(shí)間。以下我們將寫(xiě)一個(gè)最簡(jiǎn)單的COM客戶(hù)來(lái)連接該服務(wù)器,并且調(diào)用Beep的方法。
以下就是實(shí)現(xiàn)這4個(gè)步驟的C++代碼。這是一個(gè)真正可以工作的COM客戶(hù)應(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ù)器時(shí),頭部的“BeepServer.h”會(huì)被創(chuàng)建。BeepServer是一個(gè)進(jìn)程內(nèi)的COM服務(wù)器,我們將在下一節(jié)再詳細(xì)討論。在編譯該服務(wù)器時(shí),開(kāi)發(fā)工具包還會(huì)自動(dòng)產(chǎn)生幾個(gè)頭文件。這個(gè)特別的頭文件定義了接口IBeepObj。編譯服務(wù)器還會(huì)在該程序的頂部產(chǎn)生GUID。我們將它從服務(wù)器工程的頂部拷貝了過(guò)來(lái)。
以下我們將詳細(xì)討論這4個(gè)步驟。初始化COM子系統(tǒng):
這是一個(gè)簡(jiǎn)單的步驟。我們需要使用的COM方法是CoInitialize():
CoInitialize(0);
該函數(shù)接收一個(gè)參數(shù),而該參數(shù)通常是一個(gè)0,這是它的起源OLE的一個(gè)慣例。CoInitialize函數(shù)初始化COM庫(kù)。在你做其它的處理之前,你需要調(diào)用這個(gè)函數(shù)。在更為專(zhuān)業(yè)的應(yīng)用中,我們將會(huì)使用擴(kuò)展的版本--CoInitializeEx。
在完成COM的所有處理后,你要調(diào)用CoUnInitialize()。這個(gè)函數(shù)將會(huì)卸載COM庫(kù)。我通常在自己的MFC應(yīng)用中的InitInstance()和ExitInstance()函數(shù)中包含這些調(diào)用。
大部分的COM函數(shù)返回一個(gè)稱(chēng)為HRESULT的錯(cuò)誤代碼。這個(gè)錯(cuò)誤的代碼包含了幾個(gè)字段,給出了錯(cuò)誤嚴(yán)格、簡(jiǎn)要定義和錯(cuò)誤的類(lèi)型。我們使用SUCCEDDED宏,因?yàn)镃OM可以返回幾個(gè)不同的成功代碼。只是檢驗(yàn)普通的成功代碼(S_OK)將是不夠周密的。我們將在后面更為詳細(xì)地討論HRESULT。
通過(guò)一個(gè)特別的接口查詢(xún)COM
COM客戶(hù)端感興趣的是它可以調(diào)用的函數(shù),在COM中,你可以通過(guò)接口來(lái)訪(fǎng)問(wèn)一套有用的函數(shù)。接口最簡(jiǎn)單的形式就是函數(shù)的一個(gè)集合。當(dāng)我們得到COM服務(wù)器的一個(gè)接口時(shí),我們就得到了一個(gè)指向一套函數(shù)的指針。
通過(guò)調(diào)用CoCreateInstance()函數(shù),你就可以得到一個(gè)接口的指針。這是一個(gè)非常強(qiáng)大的函數(shù),它可與COM子系統(tǒng)進(jìn)行交互,并做以下的事情:
查找服務(wù)器
開(kāi)始、載入或者連接到服務(wù)器
在服務(wù)器端創(chuàng)建一個(gè)COM對(duì)象
返回指向COM對(duì)象接口的一個(gè)指針
對(duì)于查找和訪(fǎng)問(wèn)接口,有兩種數(shù)據(jù)類(lèi)型是很重要的,它們是:CLSID和IID。它們都是GloballyUniqueID's(GUID's)。GUID's用作唯一辨認(rèn)所有的COM類(lèi)和接口。
為了得到某個(gè)特別的類(lèi)和接口,你需要它的GUID。要得到GUID,有許多方法。通常我們可以由服務(wù)器的頭文件得到CLSID和IID。在我們的例子中,我們?cè)谠创a的開(kāi)始部分使用#defind語(yǔ)句定義了GUID。通過(guò)接口的一般名字來(lái)查找GUID也很方便的。
讓我們得到接口指針的函數(shù)是CoCreateInstance。hr=CoCreateInstance(
CLSID_BeepObj,//COMclassid
NULL,//outerunknown
CLSCTX_INPROC_SERVER,//serverINFO
IID_IBeepObj,//interfaceid
(void**)&IBeep);//pointertointer第一個(gè)參數(shù)是一個(gè)GUID,它可唯一指定客戶(hù)端需要使用的COM類(lèi)。GUID或者CLSID是COM類(lèi)的標(biāo)識(shí)符。世界上的每個(gè)COM類(lèi)都有自己唯一的CLSID。COM將使用該ID來(lái)查找可產(chǎn)生請(qǐng)求COM對(duì)象的服務(wù)器。一旦連接到服務(wù)器,將會(huì)創(chuàng)建該對(duì)象。
第二個(gè)參數(shù)是一個(gè)指針,它指向“outerunknown”。我們不會(huì)使用這個(gè)參數(shù),因此傳送一個(gè)NULL。在涉及到“aggregation”(集合)概念時(shí),outerunknown是很重要的。aggregation可讓一個(gè)接口直接調(diào)用另一個(gè)COM接口而無(wú)需通知客戶(hù)端。aggregation和containment是接口用來(lái)調(diào)用其它接口的兩個(gè)方法。
第三個(gè)參數(shù)定義COM類(lèi)的Context或者CLSCTX。該參數(shù)控制服務(wù)器的范圍。我們可以通過(guò)它來(lái)控制服務(wù)器是進(jìn)程內(nèi)的服務(wù)器,還是一個(gè)EXE或者是在遠(yuǎn)程的計(jì)算機(jī)上。CLSCTX是一個(gè)位掩碼,因此你可以混合幾個(gè)值。這里我們使用的是CLSCTX_INPROC_SERVER--該服務(wù)器將運(yùn)行在本地的計(jì)算機(jī),并且作為一個(gè)DLL連接到客戶(hù)。由于進(jìn)程內(nèi)的服務(wù)器是最容易實(shí)現(xiàn)的,因此我們?cè)谶@個(gè)例子中選用它來(lái)講解。
通常客戶(hù)端都不用關(guān)心服務(wù)器是如何實(shí)現(xiàn)的。這時(shí)它將使用CLSCTX_SERVER的值,該服務(wù)器可以是一個(gè)本地的或者是進(jìn)程內(nèi)的。
接著是接口的標(biāo)識(shí)符或者IID。這是另一個(gè)GUID--用來(lái)標(biāo)識(shí)我們請(qǐng)求的接口。我們請(qǐng)求的IID必須是存在的,即被由CLSID指定的COM類(lèi)支持。再次,IID的值通常由一個(gè)頭文件提供,或者使用接口名查找出來(lái)。
最后的參數(shù)是指向一個(gè)接口的指針。CoCreateInstance()將創(chuàng)建所請(qǐng)求的類(lèi)對(duì)象和接口,并且返回一個(gè)指向接口的指針。這個(gè)參數(shù)也是CoCreateInstance調(diào)用的目的。然后我們就可以使用該接口指針來(lái)調(diào)用服務(wù)器上方法。
執(zhí)行接口上的一個(gè)方法
CoCreateInstance()使用COM來(lái)創(chuàng)建一個(gè)指向IBeep接口的指針。我們可以假設(shè)接口是指向一個(gè)普通C++類(lèi)的指針,不過(guò)事實(shí)上并不是。實(shí)際上,該接口指針指向一個(gè)稱(chēng)為VTABLE的結(jié)構(gòu),它是一個(gè)函數(shù)地址表。我們可以使用->操作符來(lái)訪(fǎng)問(wèn)接口指針。
由于我們的例子使用一個(gè)進(jìn)程內(nèi)的服務(wù)器,它將作為一個(gè)DLL載入到我們的程序中。忽略接口對(duì)象的細(xì)節(jié),得到該接口的目的是用來(lái)調(diào)用服務(wù)器上的一個(gè)方法。
hr=IBeep->Beep(800);
Beep()在服務(wù)器上執(zhí)行--它令計(jì)算機(jī)發(fā)出Beep聲。有許多簡(jiǎn)單的方法可讓一部計(jì)算機(jī)發(fā)出beep聲。如果我們擁有一個(gè)遠(yuǎn)程的服務(wù)器,它運(yùn)行在另一臺(tái)計(jì)算機(jī)上,該機(jī)器將發(fā)出beep聲。
接口的方法通常都帶有參數(shù)。這些參數(shù)必須是屬于COM支持的類(lèi)型之一。有不少的規(guī)定來(lái)控制接口支持的參數(shù)。我們將在MIDL的部分更詳細(xì)地討論這個(gè)問(wèn)題,MIDL是COM的接口定義工具。
釋放接口
C++的一個(gè)規(guī)則是所有分配的事物都應(yīng)該反分配。由于我們并不是使用new來(lái)創(chuàng)建接口,因此我們不能使用delete來(lái)刪除它。所有的COM接口都擁有一個(gè)稱(chēng)為Release()的方法來(lái)斷開(kāi)對(duì)象,并且刪除它。釋放一個(gè)接口是很重要的,因?yàn)樗稍试S服務(wù)器來(lái)清除它。如果你使用CoCreateInstance來(lái)創(chuàng)建一個(gè)接口,你將需要調(diào)用Release()。
總結(jié)
在這一節(jié)中我們講解了一個(gè)簡(jiǎn)單的COM客戶(hù)。COM是一個(gè)客戶(hù)驅(qū)動(dòng)的系統(tǒng)。所有都是為了令客戶(hù)更容易得到組件對(duì)象。相信該客戶(hù)程序的簡(jiǎn)單性會(huì)給你留下一個(gè)深刻的印象。這里定義的4個(gè)步驟可讓你使用大量的組件和大范圍的應(yīng)用。
其中的一些步驟是基本的,例如CoInitialize()和CoUninitialize()。其中的一些初次看來(lái)沒(méi)有太多的作用。不過(guò)從更高的級(jí)別來(lái)看,懂得這些也是重要的。我們將在以后的例子中進(jìn)一步談及。
VisualC++Version5和6通過(guò)使用“智能指針”和#import令客戶(hù)端的程序更加簡(jiǎn)化。在這個(gè)例子中我們使用的是一個(gè)低級(jí)的C++格式,以便更好地解釋這個(gè)概念。我們將在后面的部分討論智能指針和import。
在下一部分中,我們將建立一個(gè)簡(jiǎn)單的進(jìn)程內(nèi)服務(wù)器去管理IBeep接口。我們將在后面的章節(jié)繼續(xù)深入討論接口和激活的細(xì)節(jié)。理解簡(jiǎn)單的DCOM服務(wù)器
以上我們主要講解了如何通過(guò)一個(gè)客戶(hù)應(yīng)用使用COM。對(duì)于客戶(hù)來(lái)說(shuō),COM的編程技巧是相當(dāng)簡(jiǎn)單的。客戶(hù)端的應(yīng)用向COM子系統(tǒng)請(qǐng)求一個(gè)特定的組件,服務(wù)器端將其傳送過(guò)來(lái)。
實(shí)際上,對(duì)于后臺(tái)的組件管理工作,還需要寫(xiě)很多的代碼。真正的對(duì)象實(shí)現(xiàn)需要使用復(fù)雜的系統(tǒng)組件和標(biāo)準(zhǔn)的應(yīng)用模塊。就算是使用MFC,也是很復(fù)雜的。大多數(shù)的專(zhuān)業(yè)編程者都不會(huì)花時(shí)間來(lái)研究這個(gè)過(guò)程。自從COM的標(biāo)準(zhǔn)發(fā)布以來(lái),很快就令我們明白到讓開(kāi)發(fā)者來(lái)自己寫(xiě)這些代碼是不現(xiàn)實(shí)的。
當(dāng)你查看實(shí)現(xiàn)COM的真正代碼時(shí),你會(huì)發(fā)現(xiàn)其中大部分都是重復(fù)的。對(duì)于這類(lèi)復(fù)雜的問(wèn)題,傳統(tǒng)C++的解決之道是創(chuàng)建一個(gè)COM類(lèi)庫(kù)。實(shí)際上,MFCOLE類(lèi)提供了大部分的COM特性。
不過(guò)對(duì)于COM組件來(lái)說(shuō),MFC和OLE并不是一個(gè)好的選擇,有幾個(gè)理由。隨著ActiveX和微軟Internet策略的推出,COM對(duì)象應(yīng)該要非常的緊湊和快速。ActiveX需要COM對(duì)象可以經(jīng)過(guò)網(wǎng)絡(luò)相當(dāng)快地被復(fù)制。如果你使用MFC較多,就會(huì)發(fā)現(xiàn)它實(shí)在太大了(特別是在靜態(tài)鏈接時(shí))。通過(guò)網(wǎng)絡(luò)來(lái)傳送巨大的MFC對(duì)象是不現(xiàn)實(shí)的。
或許通過(guò)MFC/OLE方法來(lái)實(shí)現(xiàn)COM組件的最大問(wèn)題是復(fù)雜性。OLE編程是復(fù)雜的,并且大部分的編程者都不會(huì)在上面走得很遠(yuǎn)。有大量關(guān)于OLE的書(shū),這都說(shuō)明它是非常難以掌握的。
由于OLE的開(kāi)發(fā)有不少的難度,因此微軟創(chuàng)建了一個(gè)稱(chēng)為ATL(ActiveTemplateLibrary)新工具。對(duì)于COM編程來(lái)說(shuō),ATL是當(dāng)前最實(shí)用的工具。實(shí)際上,如果你對(duì)其背后的東西沒(méi)有興趣,使用ATL向?qū)?lái)編寫(xiě)COM服務(wù)器是相當(dāng)簡(jiǎn)單的。
這里介紹的例子都是通過(guò)ATL和ATL應(yīng)用向?qū)?lái)創(chuàng)建的。這一節(jié)我們將講解如何建立一個(gè)基于A(yíng)TL的服務(wù)器,并對(duì)向?qū)Мa(chǎn)生的代碼給出了一個(gè)摘要。關(guān)于代碼
有一點(diǎn)你要花時(shí)間去習(xí)慣,編寫(xiě)ATL服務(wù)器和傳統(tǒng)的編程是不一樣的。COM服務(wù)器其實(shí)是幾個(gè)獨(dú)立組件的協(xié)作構(gòu)成的,包括有:
。你的應(yīng)用
。COM子系統(tǒng)
。ATL模板類(lèi)
?!癐DL”代碼和MIDL產(chǎn)生的“C”頭文件和程序
。系統(tǒng)寄存器(注冊(cè)表)
要將一個(gè)基于A(yíng)TL的COM應(yīng)用作為一個(gè)整體看是挺困難的。即使你知道它正在做什么,還有很大一塊應(yīng)用你是看不到的。真正服務(wù)器中的大部分邏輯都深入隱藏在A(yíng)TL的頭文件中。你將不會(huì)找到一個(gè)單一的用來(lái)管理和控制服務(wù)器的main()函數(shù)。你只找到一個(gè)用來(lái)調(diào)用基本ATL對(duì)象的瘦外殼。
在以下的部分中,我們將把所有這些令服務(wù)器運(yùn)作的部分放在一起。首先我們會(huì)使用ATLCOM應(yīng)用向?qū)?lái)創(chuàng)建服務(wù)器。第二步我們將加入一個(gè)COM對(duì)象和一個(gè)方法。我們將寫(xiě)一個(gè)進(jìn)程內(nèi)的服務(wù)器,因?yàn)樗亲钊菀讓?shí)現(xiàn)的COM服務(wù)器之一。一個(gè)進(jìn)程內(nèi)的服務(wù)器也不用建立一個(gè)proxy和stub對(duì)象。
建立一個(gè)基于DLL(進(jìn)程內(nèi))的COM服務(wù)器
一個(gè)進(jìn)程內(nèi)的服務(wù)器就是一個(gè)會(huì)在運(yùn)行時(shí)載入到你程序中的COM類(lèi)。換句話(huà)說(shuō),就是一個(gè)動(dòng)態(tài)鏈接庫(kù)(DLL)中的COM對(duì)象。用傳統(tǒng)的觀(guān)點(diǎn)來(lái)看,一個(gè)DLL并不是一個(gè)真正的服務(wù)器,因?yàn)樗鼤?huì)直接載入到客戶(hù)的地址空間中。如果你熟悉DLL,你已經(jīng)知道了許多關(guān)于COM對(duì)象如何載入和映射到調(diào)用程序的知識(shí)。
通常在調(diào)用LoadLibrary()時(shí),DLL就會(huì)被載入。在COM中,你無(wú)需顯式調(diào)用LoadLibrary()。在客戶(hù)端的程序調(diào)用CoCreateInstance()時(shí),所有的處理都會(huì)自動(dòng)啟動(dòng)。CoCreateInstance需要的其中一個(gè)參數(shù)是你要使用的COM類(lèi)的GUID。當(dāng)服務(wù)器在編譯時(shí)創(chuàng)建時(shí),它就會(huì)登記了所有它支持的COM對(duì)象。當(dāng)客戶(hù)端需要該對(duì)象時(shí),COM找到服務(wù)器DLL,并且自動(dòng)裝載它。一旦載入,DLL就擁有了創(chuàng)建COM對(duì)象的類(lèi)庫(kù)。
CoCreateInstance()返回一個(gè)指向COM對(duì)象的指針,它是用來(lái)調(diào)用方法的(再這里的例子中,這個(gè)方法就是被稱(chēng)為Beep()的方法)。COM的一個(gè)便利之處是DLL可以在不需要的時(shí)候被自動(dòng)卸載。在對(duì)象被釋放和CoUninitialize()被調(diào)用后,F(xiàn)reeLibrary()將會(huì)被調(diào)用來(lái)卸載服務(wù)器DLL。
如果你對(duì)以上的都不熟悉也不要緊。要使用COM,你不需要知道關(guān)于DLL的任何知識(shí)。你所要做的是調(diào)用CoCreateInstance()。COM的其中一個(gè)好處是它隱藏了這些細(xì)節(jié),因此你無(wú)需擔(dān)心此類(lèi)問(wèn)題。
進(jìn)程內(nèi)的COM服務(wù)器有優(yōu)點(diǎn)也有缺點(diǎn)。如果動(dòng)態(tài)鏈接是你的系統(tǒng)設(shè)計(jì)中的重要一環(huán),那么你將發(fā)現(xiàn)COM可提供一個(gè)極好的方式來(lái)管理DLL。一些有經(jīng)驗(yàn)的編程者會(huì)將所有他們的DLL都寫(xiě)成為進(jìn)程內(nèi)的COM服務(wù)器。COM處理所有涉及載入、卸載的雜事,而輸出DLL函數(shù)和COM函數(shù)調(diào)用只有很少的系統(tǒng)開(kāi)銷(xiāo)。
我們選擇一個(gè)進(jìn)程內(nèi)服務(wù)器的主要理由就更簡(jiǎn)單了:它可令例子更加簡(jiǎn)單。我們不必關(guān)心如何啟動(dòng)遠(yuǎn)程的服務(wù)器(EXE或者服務(wù)),因?yàn)槲覀兊姆?wù)器將會(huì)在需要的時(shí)候自動(dòng)載入。我們也無(wú)需建立一個(gè)proxy/stubDLL來(lái)做marshalling的工作。
缺點(diǎn)是,由于進(jìn)程內(nèi)的服務(wù)器與我們的客戶(hù)綁定很緊密,因此COM許多重要的“分布”特性沒(méi)有展現(xiàn)出來(lái)。一個(gè)DLL服務(wù)器和它的客戶(hù)共享內(nèi)存,而一個(gè)分布的服務(wù)器將令客戶(hù)端更加隔離開(kāi)來(lái)。在一個(gè)分布的客戶(hù)和服務(wù)器間傳送數(shù)據(jù)的處理被稱(chēng)為marshaling。marshaling在COM上的利用是受到限制的,而在進(jìn)程內(nèi)的服務(wù)器中,我們無(wú)需關(guān)心這些。使用ATL向?qū)?chuàng)建服務(wù)器
為了讓你理解COM的基本規(guī)則,我們將創(chuàng)建一個(gè)非常簡(jiǎn)單的COM服務(wù)器。該服務(wù)器只有一個(gè)方法--Beep()。這個(gè)方法只是發(fā)出Beep的聲音--一個(gè)不是很有用的方法。我們將要做的是設(shè)置該服務(wù)器所有部分。一旦該體系設(shè)置完畢,要加入其它有用的方法就變得非常簡(jiǎn)單了。
使用ATLAppWizard是一個(gè)可快速產(chǎn)生一個(gè)COM服務(wù)器的簡(jiǎn)單方法。該向?qū)Э勺屛覀冞x擇所有基本的選項(xiàng),并且將產(chǎn)生我們需要的大部分代碼。以下就是一個(gè)產(chǎn)生服務(wù)器的詳細(xì)步驟。在這個(gè)程序中,我們將稱(chēng)該服務(wù)器為BeepServer。所有的COM服務(wù)器都至少要有一個(gè)接口,而我們的接口將會(huì)被稱(chēng)為IBeepObj。你可以為你的COM接口取任意的名字,不過(guò)如果你想遵循標(biāo)準(zhǔn)的命名傳統(tǒng),你最好使用“I”的前綴來(lái)命名它。
注意:很多人在這時(shí)可能還會(huì)分不清COM“對(duì)象”、“類(lèi)”和“接口”定義之間的區(qū)別。特別是對(duì)于C++的編程者,這些術(shù)語(yǔ)開(kāi)始都不太令人舒服。不過(guò),當(dāng)你了解這些例子后,你的混淆就會(huì)減少。在大部分的微軟文檔中,COM類(lèi)都用“coclass”來(lái)表示,以將COM類(lèi)和普通的C++類(lèi)區(qū)分開(kāi)來(lái)。
以下就是使用VisualC++version6來(lái)創(chuàng)建一個(gè)新的COM服務(wù)器的步驟(它與版本5中的幾乎一樣):
1。首先,創(chuàng)建一個(gè)新的“ATLCOMAppWizard”項(xiàng)目。由主菜單中選擇File/New。
2。在“New”的對(duì)話(huà)框中選擇“Projects”標(biāo)簽頁(yè)。在項(xiàng)目類(lèi)型中選擇“ATLCOMAppWizard”。選擇以下的選項(xiàng)并且按下OK。
。項(xiàng)目的名字:BeepServer
。創(chuàng)建新的Workspace
。Location:你的工作目錄
圖一
3。在第一個(gè)的AppWizard對(duì)話(huà)框中我們將創(chuàng)建一個(gè)基于DLL(進(jìn)程內(nèi))的服務(wù)器。輸入以下的設(shè)置:
。動(dòng)態(tài)鏈接庫(kù)
。不允許合并proxy/stub代碼
。不支持MFC
圖二
4。按下完成
AppWizard創(chuàng)建一個(gè)基于DLL的COM服務(wù)器,并且?guī)в兴斜匾奈募km然該服務(wù)器將可編譯和運(yùn)行,但它只是一個(gè)空殼。為了令它做我們想做的事情,我們將需要一個(gè)COM接口和支持該接口的類(lèi)。我們也必須寫(xiě)接口中的方法。
加入一個(gè)COM對(duì)象和一個(gè)方法
現(xiàn)在我們繼續(xù)COM對(duì)象的定義,包括接口和方法。類(lèi)的名字是BeepObj,它擁有一個(gè)稱(chēng)為IBeepObj的接口:
1。查看“ClassView”的標(biāo)簽頁(yè)。在開(kāi)始的時(shí)候它的列表中僅有唯一一個(gè)項(xiàng)目。右擊“BeepServerClasses”項(xiàng)
2。選擇“NewATLObject...”。這個(gè)步驟也可通過(guò)主菜單來(lái)完成。在彈出的菜單項(xiàng)中選擇“NewATLObject”。
圖三
3.在對(duì)象向?qū)У膶?duì)話(huà)框中選擇“Objects”。選擇“SimpleObject”并且按Next。
圖四
4。選擇Names的標(biāo)簽頁(yè)。輸入對(duì)象的名字:BeepObj。其余所有的選擇都會(huì)自動(dòng)填入標(biāo)準(zhǔn)的名字
圖五
5。按下“Attributes”標(biāo)簽頁(yè)并且選擇ApartmentThreading,CustomInterface,NoAggregation。很明顯,在aggregation在這個(gè)服務(wù)器中并沒(méi)有用到。
圖六
6。按下OK,這將創(chuàng)建Com對(duì)象
為服務(wù)器加入一個(gè)方法
我們現(xiàn)在已經(jīng)創(chuàng)建了一個(gè)空的COM對(duì)象。不過(guò),它還是一個(gè)無(wú)用的對(duì)象,因?yàn)樗⒉蛔鋈魏蔚氖虑?。我們將?chuàng)建一個(gè)稱(chēng)為Beep()的簡(jiǎn)單方法,它可令系統(tǒng)發(fā)出一次beep聲。我們的COM方法將調(diào)用Win32API函數(shù):Beep()。
1。打開(kāi)“ClassView”標(biāo)簽。選擇IBeepObj的接口。該接口有由一個(gè)類(lèi)似匙的小圖標(biāo)代表
圖七
2。右擊IBeepObj的接口。由菜單中選擇“AddMethod”。
3。在“AddMethodtoInterface”對(duì)話(huà)框中,輸入以下的信息并且按下OK。加入“Beep”的方法并且給它一個(gè)單一的[in]參數(shù)作為持續(xù)時(shí)間。這將是發(fā)beep音的長(zhǎng)度,以毫秒計(jì)。
圖八
4.“AddMethod”已經(jīng)創(chuàng)建了我們定義方法的MIDL定義。該定義以IDL編寫(xiě),并且定義該方法到MIDL編譯器。如果你想看IDL的代碼,雙擊“CalssView”標(biāo)簽頁(yè)中的“IBeepObj”接口。這將打開(kāi)和顯示BeepServer.IDL文件的內(nèi)容。我們沒(méi)有必要改變這個(gè)文件,我們的接口定義如下所示:interfaceIBeepObj:IUnknown
{
[helpstring("methodBeep")]
HRESULTBeep([in]LONGduration);
};IDL的句法與C++類(lèi)似。這一行和C++的函數(shù)原型相當(dāng)。我們將在以后談?wù)揑DL的句法。
5?,F(xiàn)在我們將要寫(xiě)該方法的C++代碼。AppWizard已經(jīng)為我們的C++函數(shù)寫(xiě)入一個(gè)空殼,并且將它加入到頭文件的類(lèi)定義中(BeepServer.H)。
打開(kāi)BeepObj.CPP的源文件。找到//TODO:行并且加入到APIBeep函數(shù)的調(diào)用。修改Beep()方法為如下:STDMETHODIMPCBeepObj::Beep(LONGduration)
{
//TODO:Addyourimplementationcodehere
::Beep(550,duration);
returnS_OK;
}6。保存文件,并且編譯該項(xiàng)目
我們已經(jīng)擁有一個(gè)完整的COM服務(wù)器了。當(dāng)項(xiàng)目結(jié)束編譯時(shí),你應(yīng)該會(huì)看到如下的信息: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)這意味著開(kāi)發(fā)工具已經(jīng)完成了以下的步驟:
。執(zhí)行MIDL編譯器來(lái)產(chǎn)生代碼和類(lèi)庫(kù)
。編譯源文件
。鏈接項(xiàng)目來(lái)創(chuàng)建BeepServer.DLL
。注冊(cè)COM組件
。使用RegSvr32注冊(cè)DLL,以便在需要的時(shí)候自動(dòng)下載
看看我們產(chǎn)生的項(xiàng)目吧。在我們按下按鈕的時(shí)候,AppWizard已經(jīng)產(chǎn)生了文件。如果你觀(guān)看“FileView”標(biāo)簽,可看到已經(jīng)產(chǎn)生了以下的文件源文件描述BeepServer.dswProjectworkspaceBeepServer.dsp項(xiàng)目文件BeepServer.plg項(xiàng)目的日志文件,包含了項(xiàng)目建立時(shí)的詳細(xì)錯(cuò)誤信息BeepServer.cppDLL主程序,DLL輸出的實(shí)現(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,它定義了注冊(cè)表的腳本,用來(lái)將COM的信息載入到寄存器中。
Resource.h微軟DeveloperStudio產(chǎn)生的包含文件StdAfx.cpp預(yù)編譯頭的源Stdafx.h標(biāo)準(zhǔn)的頭BeepServer.tlb由MIDL產(chǎn)生的類(lèi)庫(kù)。該文件是COM接口和對(duì)象的二進(jìn)制描述。作為連接一個(gè)客戶(hù)的一個(gè)可選方法,TypeLib是非常有用的BeepObj.cppCBeepObj的實(shí)現(xiàn)。該文件包含了所有真正的C++代碼,用來(lái)實(shí)現(xiàn)COMBeepObj對(duì)象中的所有方法。BeepObj.hBeepObjCOM對(duì)象的定義BeepObj.rgs注冊(cè)表的腳本,用來(lái)在注冊(cè)表中登記COM組件。在服務(wù)器工程被建立時(shí),注冊(cè)是自動(dòng)進(jìn)行的BeepServer_i.c包含了IID和CLSID的真正定義,這個(gè)文件通常被包含在cpp代碼中。
還有另外幾個(gè)proxy/stub文件,由MIDL產(chǎn)生。只是幾分鐘,我們就創(chuàng)建了一個(gè)完整的COM服務(wù)器應(yīng)用。在沒(méi)有向?qū)У娜兆永铮瑢?xiě)一個(gè)服務(wù)器將需要數(shù)個(gè)小時(shí)。向?qū)У娜秉c(diǎn)是我們有了一大塊沒(méi)有完全弄懂的代碼。在下面的部分我們將更為詳細(xì)地查看產(chǎn)生的模塊,然后是整個(gè)的應(yīng)用。
總結(jié)
整個(gè)服務(wù)器的代碼幾乎都是完全由ATL向?qū)Мa(chǎn)生的。我們使用的是一個(gè)基于DLL的服務(wù)器。不過(guò)這個(gè)過(guò)程對(duì)于所有的服務(wù)器類(lèi)型幾乎都是一樣的。使用這個(gè)架構(gòu),我們可以很快的開(kāi)發(fā)出一個(gè)服務(wù)器應(yīng)用,因?yàn)槟悴恍柚涝S多的細(xì)節(jié)就可以開(kāi)始工作。
在以下的章節(jié)中,我們將查看進(jìn)程內(nèi)服務(wù)器和ATL的代碼。我們已經(jīng)討論了DCOM的基本要點(diǎn),了解了如何創(chuàng)建一個(gè)簡(jiǎn)單的DCOM服務(wù)器和一個(gè)相關(guān)的客戶(hù)端。你也可以看到這個(gè)基本的過(guò)程是非常簡(jiǎn)單的ATL向?qū)幚砹朔?wù)器端的大部分細(xì)節(jié),要激活服務(wù)器,你只需要在客戶(hù)端寫(xiě)10行左右的代碼就可以了。
接下來(lái)我們將討論兩個(gè)相關(guān)的主題。首先是創(chuàng)建你自己的COM客戶(hù)和服務(wù)器,結(jié)合第一部分我們所學(xué)到的,讓你了解要在自己的代碼中集成一個(gè)DCOM服務(wù)器,確實(shí)需要做哪些事情。然后我們將快速地看一下由ATL向?qū)Мa(chǎn)生的代碼。
本文的最后將會(huì)講解要?jiǎng)?chuàng)建一個(gè)分布式的COM服務(wù)器,你需要經(jīng)過(guò)的步驟。所謂分布式的COM服務(wù)器,是指該服務(wù)器可以處在網(wǎng)絡(luò)的別處,并且可通過(guò)網(wǎng)絡(luò)非常簡(jiǎn)單和透明地激活。
創(chuàng)建自己的COM客戶(hù)和服務(wù)器
在第一部分的DCOM介紹中,你可以看到要?jiǎng)?chuàng)建COM客戶(hù)和服務(wù)器是非常簡(jiǎn)單的。只要在客戶(hù)和服務(wù)器端寫(xiě)入幾行代碼就可以產(chǎn)生一個(gè)完整的COM應(yīng)用。你現(xiàn)在明白到為什么許多的開(kāi)發(fā)者在創(chuàng)建一個(gè)DLL時(shí)會(huì)使用COM了--因?yàn)閮H需要大概5分鐘,就可以設(shè)置好一個(gè)進(jìn)程內(nèi)的COMDLL,并且令它工作。
本部分的目的是討論如何創(chuàng)建自己的COM服務(wù)器,并且在你創(chuàng)建的真正應(yīng)用中使用它們。你也會(huì)記得,第一部分介紹的客戶(hù)端代碼是非常少的。我們將介紹要?jiǎng)?chuàng)建服務(wù)器需要進(jìn)行的基本步驟,然后看看要正確地激活服務(wù)器,你需要在客戶(hù)端寫(xiě)入哪些代碼。
服務(wù)器端
ATL向?qū)Я頒OM服務(wù)器的創(chuàng)建變得非常的簡(jiǎn)單。創(chuàng)建一個(gè)COMcoclass的第一步是要分離出一個(gè)或者多個(gè)的功能函數(shù),你要從一個(gè)應(yīng)用的代碼主體中分離出這些功能函數(shù)。至于分離出來(lái)的目的,可以是多樣的,你可能是想讓該函數(shù)可以跨越多個(gè)應(yīng)用重新使用,也可能是讓一個(gè)隊(duì)伍的編程者更容易地分離出各個(gè)獨(dú)立的工作組,或者是讓代碼的開(kāi)發(fā)和維護(hù)變得更加的簡(jiǎn)單。不論是出于什么原因,定義功能是第一步。
有一點(diǎn)可能令定義這些邊界變得更為簡(jiǎn)單,這就是COM服務(wù)器的運(yùn)作和一個(gè)普通的C++類(lèi)是幾乎一樣的。象一個(gè)類(lèi),你實(shí)例化一個(gè)COM類(lèi),然后可以開(kāi)始調(diào)用它的方法。COM的實(shí)例化和方法調(diào)用的句法和C++是有點(diǎn)不同的,不過(guò)它們的想法是一樣的。如果一個(gè)服務(wù)器僅有一個(gè)接口,它事實(shí)上的用法就相當(dāng)于一個(gè)類(lèi)。(不過(guò)在訪(fǎng)問(wèn)對(duì)象時(shí),你仍然需要遵守COM的規(guī)定)
一旦你已經(jīng)定義了功能和訪(fǎng)問(wèn)它的方法,就可以建立自己的服務(wù)器。在第一部分中,我們已經(jīng)知道,要?jiǎng)?chuàng)建一個(gè)服務(wù)器,有4個(gè)基本的步驟:
1。使用ATL向?qū)?lái)創(chuàng)建你的COM服務(wù)器的外殼。你選擇該服務(wù)器是一個(gè)DLL、一個(gè)EXE或者是一個(gè)服務(wù)。
2。在服務(wù)器的外殼中創(chuàng)建一個(gè)新的COM對(duì)象。你將要選擇線(xiàn)程的模式,這將會(huì)創(chuàng)建可裝入方法的接口。
3。在你的對(duì)象中加入方法,并且聲明它們的參數(shù)
4。為你的方法寫(xiě)代碼
上面的這些步驟已經(jīng)在第一部分中的“理解一個(gè)簡(jiǎn)單的COM服務(wù)器”中詳細(xì)介紹過(guò)了。
經(jīng)過(guò)第一部分的介紹后,一個(gè)常見(jiàn)的問(wèn)題是關(guān)于線(xiàn)程模式,也就是COM對(duì)象的獨(dú)立線(xiàn)程(apartment-threade)和自由線(xiàn)程(free-threaded)之間的區(qū)別?要理解它們之間的區(qū)別的最簡(jiǎn)單方法是將獨(dú)立線(xiàn)程看成為單線(xiàn)程,而將自由線(xiàn)程想象為多線(xiàn)程。
在獨(dú)立線(xiàn)程中,多個(gè)服務(wù)器客戶(hù)的方法調(diào)用在服務(wù)器端的COM對(duì)象中被串行化,也就是說(shuō),每個(gè)獨(dú)立的方法調(diào)用完成后,才會(huì)開(kāi)始下一個(gè)的方法調(diào)用。因此獨(dú)立線(xiàn)程的COM對(duì)象天生就是線(xiàn)程安全的,而自由線(xiàn)程的COM對(duì)象可同時(shí)在COM對(duì)象上有多個(gè)的方法調(diào)用執(zhí)行。每個(gè)客戶(hù)的方法調(diào)用都在一個(gè)不同的線(xiàn)程中運(yùn)行。因此,在一個(gè)自由線(xiàn)程的COM對(duì)象中,你必須要注意多線(xiàn)程的問(wèn)題,例如同步。
開(kāi)始的時(shí)候你將更趨向于使用獨(dú)立的線(xiàn)程,因?yàn)樗雍?jiǎn)單,不過(guò)以后最好轉(zhuǎn)向到自由線(xiàn)程,因?yàn)樗兄嗟膬?yōu)點(diǎn)。
客戶(hù)端
第一部分介紹的客戶(hù)端程序非常清楚和緊湊。不過(guò),它包含很少的錯(cuò)誤檢測(cè)代碼,因此要在一個(gè)真正的程序中應(yīng)用是不足夠的。讓我們?cè)俅慰匆幌逻@些代碼,它非常簡(jiǎn)單,因此可讓你清楚地看到要?jiǎng)?chuàng)建一個(gè)客戶(hù)端的必要步驟。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,并且得到指向一個(gè)接口的指針。然后你就可以調(diào)用接口的方法。在完成方法調(diào)用后,你就可釋放接口并且調(diào)用CoUninitialize。整個(gè)步驟就完成了。
不過(guò),在一個(gè)COM客戶(hù)嘗試啟動(dòng)一個(gè)COM服務(wù)器時(shí),可出現(xiàn)各種不同的錯(cuò)誤。一些常見(jiàn)的問(wèn)題包括有:
.客戶(hù)端不能啟動(dòng)COM
.客戶(hù)端不能查找到請(qǐng)求的服務(wù)器
.客戶(hù)端能查找到請(qǐng)求的服務(wù)器,但是不能正確地啟動(dòng)
.客戶(hù)端不能找到請(qǐng)求的接口
.客戶(hù)端不能找到請(qǐng)求的方法
.客戶(hù)端可以找到請(qǐng)求的方法,但在調(diào)用時(shí)失敗
.客戶(hù)端不能正確地清除
為了跟蹤這些潛在的問(wèn)題,你必須在每一步作檢查,具體是查看HRESULT的值。以上的代碼有作檢查,不過(guò)在出錯(cuò)的時(shí)候并沒(méi)提示。以下的函數(shù)補(bǔ)救了這個(gè)不足://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的值,并且打印出它的所有成員,包括有極為有用的英語(yǔ)錯(cuò)誤信息值ErrorMessage。你可以在任何的時(shí)候調(diào)用它:
//displayHRESULTonscreen
ShowStatus(hr);
要完整了解一個(gè)簡(jiǎn)單的COM程序可產(chǎn)生的不同錯(cuò)誤模式,以下的客戶(hù)端解釋代碼使用了一個(gè)MFC對(duì)話(huà)框應(yīng)用,可讓你控制一些可能的錯(cuò)誤信息,并且看到該信息如何影響HRESULT的值??蛻?hù)端的運(yùn)行如圖1所示。
圖一
左邊的單選按鈕可讓你選擇各種的錯(cuò)誤,包括有缺少CoInitialize函數(shù),一個(gè)錯(cuò)誤的classID,以及一個(gè)錯(cuò)誤的接口ID。如果你按下運(yùn)行的按鈕,在右邊你將可看到客戶(hù)端的不同函數(shù)返回的不同錯(cuò)誤對(duì)HRESULT的值的影響。
在你使用該例子中的客戶(hù)端代碼時(shí),你將會(huì)發(fā)現(xiàn)該版本要比我們?cè)瓉?lái)使用的標(biāo)準(zhǔn)客戶(hù)端代碼強(qiáng)壯。它還允許通過(guò)DCOM作遠(yuǎn)程連接。例如,它使用CoInitializeSecurity函數(shù)來(lái)設(shè)置默認(rèn)的安全性,它還使用CoCreateInstanceEx函數(shù)以便另一臺(tái)機(jī)器上的遠(yuǎn)程服務(wù)器可以被調(diào)用。研究一下這些代碼,在文檔中查找這兩個(gè)函數(shù),你將會(huì)驚奇地發(fā)現(xiàn)要理解它是非常簡(jiǎn)單的,你終于對(duì)COM有了一些了解。理解ATL產(chǎn)生的代碼
我們服務(wù)器端DLL的源代碼是由ATL產(chǎn)生的。對(duì)于許多人來(lái)說(shuō),可以完全不用了解ATL創(chuàng)建的代碼。不過(guò),對(duì)于一些喜歡尋根究底的人來(lái)說(shuō),這是不可以接受的。這里就介紹一下由ATL產(chǎn)生的代碼。
服務(wù)端的DLL代碼由三種不同類(lèi)型的文件組成
首先,是傳統(tǒng)的C++源文件和頭文件。在開(kāi)始時(shí),所有這些代碼是由ATL向?qū)Мa(chǎn)生的
Beep方法是通過(guò)使用“AddMethod”對(duì)話(huà)框加入的,它修改了MIDL接口的定義。MIDL的源代碼是一個(gè)IDL文件--在這個(gè)例子中它是BeepServer.IDL。MIDL編譯器將使用該文件來(lái)創(chuàng)建幾個(gè)輸出文件。這些文件負(fù)責(zé)了大部分實(shí)現(xiàn)服務(wù)器的工作。當(dāng)我們?yōu)镃OM對(duì)象加入方法時(shí),我們也將在IDL加入一些東西。
第三組的源文件是自動(dòng)產(chǎn)生的MIDL輸出文件,是由MIDL編譯器產(chǎn)生的。這些文件是源代碼文件,不過(guò)由于它們是由MIDL編譯器通過(guò)IDL源代碼自動(dòng)產(chǎn)生的,因此不能被向?qū)Щ蛘唛_(kāi)發(fā)者直接修改。你可以將它們稱(chēng)為“第二產(chǎn)生文件”--向?qū)?chuàng)建了一個(gè)IDL文件,而MIDL編譯器由該IDL文件創(chuàng)建了源代碼文件。由MIDL創(chuàng)建的文件包括有:
BeepServer.RGS-服務(wù)器的注冊(cè)腳本
BeepServer.h-該文件包括了COM組件的定義
BeepServer_i.c-COM組件的GUID結(jié)構(gòu)
Proxy/Stubfiles-包括了"C"源代碼、DLL定義,以及Proxy和Stub的makefile(.mk)
ATL向?qū)н€創(chuàng)建了一個(gè)應(yīng)用的“資源”。如果你查看項(xiàng)目的資源,你將會(huì)發(fā)現(xiàn)它處在“REGISTRY”下。該資源包含了BeepServer.RGS中定義的注冊(cè)腳本。資源的名字是IDR_BEEPOBJ。
我們將在以下的部分看一下這些不同的組件
主C++模塊
我們?cè)谶\(yùn)行ATLCOMAppwizard時(shí),我們選擇創(chuàng)建一個(gè)基于DLL的服務(wù)器,并且選擇不使用MFC。向?qū)У牡谝粋€(gè)選擇屏幕決定了服務(wù)器的整體配置。
AppWizard創(chuàng)建了一個(gè)標(biāo)準(zhǔn)的DLL模塊。該類(lèi)的標(biāo)準(zhǔn)DLL并沒(méi)有一個(gè)WinMain應(yīng)用循環(huán),不過(guò)它有一個(gè)DllMain函數(shù)用作在載入時(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ù)的真正工作是檢查有沒(méi)有一個(gè)客戶(hù)連上該DLL,然后會(huì)做一些初始化的工作。一眼看去,并沒(méi)有一個(gè)明顯的指示這是一個(gè)COM應(yīng)用。
我們新服務(wù)器的COM部分被封裝到ATL類(lèi)CComModule中。CComModule是ATL服務(wù)器的基類(lèi)。它包含了所有用作登記和運(yùn)行服務(wù)器、開(kāi)始和維護(hù)COM對(duì)象的COM邏輯。CComModule被定義在頭文件“altbase.h”中。該代碼用以下的行聲明一個(gè)全局的CComMoudule對(duì)象:
CComModule_Module;
這個(gè)單一的對(duì)象包含了許多用作我們應(yīng)用的COM服務(wù)器功能。它在程序執(zhí)行開(kāi)始時(shí)的創(chuàng)建和初始化設(shè)置了一連串的事件動(dòng)作。
ATL需要你的服務(wù)器命名它的全局CComModule對(duì)象“_Module”。使用你自己的類(lèi)來(lái)覆蓋CComModule是可以的,不過(guò)你不能改變它的名字。
如果我們選擇創(chuàng)建一個(gè)可執(zhí)行的服務(wù)器,或者一個(gè)帶MFC的DLL,代碼將會(huì)是完全不同。這時(shí)還會(huì)有一個(gè)基于CComModule的全局對(duì)象,不過(guò)程序的入口將會(huì)是WinMain()。選擇一個(gè)基于MFC的DLL將會(huì)創(chuàng)建一個(gè)基于CWinApp的主對(duì)象。
對(duì)象映射
CComModule通過(guò)在前面部分看到的對(duì)象映射連接到我們的COM對(duì)象(CBeepObj)。一個(gè)對(duì)象映射定義了該服務(wù)器控制的所有COM對(duì)象的一個(gè)數(shù)組。對(duì)象映射在代碼中使用OBJECT_MAP宏定義。以下就是我們DLL的對(duì)象映射:BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_BeepObj,CBeepObj)
END_OBJECT_MAP()OBJECT_ENTRY宏通過(guò)一個(gè)C++類(lèi)與對(duì)象的CLSID關(guān)聯(lián)。一個(gè)服務(wù)器中包含有多于一個(gè)的COM對(duì)象是很常見(jiàn)的。這時(shí),對(duì)于每個(gè)對(duì)象,都將會(huì)有一個(gè)OBJECT_ENTRY。
輸出文件
我們這個(gè)進(jìn)程內(nèi)的DLL和大部分的DLL一樣,都擁有一個(gè)輸出文件。輸出文件將被客戶(hù)端用來(lái)連接到我們DLL中的外部函數(shù)。這些定義都放在BeepServer.def文件中:;BeepServer.def:Declaresthemoduleparameters.
LIBRARY"BeepServer.DLL"
EXPORTS
DllCanUnloadNow@1PRIVATE
DllGetClassObject@
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 二零二五版高科技產(chǎn)品出口許可與合同履行協(xié)議3篇
- 二零二五版國(guó)際貿(mào)易合同擔(dān)保法風(fēng)險(xiǎn)管理合同3篇
- 碎石加工設(shè)備2025年度保險(xiǎn)合同2篇
- 二零二五版企業(yè)員工勞務(wù)派遣與員工福利保障合同3篇
- 二零二五年度糧食儲(chǔ)備與農(nóng)業(yè)產(chǎn)業(yè)化合作合同3篇
- 二零二五年度高層綜合樓公共收益分配管理合同3篇
- 二零二五年度校車(chē)運(yùn)營(yíng)服務(wù)與兒童座椅安全檢測(cè)合同3篇
- 二零二五版帶儲(chǔ)藏室裝修包售二手房合同范本3篇
- 二零二五年房地產(chǎn)合作開(kāi)發(fā)與股權(quán)讓渡綜合合同2篇
- 二零二五年度花木種植與生態(tài)農(nóng)業(yè)園區(qū)建設(shè)合同3篇
- 2024年高標(biāo)準(zhǔn)農(nóng)田建設(shè)土地承包服務(wù)協(xié)議3篇
- 閱讀理解(專(zhuān)項(xiàng)訓(xùn)練)-2024-2025學(xué)年湘少版英語(yǔ)六年級(jí)上冊(cè)
- 2024-2025學(xué)年人教版數(shù)學(xué)六年級(jí)上冊(cè) 期末綜合試卷(含答案)
- 2024年全國(guó)統(tǒng)一高考英語(yǔ)試卷(新課標(biāo)Ⅰ卷)含答案
- 2024年認(rèn)證行業(yè)法律法規(guī)及認(rèn)證基礎(chǔ)知識(shí) CCAA年度確認(rèn) 試題與答案
- 醫(yī)院患者傷口換藥操作課件
- 欠薪強(qiáng)制執(zhí)行申請(qǐng)書(shū)
- 礦山年中期開(kāi)采重點(diǎn)規(guī)劃
- 資源庫(kù)建設(shè)項(xiàng)目技術(shù)規(guī)范匯編0716印刷版
- GC2級(jí)壓力管道安裝質(zhì)量保證體系文件編寫(xiě)提綱
- 預(yù)應(yīng)力混凝土簡(jiǎn)支小箱梁大作業(yè)計(jì)算書(shū)
評(píng)論
0/150
提交評(píng)論