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

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

PAGEPAGE38介紹

對于許多人來說,學習COM和DCOM是一件吃力的事情。COM的用處很大,不少微軟的產品和編程者工具都是基于COM,不過,COM是一門頗難掌握的技術,你可能曾經想去學習它,閱讀過一些書,使用過一些向導等,不過還是不太懂。它看來很復雜,而且還帶有不少的新名詞,例如"marshalling","apartmentthreads","singletonobjects"等,讓你摸不著頭腦。

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

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

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

簡單的COM服務器--使用ATL向導來建立一個服務器

下載工程文件

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

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

COM的基本要素

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

一、類和對象

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

public:

intMethodA(inta);

intMethodB(floatb);

floatMethodC(floatc);

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

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

px=newxxx;//創建對象

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

deletepx;//釋放對象

你要明白到,COM使用相同的面向對象模型。COM擁有與C++對象一樣的類、成員函數和實例。雖然你從來不會在一個COM對象上調用new方法,不過你必須在內存中創建它。你通過指針來訪問COM對象,在你完成處理后,你必須釋放它們。

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

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

CoCreateInstance(,,,,&pi)//創建接口

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

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

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

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

在某種程度上,COM對象要比它們的同胞C++更復雜,從網絡應用方面考慮,大多數的復雜性都是必要的。以下就是在設計COM時的4個基本要素:

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

。COM方法可通過網絡調用

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

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

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

COM可以跨進程運行

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

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

COM方法可以通過網絡調用

如果你可以訪問網絡上某臺機器,而你想要使用的某個對象的COM服務器已經被安裝在該機器上,你就可以在那臺機器上創建COM對象。當然,你必須要有相應的權限,并且那臺機器上已經進行了正確的設置。

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

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

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

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

COM是語言無關的

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

COM詞匯

我們碰到的其中一個問題是要記住兩套術語。你可能已經熟悉C++和一些面向對象的術語。以下的表格將COM和傳統術語間類似的地方列了出來。

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

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

接口

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

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

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

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

public:

intDoThis();

private:

voidHelper1();

intcount;

intx,y,z;

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

classyyy{

intDoThis();

};

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

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

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

接口將客戶和服務器隔離開來

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

客戶端程序對于實現COM的COM對象或者C++類一無所知。它只能看到接口。接口就象COM對象的一個窗口。接口的設計者只讓客戶看到設計者希望展示的部分。圖一展示了客戶是如何通過接口來訪問一個COM對象的。

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

COM接口的形象化

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

Left()On()

Right()Off()

Slower()Louder()

Faster()Softer()

Forward()NextStation()

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

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

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

組件的形象化

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

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

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

所有接口的源頭--IUnknown

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

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

一個典型的COM對象

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

想象一下,如果你要創建一個最簡單的COM對象。這個對象支持一個單一的接口,并且該接口只含有一個單一的函數。這個函數的功能也很簡單--只是發出beep聲。當一個編程者創建該COM對象,并且調用該對象支持的單一接口中的成員函數時,COM對象存在的機器將會發出beep聲。更進一步的是,你要在一臺機器上運行這個COM對象,但是從網絡上的另一臺機器來調用它。

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

。你必須創建該COM對象,并且給它一個名字。該對象將會在一個相關的COM服務器中實現

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

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

。你將需要安裝COM服務器

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

如何做到唯一--GUID

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

1。通過一些準政府組織來登記名字;

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

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

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

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

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

{

unsignedlongData1;

unsignedshortData2;

unsignedshortData3;

unsignedcharData4[8];

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

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

COM對象和COM接口都有一個GUID來標識自己。因此我們為該對象選用的名字“Beeper”是沒有關系的。對象是通過它的GUID來命名的。我們將該對象的GUID稱為它的classID。然后我們就可以使用一個#defind或者一個常數來令Beeper的名字和該GUID相關,這樣我們就無需在代碼中都使用這個128位的值。同樣接口也將擁有一個GUID。要注意的是許多由不同的編程者來創建的不同COM對象將支持同樣的IBeep接口,而它們都將全部使用同樣的GUID來命名它。如果沒有同樣的GUID,COM就認為這是一個不同的接口。GUID就是它的名字。

一個COM服務器

COM服務器就是實現COM接口和類的程序。COM服務器有三個基本的配置。

。進程中或者DLL服務器

。Stand-aloneEXE服務器

。基于WindowsNT的服務

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

進程中的服務器是作為動態連接庫(DLL)實現的。這意味著該服務器在運行時被動態地放進你的進程中

COM服務器將成為你應用中的一部分,而COM操作在應用的線程中進行。事實上,許多的COM對象都是以這種方式實現的,因為性能很好--一個COM函數調用的系統開銷很小,但你可以得到COM所有的設計和重用的好處

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

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

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

還有第四種的服務器,稱為“surrogate”,這是一個可允許進程中的服務器在遠程運行的程序。對于要建立一個可通過網絡訪問的基于DLL的COM服務器,surrogate是很有用的。

客戶端和服務器端的交互

在COM中,客戶端程序驅動所有的事情。服務器是被動的,只響應客戶的請求。這意味著對于客戶的個別方法調用,COM服務器以一個同步的方式運作

。客戶端的程序啟動服務器

??蛻舳苏埱驝OM對象和接口

??蛻舳税l起所有的方法調用到服務器

??蛻舳酸尫欧掌鞯慕涌冢试S服務器關閉

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

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

客戶請求

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

服務器響應

。啟動服務器(如果需要)。如果是一個進程內的服務器,DLL將被載入??蓤绦械姆掌鲗⒂蒘CM運行

。創建請求的COM對象

。創建到COM對象的一個接口

。增加激活接口的引用計數

。返回該接口給客戶

客戶請求

調用接口的一個方法

服務器響應

執行一個COM對象的方法

客戶請求

釋放接口

服務器響應

減少接口引用的數目

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

如果沒有活動的連接,關閉服務器。某些服務器不會關閉自身

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

三、總結

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

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

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

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

我們不斷強調以上的內容是有理由的,因為COM服務器和客戶的分別可以是更為復雜的。許多時候,應用客戶也將是一個COM服務端,而應用的服務器也可是一個COM客戶。一個應用同時是COM客戶和服務器是很常見的。在這一章中,我們將讓這個區別最簡單化,涉及的只是一個純COM客戶。

客戶端連接的4個步驟

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

以下是我們將要進行的4個步驟:

1、初始化COM子系統,并且在完成時關閉它;

2、經一個服務器的特有接口查詢COM

3、執行接口上的方法

4、釋放該接口

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

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

以下就是實現這4個步驟的C++代碼。這是一個真正可以工作的COM客戶應用。#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;

}在編譯服務器時,頭部的“BeepServer.h”會被創建。BeepServer是一個進程內的COM服務器,我們將在下一節再詳細討論。在編譯該服務器時,開發工具包還會自動產生幾個頭文件。這個特別的頭文件定義了接口IBeepObj。編譯服務器還會在該程序的頂部產生GUID。我們將它從服務器工程的頂部拷貝了過來。

以下我們將詳細討論這4個步驟。初始化COM子系統:

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

CoInitialize(0);

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

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

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

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

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

通過調用CoCreateInstance()函數,你就可以得到一個接口的指針。這是一個非常強大的函數,它可與COM子系統進行交互,并做以下的事情:

查找服務器

開始、載入或者連接到服務器

在服務器端創建一個COM對象

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

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

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

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

CLSID_BeepObj,//COMclassid

NULL,//outerunknown

CLSCTX_INPROC_SERVER,//serverINFO

IID_IBeepObj,//interfaceid

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

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

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

通??蛻舳硕疾挥藐P心服務器是如何實現的。這時它將使用CLSCTX_SERVER的值,該服務器可以是一個本地的或者是進程內的。

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

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

執行接口上的一個方法

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

由于我們的例子使用一個進程內的服務器,它將作為一個DLL載入到我們的程序中。忽略接口對象的細節,得到該接口的目的是用來調用服務器上的一個方法。

hr=IBeep->Beep(800);

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

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

釋放接口

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

總結

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

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

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

在下一部分中,我們將建立一個簡單的進程內服務器去管理IBeep接口。我們將在后面的章節繼續深入討論接口和激活的細節。理解簡單的DCOM服務器

以上我們主要講解了如何通過一個客戶應用使用COM。對于客戶來說,COM的編程技巧是相當簡單的??蛻舳说膽孟駽OM子系統請求一個特定的組件,服務器端將其傳送過來。

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

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

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

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

由于OLE的開發有不少的難度,因此微軟創建了一個稱為ATL(ActiveTemplateLibrary)新工具。對于COM編程來說,ATL是當前最實用的工具。實際上,如果你對其背后的東西沒有興趣,使用ATL向導來編寫COM服務器是相當簡單的。

這里介紹的例子都是通過ATL和ATL應用向導來創建的。這一節我們將講解如何建立一個基于ATL的服務器,并對向導產生的代碼給出了一個摘要。關于代碼

有一點你要花時間去習慣,編寫ATL服務器和傳統的編程是不一樣的。COM服務器其實是幾個獨立組件的協作構成的,包括有:

。你的應用

。COM子系統

。ATL模板類

?!癐DL”代碼和MIDL產生的“C”頭文件和程序

。系統寄存器(注冊表)

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

在以下的部分中,我們將把所有這些令服務器運作的部分放在一起。首先我們會使用ATLCOM應用向導來創建服務器。第二步我們將加入一個COM對象和一個方法。我們將寫一個進程內的服務器,因為它是最容易實現的COM服務器之一。一個進程內的服務器也不用建立一個proxy和stub對象。

建立一個基于DLL(進程內)的COM服務器

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

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

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

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

進程內的COM服務器有優點也有缺點。如果動態鏈接是你的系統設計中的重要一環,那么你將發現COM可提供一個極好的方式來管理DLL。一些有經驗的編程者會將所有他們的DLL都寫成為進程內的COM服務器。COM處理所有涉及載入、卸載的雜事,而輸出DLL函數和COM函數調用只有很少的系統開銷。

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

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

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

使用ATLAppWizard是一個可快速產生一個COM服務器的簡單方法。該向導可讓我們選擇所有基本的選項,并且將產生我們需要的大部分代碼。以下就是一個產生服務器的詳細步驟。在這個程序中,我們將稱該服務器為BeepServer。所有的COM服務器都至少要有一個接口,而我們的接口將會被稱為IBeepObj。你可以為你的COM接口取任意的名字,不過如果你想遵循標準的命名傳統,你最好使用“I”的前綴來命名它。

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

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

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

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

。項目的名字:BeepServer

。創建新的Workspace

。Location:你的工作目錄

圖一

3。在第一個的AppWizard對話框中我們將創建一個基于DLL(進程內)的服務器。輸入以下的設置:

。動態鏈接庫

。不允許合并proxy/stub代碼

。不支持MFC

圖二

4。按下完成

AppWizard創建一個基于DLL的COM服務器,并且帶有所有必要的文件。雖然該服務器將可編譯和運行,但它只是一個空殼。為了令它做我們想做的事情,我們將需要一個COM接口和支持該接口的類。我們也必須寫接口中的方法。

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

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

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

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

圖三

3.在對象向導的對話框中選擇“Objects”。選擇“SimpleObject”并且按Next。

圖四

4。選擇Names的標簽頁。輸入對象的名字:BeepObj。其余所有的選擇都會自動填入標準的名字

圖五

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

圖六

6。按下OK,這將創建Com對象

為服務器加入一個方法

我們現在已經創建了一個空的COM對象。不過,它還是一個無用的對象,因為它并不做任何的事情。我們將創建一個稱為Beep()的簡單方法,它可令系統發出一次beep聲。我們的COM方法將調用Win32API函數:Beep()。

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

圖七

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

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

圖八

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

{

[helpstring("methodBeep")]

HRESULTBeep([in]LONGduration);

};IDL的句法與C++類似。這一行和C++的函數原型相當。我們將在以后談論IDL的句法。

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

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

{

//TODO:Addyourimplementationcodehere

::Beep(550,duration);

returnS_OK;

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

我們已經擁有一個完整的COM服務器了。當項目結束編譯時,你應該會看到如下的信息: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)這意味著開發工具已經完成了以下的步驟:

。執行MIDL編譯器來產生代碼和類庫

。編譯源文件

。鏈接項目來創建BeepServer.DLL

。注冊COM組件

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

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

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

還有另外幾個proxy/stub文件,由MIDL產生。只是幾分鐘,我們就創建了一個完整的COM服務器應用。在沒有向導的日子里,寫一個服務器將需要數個小時。向導的缺點是我們有了一大塊沒有完全弄懂的代碼。在下面的部分我們將更為詳細地查看產生的模塊,然后是整個的應用。

總結

整個服務器的代碼幾乎都是完全由ATL向導產生的。我們使用的是一個基于DLL的服務器。不過這個過程對于所有的服務器類型幾乎都是一樣的。使用這個架構,我們可以很快的開發出一個服務器應用,因為你不需知道許多的細節就可以開始工作。

在以下的章節中,我們將查看進程內服務器和ATL的代碼。我們已經討論了DCOM的基本要點,了解了如何創建一個簡單的DCOM服務器和一個相關的客戶端。你也可以看到這個基本的過程是非常簡單的ATL向導處理了服務器端的大部分細節,要激活服務器,你只需要在客戶端寫10行左右的代碼就可以了。

接下來我們將討論兩個相關的主題。首先是創建你自己的COM客戶和服務器,結合第一部分我們所學到的,讓你了解要在自己的代碼中集成一個DCOM服務器,確實需要做哪些事情。然后我們將快速地看一下由ATL向導產生的代碼。

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

創建自己的COM客戶和服務器

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

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

服務器端

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

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

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

1。使用ATL向導來創建你的COM服務器的外殼。你選擇該服務器是一個DLL、一個EXE或者是一個服務。

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

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

4。為你的方法寫代碼

上面的這些步驟已經在第一部分中的“理解一個簡單的COM服務器”中詳細介紹過了。

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

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

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

客戶端

第一部分介紹的客戶端程序非常清楚和緊湊。不過,它包含很少的錯誤檢測代碼,因此要在一個真正的程序中應用是不足夠的。讓我們再次看一下這些代碼,它非常簡單,因此可讓你清楚地看到要創建一個客戶端的必要步驟。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的調用初始化COM,并且得到指向一個接口的指針。然后你就可以調用接口的方法。在完成方法調用后,你就可釋放接口并且調用CoUninitialize。整個步驟就完成了。

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

.客戶端不能啟動COM

.客戶端不能查找到請求的服務器

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

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

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

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

.客戶端不能正確地清除

為了跟蹤這些潛在的問題,你必須在每一步作檢查,具體是查看HRESULT的值。以上的代碼有作檢查,不過在出錯的時候并沒提示。以下的函數補救了這個不足://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;

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

//displayHRESULTonscreen

ShowStatus(hr);

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

圖一

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

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

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

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

首先,是傳統的C++源文件和頭文件。在開始時,所有這些代碼是由ATL向導產生的

Beep方法是通過使用“AddMethod”對話框加入的,它修改了MIDL接口的定義。MIDL的源代碼是一個IDL文件--在這個例子中它是BeepServer.IDL。MIDL編譯器將使用該文件來創建幾個輸出文件。這些文件負責了大部分實現服務器的工作。當我們為COM對象加入方法時,我們也將在IDL加入一些東西。

第三組的源文件是自動產生的MIDL輸出文件,是由MIDL編譯器產生的。這些文件是源代碼文件,不過由于它們是由MIDL編譯器通過IDL源代碼自動產生的,因此不能被向導或者開發者直接修改。你可以將它們稱為“第二產生文件”--向導創建了一個IDL文件,而MIDL編譯器由該IDL文件創建了源代碼文件。由MIDL創建的文件包括有:

BeepServer.RGS-服務器的注冊腳本

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

BeepServer_i.c-COM組件的GUID結構

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

ATL向導還創建了一個應用的“資源”。如果你查看項目的資源,你將會發現它處在“REGISTRY”下。該資源包含了BeepServer.RGS中定義的注冊腳本。資源的名字是IDR_BEEPOBJ。

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

主C++模塊

我們在運行ATLCOMAppwizard時,我們選擇創建一個基于DLL的服務器,并且選擇不使用MFC。向導的第一個選擇屏幕決定了服務器的整體配置。

AppWizard創建了一個標準的DLL模塊。該類的標準DLL并沒有一個WinMain應用循環,不過它有一個DllMain函數用作在載入時初始化該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函數的真正工作是檢查有沒有一個客戶連上該DLL,然后會做一些初始化的工作。一眼看去,并沒有一個明顯的指示這是一個COM應用。

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

CComModule_Module;

這個單一的對象包含了許多用作我們應用的COM服務器功能。它在程序執行開始時的創建和初始化設置了一連串的事件動作。

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

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

對象映射

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

OBJECT_ENTRY(CLSID_BeepObj,CBeepObj)

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

輸出文件

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

LIBRARY"BeepServer.DLL"

EXPORTS

DllCanUnloadNow@1PRIVATE

DllGetClassObject@

溫馨提示

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

評論

0/150

提交評論