?? appa.htm
字號:
■COM有一個引用對象模型;程序員永遠不可能“擁有”一個對象,只能獲得對對象一個或多個接口的引用。Java也有一個引用對象模型——對一個對象的引用可“造型”成對它的某個接口的引用。<br>
■COM對象在內存里的“生存時間”取決于使用對象的客戶數量;若這個數量變成零,對象就會將自己從內存中刪去。在Java中,一個對象的生存時間也由客戶的數量決定。若不再有對那個對象的引用,對象就會等候垃圾收集器的處理。<br>
<br>
Java與COM之間這種緊密的對應關系不僅使Java程序員可以方便地訪問COM特性,也使Java成為編寫COM代碼的一種有效語言。COM是與語言無關的,但COM開發事實上采用的語言是C++和Visual
Basic。同Java相比,C++在進行COM開發時顯得更加強大,并可生成更有效的代碼,只是它很難使用。Visual
Basic比Java簡單得多,但它距離基礎操作系統太遠了,而且它的對象模型并未實現與COM很好的對應(映射)關系。Java是兩者之間一種很好的折衷方案。<br>
接下來,讓我們對COM開發的一些關鍵問題進行討論。編寫Java/COM客戶和服務器時,這些問題是首先需要弄清楚的。<br>
<br>
A.5.1 COM基礎<br>
COM是一種二進制規范,致力于實施可相互操作的對象。例如,COM認為一個對象的二進制布局必須能夠調用另一個COM對象里的服務。由于是對二進制布局的一種描述,所以只要某種語言能生成這樣的一種布局,就可通過它實現COM對象。通常,程序員不必關注象這樣的一些低級細節,因為編譯器可自動生成正確的布局。例如,假設您的程序是用C++寫的,那么大多數編譯器都能生成符合COM規范的一張虛擬函數表格。對那些不生成可執行代碼的語言,比如VB和Java,在運行期則會自動掛接到COM。<br>
COM庫也提供了幾個基本的函數,比如用于創建對象或查找系統中一個已注冊COM類的函數。<br>
一個組件對象模型的基本目標包括:<br>
■讓對象調用其他對象里的服務<br>
■允許新類型對象(或更新對象)無縫插入環境<br>
第一點正是面向對象程序設計要解決的問題:我們有一個客戶對象,它能向一個服務器對象發出請求。在這種情況下,“客戶”和“服務器”這兩個術語是在常規意義上使用的,并非指一些特定的硬件配置。對于任何面向對象的語言,第一個目標都是很容易達到的——只要您的代碼是一個完整的代碼塊,同時實現了服務器對象代碼以及客戶對象代碼。若改變了客戶和服務器對象相互間的溝通形式,只需簡單地重新編譯和鏈接一遍即可。重新啟動應用程序時,它就會自動采用組件的最新版本。<br>
但假若應用程序由一些未在自己控制之下的組件對象構成,情況就會變得迥然有異——我們不能控制它們的源碼,而且它們的更新可能完全獨立于我們的應用程序進行。例如,當我們在自己的程序里使用由其他廠商開發的ActiveX控件時,就會面臨這一情況。控件會安裝到我們的系統里,我們的程序能夠(在運行期)定位服務器代碼,激活對象,同它建立鏈接,然后使用它。以后,我們可安裝控件的新版本,我們的應用程序應該仍然能夠運行;即使在最糟的情況下,它也應禮貌地報告一條出錯消息,比如“控件未找到”等等;一般不會莫名其妙地掛起或死機。<br>
在這些情況下,我們的組件是在獨立的可執行代碼文件里實現的:DLL或EXE。若服務器對象在一個獨立的可執行代碼文件里實現,就需要由操作系統提供的一個標準方法,從而激活這些對象。當然,我們并不想在自己的代碼里使用DLL或EXE的物理名稱及位置,因為這些參數可能經常發生變化。此時,我們想使用的是由操作系統維護的一些標識符。另外,我們的應用程序需要對服務器展示出來的服務進行的一個描述。下面這兩個小節將分別討論這兩個問題。<br>
<br>
1. GUID和注冊表<br>
COM采用結構化的整數值(長度為128位)唯一性地標識系統中注冊的COM項目。這些數字的正式名稱叫作GUID(Globally
Unique IDentifier,全局唯一標識符),可由特殊的工具生成。此外,這些數字可以保證在“任何空間和時間”里獨一無二,沒有重復。在空間,是由于數字生成器會讀取網卡的ID號碼;在時間,是由于同時會用到系統的日期和時間??捎肎UID標識COM類(此時叫作CLSID)或者COM接口(IID)。盡管名字不同,但基本概念與二進制結構都是相同的。GUID亦可在其他環境中使用,這里不再贅述。<br>
GUID以及相關的信息都保存在Windows注冊表中,或者說保存在“注冊數據庫”(Registration
Database)中。這是一種分級式的數據庫,內建于操作系統中,容納了與系統軟硬件配置有關的大量信息。對于COM,注冊表會跟蹤系統內安裝的組件,比如它們的CLSID、實現它們的可執行文件的名字及位置以及其他大量細節。其中一個比較重要的細節是組件的ProgID;ProgID在概念上類似于GUID,因為它們都標識著一個COM組件。區別在于GUID是一個二進制的、通過算法生成的值。而ProgID則是由程序員定義的字串值。ProgID是隨同一個CLSID分配的。<br>
我們說一個COM組件已在系統內注冊,最起碼的一個條件就是它的CLSID和它的執行文件已存在于注冊表中(ProgID通常也已就位)。在后面的例子里,我們主要任務就是注冊與使用COM組件。<br>
注冊表的一項重要特點就是它作為客戶和服務器對象之間的一個去耦層使用。利用注冊表內保存的一些信息,客戶會激活服務器;其中一項信息是服務器執行模塊的物理位置。若這個位置發生了變動,注冊表內的信息就會相應地更新。但這個更新過程對于客戶來說是“透明”或者看不見的。后者只需直接使用ProgID或CLSID即可。換句話說,注冊表使服務器代碼的位置透明成為了可能。隨著DCOM(分布式COM)的引入,在本地機器上運行的一個服務器甚至可移到網絡中的一臺遠程機器,整個過程甚至不會引起客戶對它的絲毫注意(大多數情況下如此)。<br>
<br>
2. 類型庫<br>
由于COM具有動態鏈接的能力,同時由于客戶和服務器代碼可以分開獨立發展,所以客戶隨時都要動態偵測由服務器展示出來的服務。這些服務是用“類型庫”(Type
Library)中一種二進制的、與語言無關的形式描述的(就象接口和方法簽名)。它既可以是一個獨立的文件(通常采用.TLB擴展名),也可以是鏈接到執行程序內部的一種Win32資源。運行期間,客戶會利用類型庫的信息調用服務器中的函數。<br>
我們可以寫一個Microsoft Interface Definition Language(微軟接口定義語言,MIDL)源文件,用MIDL編譯器編譯它,從而生成一個.TLB文件。MIDL語言的作用是對COM類、接口以及方法進行描述。它在名稱、語法以及用途上都類似OMB/CORBA
IDL。然而,Java程序員不必使用MIDL。后面還會講到另一種不同的Microsoft工具,它能讀入Java類文件,并能生成一個類型庫。<br>
<br>
3. COM:HRESULT中的函數返回代碼<br>
由服務器展示出來的COM函數會返回一個值,采用預先定義好的HRESULT類型。HRESULT代表一個包含了三個字段的整數。這樣便可使用多個失敗和成功代碼,同時還可以使用其他信息。由于COM函數返回的是一個HRESULT,所以不能用返回值從函數調用里取回原始數據。若必須返回數據,可傳遞指向一個內存區域的指針,函數將在那個區域里填充數據。我們把這稱為“外部參數”。作為Java/COM程序員,我們不必過于關注這個問題,因為虛擬機會幫助我們自動照管一切。這個問題將在后續的小節里講述。<br>
<br>
A.5.2 MS Java/COM集成<br>
同C++/COM程序員相比,Microsoft Java編譯器、虛擬機以及各式各樣的工具極大簡化了Java/COM程序員的工作。編譯器有特殊的引導命令和包,可將Java類當作COM類對待。但在大多數情況下,我們只需依賴Microsoft
JVM為COM提供的支持,同時利用兩個有力的外部工具。<br>
Microsoft Java Virtual Machine(JVM)在COM和Java對象之間扮演了一座橋梁的角色。若將Java對象創建成一個COM服務器,那么我們的對象仍然會在JVM內部運行。Microsoft
JVM是作為一個DLL實現的,它向操作系統展示出了COM接口。在內部,JVM將對這些COM接口的函數調用映射成Java對象中的方法調用。當然,JVM必須知道哪個Java類文件對應于服務器執行模塊;之所以能夠找出這方面的信息,是由于我們事前已用Javareg在Windows注冊表內注冊了類文件。Javareg是與Microsoft
Java SDK配套提供的一個工具程序,能讀入一個Java類文件,生成相應的類型庫以及一個GUID,并可將類注冊到系統內。亦可用Javareg注冊遠程服務器。例如,可用它注冊在不同機器上運行的一個服務器。<br>
如果想寫一個Java/COM客戶,必須經歷一系列不同的步驟。Java/COM“客戶”是一些特殊的Java代碼,它們想激活和使用系統內注冊的一個COM服務器。同樣地,虛擬機會與COM服務器溝通,并將它提供的服務作為Java類內的各種方法展示(揭示)出來。另一個Microsoft工具是jactivex,它能讀取一個類型庫,并生成相應的Java源文件,在其中包含特殊的編譯器引導命令。生成的源文件屬于我們在指定類型庫之后命名的一個包的一部分。下一步是在自己的COM客戶Java源文件中導入那個包。<br>
接下來讓我們討論兩個例子。<br>
<br>
A.5.3 用Java設計COM服務器<br>
本節將介紹ActiveX控件、Automation服務器或者其他任何符合COM規范的服務器的開發過程。下面這個例子實現了一個簡單的Automation服務器,它能執行整數加法。我們用setAddend()方法設置addend的值。每次調用sum()方法的時候,addend就會添加到當前result里。我們用getResult()獲得result值,并用clear()重新設置值。用于實現這一行為的Java類是非常簡單的:<br>
<br>
1023頁上程序<br>
<br>
為了將這個Java類作為一個COM對象使用,我們將Javareg工具應用于編譯好的Adder.class文件。這個工具提供了一系列選項;在這種情況下,我們指定Java類文件名("Adder"),想為這個服務器在注冊表里置入的ProgID("JavaAdder.Adder.1"),以及想為即將生成的類型庫指定的名字("JavaAdder.tlb")。由于尚未給出CLSID,所以Javareg會自動生成一個。若我們再次對同樣的服務器調用Javareg,就會直接使用現成的CLSID。<br>
<br>
javareg /register<br>
/class:Adder /progid:JavaAdder.Adder.1<br>
/typelib:JavaAdder.tlb<br>
<br>
Javareg也會將新服務器注冊到Windows注冊表。此時,我們必須記住將Adder.class復制到Windows\Java\trustlib目錄。考慮到安全方面的原因(特別是涉及程序片調用COM服務的問題),只有在COM服務器已安裝到trustlib目錄的前提下,這些服務器才會被激活。<br>
現在,我們已在自己的系統中安裝了一個新的Automation服務器。為進行測試,我們需要一個Automation控制器,而Automation控制器就是Visual
Basic(VB)。在下面,大家會看到幾行VB代碼。按照VB的格式,我設置了一個文本框,用它從用戶那里接收要相加的值。并用一個標簽顯示結果,用兩個下推按鈕分別調用sum()和clear()方法。最開始,我們聲明了一個名為Adder的對象變量。在Form_Load子例程中(在窗體首次顯示時載入),會調用Adder自動服務器的一個新實例,并對窗體的文本字段進行初始化。一旦用戶按下“Sum”或者“Clear”按鈕,就會調用服務器中對應的方法。<br>
<br>
1024頁程序<br>
<br>
注意,這段代碼根本不知道服務器是用Java實現的。<br>
運行這個程序并調用了CreateObject()函數以后,就會在Windows注冊表里搜索指定的ProgID。在與ProgID有關的信息中,最重要的是Java類文件的名字。作為一個響應,會啟動Java虛擬機,而且在JVM內部調用Java對象的實例。從那個時候開始,JVM就會自動接管客戶和服務器代碼之間的交流。<br>
<br>
A.5.4 用Java設計COM客戶<br>
現在,讓我們轉到另一側,并用Java開發一個COM客戶。這個程序會調用系統已安裝的COM服務器內的服務。就目前這個例子來說,我們使用的是在前一個例子里為服務器實現的一個客戶。盡管代碼在Java程序員的眼中看起來比較熟悉,但在幕后發生的一切卻并不尋常。本例使用了用Java寫成的一個服務器,但它可應用于系統內安裝的任何ActiveX控件、ActiveX
Automation服務器或者ActiveX組件——只要我們有一個類型庫。<br>
首先,我們將Jactivex工具應用于服務器的類型庫。Jactivex有一系列選項和開關可供選擇。但它最基本的形式是讀取一個類型庫,并生成Java源文件。這個源文件保存于我們的windows/java/trustlib目錄中。通過下面這行代碼,它應用于為外部COM
Automation服務器生成的類型庫:<br>
<br>
jactivex /javatlb JavaAdder.tlb<br>
<br>
Jactivex完成以后,我們再來看看自己的windows/java/trustlib目錄。此時可在其中看到一個新的子目錄,名為javaadder。這個目錄包含了用于新包的源文件。這是在Java里與類型庫的功能差不多的一個庫。這些文件需要使用Microsoft編譯器的專用引導命令:@com。jactivex生成多個文件的原因是COM使用多個實體來描述一個COM服務器(另一個原因是我沒有對MIDL文件和Java/COM工具的使用進行細致的調整)。<br>
名為Adder.java的文件等價于MIDL文件中的一個coclass引導命令:它是對一個COM類的聲明。其他文件則是由服務器揭示出來的COM接口的Java等價物。這些接口(比如Adder_DispatchDefault.java)都屬于“遣送”(Dispatch)接口,屬于Automation控制器與Automation服務器之間的溝通機制的一部分。Java/COM集成特性也支持雙接口的實現與使用。但是,IDispatch和雙接口的問題已超出了本附錄的范圍。<br>
在下面,大家可看到對應的客戶代碼。第一行只是導入由jactivex生成的包。然后創建并使用COM
Automation服務器的一個實例,就象它是一個原始的Java類那樣。請注意行內的類型模型,其中“例示”了COM對象(即生成并調用它的一個實例)。這與COM對象模型是一致的。在COM中,程序員永遠不會得到對整個對象的一個引用。相反,他們只能擁有對類內實現的一個或多個接口的引用。<br>
“例示”Adder類的一個Java對象以后,就相當于指示COM激活服務器,并創建這個COM對象的一個實例。但我們隨后必須指定自己想使用哪個接口,在由服務器實現的接口中挑選一個。這正是類型模型完成的工作。這兒使用的是“默認遣送”接口,它是Automation控制器用于同一個Automation服務器通信的標準接口。欲了解這方面的細節,請參考由Ibid編著的《Inside
COM》。請注意激活服務器并選擇一個COM接口是多么容易!<br>
<br>
1026頁程序<br>
<br>
現在,我們可以編譯它,并開始運行程序。<br>
<br>
1. com.ms.com包<br>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -