?? appa.htm
字號:
作為訪問JNI函數的一個例子,請思考上述的代碼。在這里,我們利用JNIEnv的自變量jEnv來訪問一個Java字串。Java字串采取的是Unicode格式,所以假若收到這樣一個字串,并想把它傳給一個非Unicode函數(如printf()),首先必須用JNI函數GetStringUTFChars()將其轉換成ASCII字符。該函數能接收一個Java字串,然后把它轉換成UTF-8字符(用8位寬度容納ASCII值,或用16位寬度容納Unicode;若原始字串的內容完全由ASCII構成,那么結果字串也是ASCII)。<br>
GetStringUTFChars是JNIEnv間接指向的那個結構里的一個字段,而這個字段又是指向一個函數的指針。為訪問JNI函數,我們用傳統的C語法來調用一個函數(通過指針)。利用上述形式可實現對所有JNI函數的訪問。<br>
<br>
A.1.3 傳遞和使用Java對象<br>
在前例中,我們將一個字串傳遞給固有方法。事實上,亦可將自己創建的Java對象傳遞給固有方法。<br>
在我們的固有方法內部,可訪問已收到的那些對象的字段及方法。<br>
為傳遞對象,聲明固有方法時要采用原始的Java語法。如下例所示,MyJavaClass有一個public(公共)字段,以及一個public方法。UseObjects類聲明了一個固有方法,用于接收MyJavaClass類的一個對象。為調查固有方法是否能控制自己的自變量,我們設置了自變量的public字段,調用固有方法,然后打印出public字段的值。<br>
<br>
1004頁上程序<br>
<br>
編譯好代碼,并將.class文件傳遞給javah后,就可以實現固有方法。在下面這個例子中,一旦取得字段和方法ID,就會通過JNI函數訪問它們。<br>
<br>
1004-1005頁程序<br>
<br>
除第一個自變量外,C++函數會接收一個jobject,它代表Java對象引用“固有”的那一面——那個引用是我們從Java代碼里傳遞的。我們簡單地讀取aValue,把它打印出來,改變這個值,調用對象的divByTwo()方法,再將值重新打印一遍。<br>
為訪問一個字段或方法,首先必須獲取它的標識符。利用適當的JNI函數,可方便地取得類對象、元素名以及簽名信息。這些函數會返回一個標識符,利用它可訪問對應的元素。盡管這一方式顯得有些曲折,但我們的固有方法確實對Java對象的內部布局一無所知。因此,它必須通過由JVM返回的索引訪問字段和方法。這樣一來,不同的JVM就可實現不同的內部對象布局,同時不會對固有方法造成影響。<br>
若運行Java程序,就會發現從Java那一側傳來的對象是由我們的固有方法處理的。但傳遞的到底是什么呢?是指針,還是Java引用?而且垃圾收集器在固有方法調用期間又在做什么呢?<br>
垃圾收集器會在固有方法執行期間持續運行,但在一次固有方法調用期間,我們的對象可保證不會被當作“垃圾”收集去。為確保這一點,事先創建了“局部引用”,并在固有方法調用之后立即清除。由于它們的“生命期”與調用過程息息相關,所以能夠保證對象在固有方法調用期間的有效性。<br>
由于這些引用會在每次函數調用的時候創建和破壞,所以不可在static變量中制作固有方法的局部副本(本地拷貝)。若希望一個引用在函數存在期間持續有效,就需要一個全局引用。全局引用不是由JVM創建的,但通過調用特定的JNI函數,程序員可將局部引用擴展為全局引用。創建一個全局引用時,需對引用對象的“生存時間”負責。全局引用(以及它引用的對象)會一直留在內存里,直到用特定的JNI函數明確釋放了這個引用。它類似于C的malloc()和free()。<br>
<br>
A.1.4 JNI和Java異常<br>
利用JNI,可丟棄、捕捉、打印以及重新丟棄Java異常,就象在一個Java程序里那樣。但對程序員來說,需自行調用專用的JNI函數,以便對異常進行處理。下面列出用于異常處理的一些JNI函數:<br>
■Throw():丟棄一個現有的異常對象;在固有方法中用于重新丟棄一個異常。<br>
■ThrowNew():生成一個新的異常對象,并將其丟棄。<br>
■ExceptionOccurred():判斷一個異常是否已被丟棄,但尚未清除。<br>
■ExceptionDescribe():打印一個異常和堆棧跟蹤信息。<br>
■ExceptionClear():清除一個待決的異常。<br>
■FatalError():造成一個嚴重錯誤,不返回。<br>
在所有這些函數中,最不能忽視的就是ExceptionOccurred()和ExceptionClear()。大多數JNI函數都能產生異常,而且沒有象在Java的try塊內的那種語言特性可供利用。所以在每一次JNI函數調用之后,都必須調用ExceptionOccurred(),了解異常是否已被丟棄。若偵測到一個異常,可選擇對其加以控制(可能時還要重新丟棄它)。然而,必須確保異常最終被清除。這可以在自己的函數中用ExceptionClear()來實現;若異常被重新丟棄,也可能在其他某些函數中進行。但無論如何,這一工作是必不可少的。<br>
我們必須保證異常被徹底清除。否則,假若在一個異常待決的情況下調用一個JNI函數,獲得的結果往往是無法預知的。也有少數幾個JNI函數可在異常時安全調用;當然,它們都是專門的異常控制函數。<br>
<br>
A.1.5 JNI和線程處理<br>
由于Java是一種多線程語言,幾個線程可能同時發出對一個固有方法的調用(若另一個線程發出調用,固有方法可能在運行期間暫停)。此時,完全要由程序員來保證固有調用在多線程的環境中安全進行。例如,要防范用一種未進行監視的方法修改共享數據。此時,我們主要有兩個選擇:將固有方法聲明為“同步”,或在固有方法內部采取其他某些策略,確保數據處理正確地并發進行。<br>
此外,絕對不要通過線程傳遞JNIEnv,因為它指向的內部結構是在“每線程”的基礎上分配的,而且包含了只對那些特定的線程才有意義的信息。<br>
<br>
A.1.6 使用現成代碼<br>
為實現JNI固有方法,最簡單的方法就是在一個Java類里編寫固有方法的原型,編譯那個類,再通過javah運行.class文件。但假若我們已有一個大型的、早已存在的代碼庫,而且想從Java里調用它們,此時又該如何是好呢?不可將DLL中的所有函數更名,使其符合JNI命名規則,這種方案是不可行的。最好的方法是在原來的代碼庫“外面”寫一個封裝DLL。Java代碼會調用新DLL里的函數,后者再調用原始的DLL函數。這個方法并非僅僅是一種解決方案;大多數情況下,我們甚至必須這樣做,因為必須面向對象引用調用JNI函數,否則無法使用它們。<br>
<br>
A.2 微軟的解決方案<br>
到本書完稿時為止,微軟仍未提供對JNI的支持,只是用自己的專利方法提供了對非Java代碼調用的支持。這一支持內建到編譯器Microsoft
JVM以及外部工具中。只有程序用Microsoft Java編譯器編譯,而且只有在Microsoft
Java虛擬機(JVM)上運行的時候,本節講述的特性才會有效。若計劃在因特網上發行自己的應用,或者本單位的內聯網建立在不同平臺的基礎上,就可能成為一個嚴重的問題。<br>
微軟與Win32代碼的接口為我們提供了連接Win32的三種途徑:<br>
(1) J/Direct:方便調用Win32 DLL函數的一種途徑,具有某些限制。<br>
(2) 本原接口(RNI):可調用Win32 DLL函數,但必須自行解決“垃圾收集”問題。<br>
(3) Java/COM集成:可從Java里直接揭示或調用COM服務。<br>
后續的小節將分別探討這三種技術。<br>
寫作本書的時候,這些特性均通過了Microsoft SDK for Java 2.0 beta 2的支持。可從微軟公司的Web站點下載這個開發平臺(要經歷一個痛苦的選擇過程,他們叫作“Active
Setup”)。Java SDK是一套命令行工具的集合,但編譯引擎可輕易嵌入Developer
Studio環境,以便我們用Visual J++ 1.1來編譯Java 1.1代碼。<br>
<br>
A.3 J/Direct<br>
J/Direct是調用Win32 DLL函數最簡單的方式。它的主要設計目標是與Win32API打交道,但完全可用它調用其他任何API。但是,盡管這一特性非常方便,但它同時也造成了某些限制,且降低了性能(與RNI相比)。但J/Direct也有一些明顯的優點。首先,除希望調用的那個DLL里的代碼之外,沒有必要再編寫額外的非Java代碼,換言之,我們不需要一個封裝器或者代理/存根DLL。其次,函數自變量與標準數據類型之間實現了自動轉換。若必須傳遞用戶自定義的數據類型,那么J/Direct可能不按我們的希望工作。第三,就象下例展示的那樣,它非常簡單和直接。只需少數幾行,這個例子便能調用Win32
API函數MessageBox(),它能彈出一個小的模態窗口,并帶有一個標題、一條消息、一個可選的圖標以及幾個按鈕。<br>
<br>
1008頁程序<br>
<br>
令人震驚的是,這里便是我們利用J/Direct調用Win32 DLL函數所需的全部代碼。其中的關鍵是位于示范代碼底部的MessageBox()聲明之前的@dll.import引導命令。它表面上看是一條注釋,但實際并非如此。它的作用是告訴編譯器:引導命令下面的函數是在USER32
DLL里實現的,而且應相應地調用。我們要做的全部事情就是提供與DLL內實現的函數相符的一個原型,并調用函數。但是毋需在Java版本里手工鍵入需要的每一個Win32
API函數,一個Microsoft Java包會幫我們做這件事情(很快就會詳細解釋)。為了讓這個例子正常工作,函數必須“按名稱”由DLL導出。但是,也可以用@dll.import引導命令“按順序”鏈接。舉個例子來說,我們可指定函數在DLL里的入口位置。稍后還會具體講述@dll.import引導命令的特性。<br>
用非Java代碼進行鏈接的一個重要問題就是函數參數的自動配置。正如大家看到的那樣,MessageBox()的Java聲明采用了兩個字串自變量,但原來的C方案則采用了兩個char指針。編譯器會幫助我們自動轉換標準數據類型,同時遵照本章后一節要講述的規則。<br>
最好,大家或許已注意到了main()聲明中的UnsatisfiedLinkError異常。在運行期的時候,一旦鏈接程序不能從非Java函數里解析出符號,就會觸發這一異常(事件)。這可能是由多方面的原因造成的:.dll文件未找到;不是一個有效的DLL;或者J/Direct未獲您所使用的虛擬機的支持。為了使DLL能被找到,它必須位于Windows或Windows\System目錄下,位于由PATH環境變量列出的一個目錄中,或者位于和.class文件相同的目錄。J/Direct獲得了Microsoft
Java編譯器1.02.4213版本及更高版本的支持,也獲得了Microsoft JVM 4.79.2164及更高版本的支持。為了解自己編譯器的版本號,請在命令行下運行JVC,不要加任何參數。為了解JVM的版本號,請找到msjava.dll的圖標,并利用右鍵彈出菜單觀察它的屬性。<br>
<br>
A.3.1 @dll.import引導命令<br>
作為使用J/Direct唯一的途徑,@dll.import引導命令相當靈活。它提供了為數眾多的修改符,可用它們自定義同非Java代碼建立鏈接關系的方式。它亦可應用于類內的一些方法,或應用于整個類。也就是說,我們在那個類內聲明的所有方法都是在相同的DLL里實現的。下面讓我們具體研究一下這些特性。<br>
<br>
1. 別名處理和按順序鏈接<br>
為了使@dll.import引導命令能象上面顯示的那樣工作,DLL內的函數必須按名字導出。然而,我們有時想使用與DLL里原始名字不同的一個名字(別名處理),否則函數就可能按編號(比如按順序)導出,而不是按名字導出。下面這個例子聲明了FinestraDiMessaggio()(用意大利語說的“MessageBox”)。正如大家看到的那樣,使用的語法是非常簡單的。<br>
<br>
1010頁上程序<br>
<br>
下面這個例子展示了如何同DLL里并非按名字導出的一個函數建立鏈接,那個函數事實是按它們在DLL里的位置導出的。這個例子假設有一個名為MYMATH的DLL,這個DLL在位置編號3處包含了一個函數。那個函數獲取兩個整數作為自變量,并返回兩個整數的和。<br>
<br>
1010頁下程序<br>
<br>
可以看出,唯一的差異就是entrypoint自變量的形式。<br>
<br>
2. 將@dll.import應用于整個類<br>
@dll.import引導命令可應用于整個類。也就是說,那個類的所有方法都是在相同的DLL里實現的,并具有相同的鏈接屬性。引導命令不會由子類繼承;考慮到這個原因,而且由于DLL里的函數是自然的static函數,所以更佳的設計方案是將API函數封裝到一個獨立的類里,如下所示:<br>
<br>
1011頁程序<br>
<br>
由于MessageBeep()和MessageBox()函數已在不同的類里被聲明成static函數,所以必須在調用它們時規定作用域。大家也許認為必須用上述的方法將所有Win32
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -