JNI API完全手冊_第1頁
JNI API完全手冊_第2頁
JNI API完全手冊_第3頁
JNI API完全手冊_第4頁
JNI API完全手冊_第5頁
已閱讀5頁,還剩71頁未讀 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

1、1 - 簡介 本章介紹 Java 本地接口(Java Native Interface,JNI)。JNI 是本地編程接口。它使得在 Java 虛擬機 (VM) 內(nèi)部運行的 Java 代碼能夠與用其它編程語言(如 C、C+ 和匯編語言)編寫的應(yīng)用程序和庫進行互操作。JNI 最重要的好處是它沒有對底層 Java 虛擬機的實現(xiàn)施加任何限制。因此,Java 虛擬機廠商可以在不影響虛擬機其它部分的情況下添加對 JNI 的支持。程序員只需編寫一種版本的本地應(yīng)用程序或庫,就能夠與所有支持 JNI 的 Java 虛擬機協(xié)同工作。本章論及以下主題: Java 本地接口概述 背景 目標(biāo) Java 本地接口方法 利

2、用 JNI 編程 JDK 1.1.2 中的變化 Java 本地接口概述 盡管可以完全用 Java 編寫應(yīng)用程序,但是有時單獨用 Java 不能滿足應(yīng)用程序的需要。程序員使用 JNI 來編寫 Java 本地方法,可以處理那些不能完全用 Java 編寫應(yīng)用程序的情況。以下示例說明了何時需要使用 Java 本地方法: 標(biāo)準(zhǔn) Java 類庫不支持與平臺相關(guān)的應(yīng)用程序所需的功能。 已經(jīng)擁有了一個用另一種語言編寫的庫,而又希望通過 JNI 使 Java 代碼能夠訪問該庫。 想用低級語言(如匯編語言)實現(xiàn)一小段時限代碼。通過用 JNI 編程,可以將本地方法用于: 創(chuàng)建、檢查及更新 Java 對象(包括數(shù)組和

3、字符串)。 調(diào)用 Java 方法。 捕捉和拋出異常。 加載類和獲得類信息。 執(zhí)行運行時類型檢查。也可以與調(diào)用 API 一起使用 JNI,以允許任意本地應(yīng)用程序嵌入到 Java 虛擬機中。這樣使得程序員能夠輕易地讓已有應(yīng)用程序支持 Java,而不必與虛擬機源代碼相鏈接。背景 目前,不同廠商的虛擬機提供了不同的本地方法接口。這些不同的接口使程序員不得不在給定平臺上編寫、維護和分發(fā)多種版本的本地方法庫。下面簡要分析一下部分已有本地方法接口,例如: JDK 1.0 本地方法接口 Netscape 的 Java 運行時接口 Microsoft 的原始本地接口和 Java/COM 接口 JDK 1.0 本

4、地方法接口 JDK 1.0 附帶有本地方法接口。遺憾的是,有兩點原因使得該接口不適合于其它 Java 虛擬機。第一,平臺相關(guān)代碼將 Java 對象中的域作為 C 結(jié)構(gòu)的成員來進行訪問。但是,Java 語言規(guī)范沒有規(guī)定在內(nèi)存中對象是如何布局的。如果 Java 虛擬機在內(nèi)存中布局對象的方式有所不同,程序員就不得不重新編譯本地方法庫。第二,JDK 1.0 的本地方法接口依賴于保守的垃圾收集器。例如,無限制地使用 unhand 宏使得有必要以保守方式掃描本地堆棧。Java 運行時接口 Netscape 建議使用 Java 運行時接口 (JRI),它是 Java 虛擬機所提供服務(wù)的通用接口。JRI 的設(shè)

5、計融入了可移植性-它幾乎沒有對底層 Java 虛擬機的實現(xiàn)細(xì)節(jié)作任何假設(shè)。JRI 提出了各種各樣的問題,包括本地方法、調(diào)試、反射、嵌入(調(diào)用)等等。原始本地接口和 Java/COM 接口 Microsoft Java 虛擬機支持兩種本地方法接口。在低一級,它提供了高效的原始本地接口 (RNI)。RNI 提供了與 JDK 本地方法接口有高度源代碼級的向后兼容性,盡管它們之間還有一個主要區(qū)別,即平臺相關(guān)代碼必須用 RNI 函數(shù)來與垃圾收集器進行顯式的交互,而不是依賴于保守的垃圾收集。在高一級,Microsoft 的 Java/COM 接口為 Java 虛擬機提供了與語言無關(guān)的標(biāo)準(zhǔn)二進制接口。Jav

6、a 代碼可以象使用 Java 對象一樣來使用 COM 對象。Java 類也可以作為 COM 類顯示給系統(tǒng)的其余部分。目標(biāo) 我們認(rèn)為統(tǒng)一的,經(jīng)過細(xì)致考慮的標(biāo)準(zhǔn)接口能夠向每個用戶提供以下好處: 每個虛擬機廠商都可以支持更多的平臺相關(guān)代碼。 工具構(gòu)造器不必維護不同的本地方法接口。 應(yīng)用程序設(shè)計人員可以只編寫一種版本的平臺相關(guān)代碼就能夠在不同的虛擬機上運行。獲得標(biāo)準(zhǔn)本地方法接口的最佳途徑是聯(lián)合所有對 Java 虛擬機有興趣的當(dāng)事方。因此,我們在 Java 獲得許可方之間組織了一系列研討會,對設(shè)計統(tǒng)一的本地方法接口進行了討論。從研討會可以明確地看出標(biāo)準(zhǔn)本地方法接口必須滿足以下要求: 二進制兼容性 - 主

7、要的目標(biāo)是在給定平臺上的所有 Java 虛擬機實現(xiàn)之間實現(xiàn)本地方法庫的二進制兼容性。對于給定平臺,程序員只需要維護一種版本的本地方法庫。 效率 - 若要支持時限代碼,本地方法接口必須增加一點系統(tǒng)開銷。所有已知的用于確保虛擬機無關(guān)性(因而具有二進制兼容性)的技術(shù)都會占用一定的系統(tǒng)開銷。我們必須在效率與虛擬機無關(guān)性之間進行某種折衷。 功能 - 接口必須顯示足夠的 Java 虛擬機內(nèi)部情況以使本地方法能夠完成有用的任務(wù)。Java 本地接口方法 我們希望采用一種已有的方法作為標(biāo)準(zhǔn)接口,因為這樣程序員(程序員不得不學(xué)習(xí)在不同虛擬機中的多種接口)的工作負(fù)擔(dān)最輕。遺憾的是,已有解決方案中沒有任何方案能夠完全

8、地滿足我們的目標(biāo)。Netscape 的 JRI 最接近于我們所設(shè)想的可移植本地方法接口,因而我們采用它作為設(shè)計起點。熟悉 JRI 的讀者將會注意到在 API 命名規(guī)則、方法和域 ID 的使用、局部和全局引用的使用,等等中的相似點。雖然我們進行了最大的努力,但是 JNI 并不具有對 JRI 的二進制兼容性,不過虛擬機既可以支持 JRI,又可以支持 JNI。Microsoft 的 RNI 是對 JDK 1.0 的改進,因為它可以解決使用非保守的垃圾收集器的本地方法的問題。然而,RNI 不適合用作與虛擬機無關(guān)的本地方法接口。與 JDK 類似,RNI 本地方法將 Java 對象作為 C 結(jié)構(gòu)來訪問。這

9、將導(dǎo)致兩個問題: RNI 將內(nèi)部 Java 對象的布局暴露給了平臺相關(guān)代碼。 將 Java 對象作為 C 結(jié)構(gòu)直接進行訪問使得不可能有效地加入“寫屏障”,寫屏障是高級的垃圾收集算法所必需的。作為二進制標(biāo)準(zhǔn),COM 確保了不同虛擬機之間的完全二進制兼容性。調(diào)用 COM 方法只要求間接調(diào)用,而這幾乎不會占用系統(tǒng)開銷。另外,COM 對象對動態(tài)鏈接庫解決版本問題的方式也有很大的改進。然而,有幾個因素阻礙了將 COM 用作標(biāo)準(zhǔn) Java 本地方法接口: 第一,Java/COM 接口缺少某些必需功能,例如訪問私有域和拋出普通異常。 第二,Java/COM 接口自動為 Java 對象提供標(biāo)準(zhǔn)的 IUnkno

10、wn 和 IDispatch COM 接口,因而平臺相關(guān)代碼能夠訪問公有方法和域。遺憾的是,IDispatch 接口不能處理重載的 Java 方法,而且在匹配方法名稱時不區(qū)別大小寫。另外,通過 IDispatch 接口暴露的所有 Java 方法被打包在一起來執(zhí)行動態(tài)類型檢查和強制轉(zhuǎn)換。這是因為 IDispatch 接口的設(shè)計只考慮到了弱類型的語言(例如 Basic)。 第三,COM 允許軟件組件(包括完全成熟的應(yīng)用程序)一起工作,而不是處理單個低層函數(shù)。我們認(rèn)為將所有 Java 類或低層本地方法都當(dāng)作軟件組件是不恰當(dāng)?shù)摹?第四,在 UNIX 平臺上由于缺少對 COM 的支持,所以阻礙了直接采用

11、 COM。雖然我們沒有將 Java 對象作為 COM 對象暴露給平臺相關(guān)代碼,但是 JNI 接口自身與 COM 具有二進制兼容性。我們采用與 COM 一樣的跳轉(zhuǎn)表和調(diào)用約定。這意味著,一旦具有對 COM 的跨平臺支持,JNI 就能成為 Java 虛擬機的 COM 接口。我們認(rèn)為 JNI 不應(yīng)該是給定 Java 虛擬機所支持的唯一的本地方法接口。標(biāo)準(zhǔn)接口的好處在于程序員可以將自己的平臺相關(guān)代碼庫加載到不同的 Java 虛擬機上。在某些情況下,程序員可能不得不使用低層且與虛擬機有關(guān)的接口來獲得較高的效率。但在其它情況下,程序員可能使用高層接口來建立軟件組件。實際上,我們希望隨著 Java 環(huán)境和組

12、件軟件技術(shù)發(fā)展得越來越成熟,本地方法將變得越來越不重要。利用 JNI 編程 本地方法程序設(shè)計人員應(yīng)開始利用 JNI 進行編程。利用 JNI 編程隔離了一些未知條件,例如終端用戶可能正在運行的廠商的虛擬機。遵守 JNI 標(biāo)準(zhǔn)是本地庫能在給定 Java 虛擬機上運行的最好保證。例如,雖然 JDK 1.1 將繼續(xù)支持 JDK 1.0 中所實現(xiàn)的舊式的本地方法接口,但是可以肯定的是 JDK 的未來版本將停止支持舊式的本地方法接口。依賴于舊式接口的本地方法將不得不重新編寫。如果您正在實現(xiàn) Java 虛擬機,則應(yīng)該實現(xiàn) JNI。我們(Javasoft 和獲得許可方)盡力確保 JNI 不會占用虛擬機實現(xiàn)的系

13、統(tǒng)開銷或施加任何限制,包括對象表示,垃圾收集機制等。如果您遇到了我們可能忽視了的問題,請告知我們。JDK 1.1.2 中的變化 為了更好地支持 Java 運行時環(huán)境 (JRE),在 JDK 1.1.2 中對調(diào)用 API 在幾個方面作了擴展。這些變化沒有破壞任何已有代碼,JNI 本地方法接口也沒有改變。 JDK1_1InitArgs 結(jié)構(gòu)中的 reserved0 域已被重新命名為 version。JDK1_1InitArgs 結(jié)構(gòu)保存 JNI_CreateJavaVM 的初始化參數(shù)。JNI_GetDefaultJavaVMInitArgs 和 JNI_CreateJavaVM 的調(diào)用者必須將版本

14、域設(shè)置為 0x00010001。JNI_GetDefaultJavaVMInitArgs 被更改為返回 jint,用于表示是否支持所請求的版本。 JDK1_1InitArgs 結(jié)構(gòu)中的 reserved1 域已被重新命名為 properties。這是一個 NULL-終結(jié)的字符串?dāng)?shù)組。每個字符串具有以下格式: name=value 表示系統(tǒng)屬性(該功能對應(yīng)于 Java 命令行中的 -D 選項)。 在 JDK 1.1.1 中,調(diào)用 DestroyJavaVM 的線程必須是虛擬機中的唯一用戶線程。JDK 1.1.2 放松了這一限制。如果調(diào)用 DestroyJavaVM 時有多個用戶線程,則虛擬機將等

15、待直到當(dāng)前線程成為唯一的用戶線程,然后銷毀自己。2 - 設(shè)計概述 本章著重討論 JNI 中的主要設(shè)計問題,其中的大部分問題都與本地方法有關(guān)。調(diào)用 API 的設(shè)計將在 第 5 章 “調(diào)用 API” 中討論。JNI 接口函數(shù)和指針 平臺相關(guān)代碼是通過調(diào)用 JNI 函數(shù)來訪問 Java 虛擬機功能的。JNI 函數(shù)可通過接口指針來獲得。接口指針是指針的指針,它指向一個指針數(shù)組,而指針數(shù)組中的每個元素又指向一個接口函數(shù)。每個接口函數(shù)都處在數(shù)組的某個預(yù)定偏移量中。圖 2-1 說明了接口指針的組織結(jié)構(gòu)。圖 2-1 接口指針 JNI 接口的組織類似于 C+ 虛擬函數(shù)表或 COM 接口。使用接口表而不使用硬性編

16、入的函數(shù)表的好處是使 JNI 名字空間與平臺相關(guān)代碼分開。虛擬機可以很容易地提供多個版本的 JNI 函數(shù)表。例如,虛擬機可支持以下兩個 JNI 函數(shù)表: 一個表對非法參數(shù)進行全面檢查,適用于調(diào)試程序; 另一個表只進行 JNI 規(guī)范所要求的最小程度的檢查,因此效率較高。JNI 接口指針只在當(dāng)前線程中有效。因此,本地方法不能將接口指針從一個線程傳遞到另一個線程中。實現(xiàn) JNI 的虛擬機可將本地線程的數(shù)據(jù)分配和儲存在 JNI 接口指針?biāo)赶虻膮^(qū)域中。本地方法將 JNI 接口指針當(dāng)作參數(shù)來接受。虛擬機在從相同的 Java 線程中對本地方法進行多次調(diào)用時,保證傳遞給該本地方法的接口指針是相同的。但是,一

17、個本地方法可被不同的 Java 線程所調(diào)用,因此可以接受不同的 JNI 接口指針。加載和鏈接本地方法 對本地方法的加載通過 System.loadLibrary 方法實現(xiàn)。下例中,類初始化方法加載了一個與平臺有關(guān)的本地庫,在該本地庫中給出了本地方法 f 的定義: package pkg; class Cls native double f(int i, String s); static System.loadLibrary(pkg_Cls); System.loadLibrary 的參數(shù)是程序員任意選取的庫名。系統(tǒng)按照標(biāo)準(zhǔn)的但與平臺有關(guān)的處理方法將該庫名轉(zhuǎn)換為本地庫名。例如,Solaris

18、系統(tǒng)將名稱 pkg_Cls 轉(zhuǎn)換為 libpkg_Cls.so,而 Win32 系統(tǒng)將相同的名稱 pkg_Cls 轉(zhuǎn)換為 pkg_Cls.dll。程序員可用單個庫來存放任意數(shù)量的類所需的所有本地方法,只要這些類將被相同的類加載器所加載。虛擬機在其內(nèi)部為每個類加載器保護其所加載的本地庫清單。提供者應(yīng)該盡量選擇能夠避免名稱沖突的本地庫名。如果底層操作系統(tǒng)不支持動態(tài)鏈接,則必須事先將所有的本地方法鏈接到虛擬機上。這種情況下,虛擬機實際上不需要加載庫即可完成 System.loadLibrary 調(diào)用。程序員還可調(diào)用 JNI 函數(shù) RegisterNatives() 來注冊與類關(guān)聯(lián)的本地方法。在與靜態(tài)

19、鏈接的函數(shù)一起使用時,RegisterNatives() 函數(shù)將特別有用。解析本地方法名 動態(tài)鏈接程序是根據(jù)項的名稱來解析各項的。本地方法名由以下幾部分串接而成: 前綴 Java_ mangled 全限定的類名 下劃線(“_”)分隔符 mangled 方法名 對于重載的本地方法,加上兩個下劃線(“_”),后跟 mangled 參數(shù)簽名 虛擬機將為本地庫中的方法查找匹配的方法名。它首先查找短名(沒有參數(shù)簽名的名稱),然后再查找?guī)?shù)簽名的長名稱。只有當(dāng)某個本地方法被另一個本 地方法重載時程序員才有必要使用長名。但如果本地方法的名稱與非本地方法的名稱相同,則不會有問題。因為非本地方法(Java 方

20、法)并不放在本地庫中。下例中,不必用長名來鏈接本地方法 g,因為另一個方法 g 不是本地方法,因而它并不在本地庫中。 class Cls1 int g(int i); native int g(double d); 我們采取簡單的名字?jǐn)噥y方案,以保證所有的 Unicode 字符都能被轉(zhuǎn)換為有效的 C 函數(shù)名。我們用下劃線(“_”) 字符來代替全限定的類名中的斜杠(“/”)。由于名稱或類型描述符從來不會以數(shù)字打頭,我們用 _0、.、_9 來代替轉(zhuǎn)義字符序列,如表 2-1 所示: 表 2-1 Unicode 字符轉(zhuǎn)換 轉(zhuǎn)義字符序列 表示 _0XXXX Unicode 字符 XXXX。_1 字符“_

21、” _2 簽名中的字符“;” _3 簽名中的字符“” 本地方法和接口 API 都要遵守給定平臺上的庫調(diào)用標(biāo)準(zhǔn)約定。例如,UNIX 系統(tǒng)使用 C 調(diào)用約定,而 Win32 系統(tǒng)使用 _stdcall。本地方法的參數(shù) JNI 接口指針是本地方法的第一個參數(shù)。其類型是 JNIEnv。第二個參數(shù)隨本地方法是靜態(tài)還是非靜態(tài)而有所不同。非靜態(tài)本地方法的第二個參數(shù)是對對象的引用,而靜態(tài)本地方法的第二個參數(shù)是對其 Java 類的引用。其余的參數(shù)對應(yīng)于通常 Java 方法的參數(shù)。本地方法調(diào)用利用返回值將結(jié)果傳回調(diào)用程序中。第 3 章 “JNI 的類型和數(shù)據(jù)結(jié)構(gòu)” 將描述 Java 類型和 C 類型之間的映射。代

22、碼示例 2-1 說明了如何用 C 函數(shù)來實現(xiàn)本地方法 f。對本地方法 f 的聲明如下: package pkg; class Cls native double f(int i, String s); . 具有長 mangled 名稱 Java_pkg_Cls_f_ILjava_lang_String_2 的 C 函數(shù)實現(xiàn)本地方法f: 代碼示例 2-1: 用 C 實現(xiàn)本地方法 jdouble Java_pkg_Cls_f_ILjava_lang_String_2 ( JNIEnv *env, /* 接口指針 */ jobject obj, /* “this”指針 */ jint i, /* 第

23、一個參數(shù) */ jstring s) /* 第二個參數(shù) */ /* 取得 Java 字符串的 C 版本 */ const char *str = (*env)-GetStringUTFChars(env, s, 0); /* 處理該字符串 */ . /* 至此完成對 str 的處理 */ (*env)-ReleaseStringUTFChars(env, s, str); return . 注意,我們總是用接口指針 env 來操作 Java 對象。可用 C+ 將此代碼寫得稍微簡潔一些,如代碼示例 2-2 所示: 代碼示例 2-2: 用 C+ 實現(xiàn)本地方法 extern C /* 指定 C 調(diào)用

24、約定 */ jdouble Java_pkg_Cls_f_ILjava_lang_String_2 ( JNIEnv *env, /* 接口指針 */ jobject obj, /* “this”指針 */ jint i, /* 第一個參數(shù) */ jstring s) /* 第二個參數(shù) */ const char *str = env-GetStringUTFChars(s, 0); . env-ReleaseStringUTFChars(s, str); return . 使用 C+ 后,源代碼變得更為直接,且接口指針參數(shù)消失。但是,C+ 的內(nèi)在機制與 C 的完全一樣。在 C+ 中,JNI

25、函數(shù)被定義為內(nèi)聯(lián)成員函數(shù),它們將擴展為相應(yīng)的 C 對應(yīng)函數(shù)。引用 Java 對象 基本類型(如整型、字符型等)在 Java 和平臺相關(guān)代碼之間直接進行復(fù)制。而 Java 對象由引用來傳遞。虛擬機必須跟蹤傳到平臺相關(guān)代碼中的對象,以使這些對象不會被垃圾收集器釋放。反之,平臺相關(guān)代碼必須能用某種方式通知虛擬機它不再需要那些對象,同時,垃圾收集器必須能夠移走被平臺相關(guān)代碼引用過的對象。全局和局部引用 JNI 將平臺相關(guān)代碼使用的對象引用分成兩類:局部引用和全局引用。局部引用在本地方法調(diào)用期間有效,并在本地方法返回后被自動釋放掉。全局引用將一直有效,直到被顯式釋放。對象是被作為局部引用傳遞給本地方法的

26、,由 JNI 函數(shù)返回的所有 Java 對象也都是局部引用。JNI 允許程序員從局部引用創(chuàng)建全局引用。要求 Java 對象的 JNI 函數(shù)既可接受全局引用也可接受局部引用。本地方法將局部引用或全局引用作為結(jié)果返回。大多數(shù)情況下,程序員應(yīng)該依靠虛擬機在本地方法返回后釋放所有局部引用。但是,有時程序員必須顯式釋放某個局部引用。例如,考慮以下的情形: 本地方法要訪問一個大型 Java 對象,于是創(chuàng)建了對該 Java 對象的局部引用。然后,本地方法要在返回調(diào)用程序之前執(zhí)行其它計算。對這個大型 Java 對象的局部引用將防止該對象被當(dāng)作垃圾收集,即使在剩余的運算中并不再需要該對象。 本 地方法創(chuàng)建了大量

27、的局部引用,但這些局部引用并不是要同時使用。由于虛擬機需要一定的空間來跟蹤每個局部引用,創(chuàng)建太多的局部引用將可能使系統(tǒng)耗盡內(nèi)存。 例如,本地方法要在一個大型對象數(shù)組中循環(huán),把取回的元素作為局部引用,并在每次迭代時對一個元素進行操作。每次迭代后,程序員不再需要對該數(shù)組元素的局 部引用。JNI 允許程序員在本地方法內(nèi)的任何地方對局部引用進行手工刪除。為確保程序員可以手工釋放局部引用,JNI 函數(shù)將不能創(chuàng)建額外的局部引用,除非是這些 JNI 函數(shù)要作為結(jié)果返回的引用。局部引用僅在創(chuàng)建它們的線程中有效。本地方法不能將局部引用從一個線程傳遞到另一個線程中。實現(xiàn)局部引用 為了實現(xiàn)局部引用,Java 虛擬機

28、為每個從 Java 到本地方法的控制轉(zhuǎn)換都創(chuàng)建了注冊服務(wù)程序。注冊服務(wù)程序?qū)⒉豢梢苿拥木植恳糜成錇?Java 對象,并防止這些對象被當(dāng)作垃圾收集。所有傳給本地方法的 Java 對象(包括那些作為 JNI 函數(shù)調(diào)用結(jié)果返回的對象)將被自動添加到注冊服務(wù)程序中。本地方法返回后,注冊服務(wù)程序?qū)⒈粍h除,其中的所有項都可以被當(dāng)作垃圾來收集??捎酶鞣N不同的方法來實現(xiàn)注冊服務(wù)程序,例如,使用表、鏈接列表或 hash 表來實現(xiàn)。雖然引用計數(shù)可用來避免注冊服務(wù)程序中有重復(fù)的項,但 JNI 實現(xiàn)不是必須檢測和消除重復(fù)的項。注意,以保守方式掃描本地堆棧并不能如實地實現(xiàn)局部引用。平臺相關(guān)代碼可將局部引用儲存在全局或

29、堆數(shù)據(jù)結(jié)構(gòu)中。訪問 Java 對象 JNI 提供了一大批用來訪問全局引用和局部引用的函數(shù)。這意味著無論虛擬機在內(nèi)部如何表示 Java 對象,相同的本地方法實現(xiàn)都能工作。這就是為什么 JNI 可被各種各樣的虛擬機實現(xiàn)所支持的關(guān)鍵原因。通過不透明的引用來使用訪問函數(shù)的開銷比直接訪問 C 數(shù)據(jù)結(jié)構(gòu)的開銷來得高。我們相信,大多數(shù)情況下,Java 程序員使用本地方法是為了完成一些重要任務(wù),此時這種接口的開銷不是首要問題。訪問基本類型數(shù)組 對于含有大量基本數(shù)據(jù)類型(如整數(shù)數(shù)組和字符串)的 Java 對象來說,這種開銷將高得不可接受 (考慮一下用于執(zhí)行矢量和矩陣運算的本地方法的情形便知)。對 Java 數(shù)組

30、進行迭代并且要通過函數(shù)調(diào)用取回數(shù)組的每個元素,其效率是非常低的。一個解決辦法是引入“釘住”概念,以使本地方法能夠要求虛擬機釘住數(shù)組內(nèi)容。而后,該本地方法將接受指向數(shù)值元素的直接指針。但是,這種方法包含以下兩個前提: 垃圾收集器必須支持釘住。 虛擬機必須在內(nèi)存中連續(xù)存放基本類型數(shù)組。雖然大多數(shù)基本類型數(shù)組都是連續(xù)存放的,但布爾數(shù)組可以壓縮或不壓縮存儲。因此,依賴于布爾數(shù)組確切存儲方式的本地方法將是不可移植的。我們將采取折衷方法來克服上述兩個問題。首先,我們提供了一套函數(shù),用于在 Java 數(shù)組的一部分和本地內(nèi)存緩沖之間復(fù)制基本類型數(shù)組元素。這些函數(shù)只有在本地方法只需訪問大型數(shù)組中的一小部分元素時

31、才使用。其次,程序員可用另一套函數(shù)來取回數(shù)組元素的受約束版本。記住,這些函數(shù)可能要求 Java 虛擬機分配存儲空間和進行復(fù)制。虛擬機實現(xiàn)將決定這些函數(shù)是否真正復(fù)制該數(shù)組,如下所示: 如果垃圾收集器支持釘住,且數(shù)組的布局符合本地方法的要求,則不需要進行復(fù)制。 否則,該數(shù)組將被復(fù)制到不可移動的內(nèi)存塊中(例如,復(fù)制到 C 堆中),并進行必要的格式轉(zhuǎn)換,然后返回指向該副本的指針。最后,接口提供了一些函數(shù),用以通知虛擬機本地方法已不再需要訪問這些數(shù)組元素。當(dāng)調(diào)用這些函數(shù)時,系統(tǒng)或者釋放數(shù)組,或者在原始數(shù)組與其不可移動副本之間進行協(xié)調(diào)并將副本釋放。這種處理方法具有靈活性。垃圾收集器的算法可對每個給定的數(shù)組

32、分別作出復(fù)制或釘住的決定。例如,垃圾收集器可能復(fù)制小型對象而釘住大型對象。JNI 實現(xiàn)必須確保多個線程中運行的本地方法可同時訪問同一數(shù)組。例如,JNI 可以為每個被釘住的數(shù)組保留一個內(nèi)部計數(shù)器,以便某個線程不會解開同時被另一個線程釘住的數(shù)組。注意,JNI 不必將基本類型數(shù)組鎖住以專供某個本地方法訪問。同時從不同的線程對 Java 數(shù)組進行更新將導(dǎo)致不確定的結(jié)果。訪問域和方法 JNI 允許本地方法訪問 Java 對象的域或調(diào)用其方法。JNI 用符號名稱和類型簽名來識別方法和域。從名稱和簽名來定位域或?qū)ο蟮倪^程可分為兩步。例如,為調(diào)用類 cls 中的 f 方法,平臺相關(guān)代碼首先要獲得方法 ID,如

33、下所示: jmethodID mid = env-GetMethodID(cls, f, (ILjava/lang/String;)D);然后,平臺相關(guān)代碼可重復(fù)使用該方法 ID 而無須再查找該方法,如下所示: jdouble result = env-CallDoubleMethod(obj, mid, 10, str);域 ID 或方法 ID 并不能防止虛擬機卸載生成該 ID 的類。該類被卸載之后,該方法 ID 或域 ID 亦變成無效。因此,如果平臺相關(guān)代碼要長時間使用某個方法 ID 或域 ID,則它必須確保: 保留對所涉及類的活引用,或 重新計算該方法 ID 或域 ID。JNI 對域 I

34、D 和方法 ID 的內(nèi)部實現(xiàn)并不施加任何限制。報告編程錯誤 JNI 不檢查諸如傳遞 NULL 指針或非法參數(shù)類型之類的編程錯誤。非法的參數(shù)類型包括諸如要用 Java 類對象時卻用了普通 Java 對象這樣的錯誤。JNI 不檢查這些編程錯誤的理由如下: 強迫 JNI 函數(shù)去檢查所有可能的錯誤情況將降低正常(正確)的本地方法的性能。 在許多情況下,沒有足夠的運行時的類型信息可供這種檢查使用。大多數(shù) C 庫函數(shù)對編程錯誤不進行防范。例如,printf() 函數(shù)在接到一個無效地址時通常是引起運行錯而不是返回錯誤代碼。強迫 C 庫函數(shù)檢查所有可能的錯誤情況將有可能引起這種檢查被重復(fù)進行-先是在用戶代碼中

35、進行,然后又在庫函數(shù)中再次進行。程序員不得將非法指針或錯誤類型的參數(shù)傳遞給 JNI 函數(shù)。否則,可能產(chǎn)生意想不到的后果,包括可能使系統(tǒng)狀態(tài)受損或使虛擬機崩潰。Java 異常 JNI 允許本地方法拋出任何 Java 異常。本地方法也可以處理突出的 Java 異常。未被處理的 Java 異常將被傳回虛擬機中。異常和錯誤代碼 一些 JNI 函數(shù)使用 Java 異常機制來報告錯誤情況。大多數(shù)情況下,JNI 函數(shù)通過返回錯誤代碼并拋出 Java 異常來報告錯誤情況。錯誤代碼通常是特殊的返回值(如 NULL),這種特殊的返回值在正常返回值范圍之外。因此,程序員可以: 快速檢查上一個 JNI 調(diào)用所返回的值

36、以確定是否出錯,并 通過調(diào)用函數(shù) ExceptionOccurred() 來獲得異常對象,它含有對錯誤情況的更詳細(xì)說明。在以下兩種情況中,程序員需要先查出異常,然后才能檢查錯誤代碼: 調(diào)用 Java 方法的 JNI 函數(shù)返回該 Java 方法的結(jié)果。程序員必須調(diào)用 ExceptionOccurred() 以檢查在執(zhí)行 Java 方法期間可能發(fā)生的異常。 某些用于訪問 JNI 數(shù)組的函數(shù)并不返回錯誤代碼,但可能會拋出 ArrayIndexOutOfBoundsException 或 ArrayStoreException。在所有其它情況下,返回值如果不是錯誤代碼值就可確保沒有拋出異常。異步異常

37、在多個線程的情況下,當(dāng)前線程以外的其它線程可能會拋出異步異常。異步異常并不立即影響當(dāng)前線程中平臺相關(guān)代碼的執(zhí)行,直到出現(xiàn)下列情況: 該平臺相關(guān)代碼調(diào)用某個有可能拋出同步異常的 JNI 函數(shù),或者 該平臺相關(guān)代碼用 ExceptionOccurred() 顯式檢查同步異?;虍惒疆惓!W⒁?,只有那些有可能拋出同步異常的 JNI 函數(shù)才檢查異步異常。本地方法應(yīng)在必要的地方(例如,在一個沒有其它異常檢查的緊密循環(huán)中)插入 ExceptionOccurred() 檢查以確保當(dāng)前線程可在適當(dāng)時間內(nèi)對異步異常作出響應(yīng)。異常的處理 可用兩種方法來處理平臺相關(guān)代碼中的異常: 本地方法可選擇立即返回,使異常在啟動

38、該本地方法調(diào)用的 Java 代碼中拋出。 平臺相關(guān)代碼可通過調(diào)用 ExceptionClear() 來清除異常,然后執(zhí)行自己的異常處理代碼。拋出了某個異常之后,平臺相關(guān)代碼必須先清除異常,然后才能進行其它的 JNI 調(diào)用。當(dāng)有待定異常時,只有以下這些 JNI 函數(shù)可被安全地調(diào)用:ExceptionOccurred()、ExceptionDescribe() 和 ExceptionClear()。ExceptionDescribe() 函數(shù)將打印有關(guān)待定異常的調(diào)試消息。3 - JNI 的類型和數(shù)據(jù)結(jié)構(gòu) 本章討論 JNI 如何將 Java 類型映射到本地 C 類型。基本類型 表 3-1 描述 Ja

39、va 基本類型及其與計算機相關(guān)的本地等效類型。表 3-1 基本類型和本地等效類型 Java 類型 本地類型 說明 boolean jboolean 無符號,8 位 byte jbyte 無符號,8 位 char jchar 無符號,16 位 short jshort 有符號,16 位 int jint 有符號,32 位 long jlong 有符號,64 位 float jfloat 32 位 double jdouble 64 位 void void N/A 為了使用方便,特提供以下定義。 #define JNI_FALSE 0 #define JNI_TRUE 1jsize 整數(shù)類型用于描

40、述主要指數(shù)和大小: typedef jint jsize;引用類型 JNI 包含了很多對應(yīng)于不同 Java 對象的引用類型。JNI 引用類型的組織層次如圖 3-1 所示。 圖 3-1 引用類型層次 在 C 中,所有其它 JNI 引用類型都被定義為與 jobject 一樣。例如: typedef jobject jclass;在 C+ 中,JNI 引入了虛構(gòu)類以加強子類關(guān)系。例如: class _jobject ; class _jclass : public _jobject ; . typedef _jobject *jobject; typedef _jclass *jclass;域 ID

41、 和方法 ID 方法 ID 和域 ID 是常規(guī)的 C 指針類型: struct _jfieldID; /*不透明結(jié)構(gòu) */ typedef struct _jfieldID *jfieldID; /* 域 ID */ struct _jmethodID; /* 不透明結(jié)構(gòu) */ typedef struct _jmethodID *jmethodID; /* 方法 ID */值類型 jvalue 聯(lián)合類型在參數(shù)數(shù)組中用作單元類型。其聲明方式如下: typedef union jvalue jboolean z; jbyte b; jchar c; jshort s; jint i; jlong

42、 j; jfloat f; jdouble d; jobject l; jvalue;類型簽名 JNI 使用 Java 虛擬機的類型簽名表述。表 3-2 列出了這些類型簽名。表 3-2 Java 虛擬機類型簽名 類型簽名 Java 類型 Z boolean B byte C char S short I int J long F float D double L fully-qualified-class ; 全限定的類 type type ( arg-types ) ret-type 方法類型 例如,Java 方法: long f (int n, String s, int arr);具有以

43、下類型簽名: (ILjava/lang/String;I)JUTF-8 字符串 JNI 用 UTF-8 字符串來表示各種字符串類型。UTF-8 字符串和 Java 虛擬機所使用的一樣。UTF-8 字符串的編碼方式使得僅包含非空 ASCII 字符的字符序列能夠按每字符一個字節(jié)表示,但是最多只能表示 16 位的字符。所有在 u0001 到 u007F 范圍內(nèi)的字符都用單字節(jié)表示,如下所示: 字節(jié)中的七位數(shù)據(jù)確定了所表示字符的值??兆址?(u000) 和 u0080 到 u07FF 范圍內(nèi)的字符用一對字節(jié)表示, 即 x 和 y,如下所示: 值為 (x&0x1f)6)+(y&0x3f) 的字符需用兩個

44、字節(jié)表示。u0800 到 uFFFF 范圍內(nèi)的字符用三個字節(jié)表示,即 x,y,和 z: 值為 (x&0xf)12)+(y&0x3f)6)+(z&0x3f) 的字符需用三個字節(jié)表示。此格式與“標(biāo)準(zhǔn)” UTF-8 格式之間有兩個區(qū)別。第一,空字節(jié) (byte)0 使用雙字節(jié)格式進行編碼,而不是單字節(jié)格式。這意味著 Java 虛擬機的 UTF-8 字符串不可能有嵌入的空值。第二,只使用單字節(jié)、雙字節(jié)和三字節(jié)格式。Java 虛擬機不能識別更長的 UTF-8 格式。4 - JNI 函數(shù) 本章為 JNI 函數(shù)提供參考信息。其中列出了全部 JNI 函數(shù),同時也給出了 JNI 函數(shù)表的準(zhǔn)確布局。 注意:“必須

45、”一詞用于約束 JNI 編程人員。例如,當(dāng)說明某個 JNI 函數(shù)必須接收非空對象時,就應(yīng)確保不要向該 JNI 函數(shù)傳遞 NULL。這時,JNI 實現(xiàn)將無需在該 JNI 函數(shù)中執(zhí)行 NULL 指針檢查。 本章的部分資料改編自 Netscape 的 JRI 文檔。該參考資料按用法對函數(shù)進行組織。參考部分按下列函數(shù)區(qū)域進行組織: 版本信息 類操作 異常 全局及局部引用 對象操作 訪問對象的域 調(diào)用實例方法 訪問靜態(tài)域 調(diào)用靜態(tài)方法 字符串操作 數(shù)組操作 注冊本地方法 監(jiān)視程序操作 Java 虛擬機接口 接口函數(shù)表 每個函數(shù)均可通過 JNIEnv 參數(shù)以固定偏移量進行訪問。JNIEnv 的類型是一個指

46、針,指向存儲全部 JNI 函數(shù)指針的結(jié)構(gòu)。其定義如下:注意:前三項留作將來與 COM 兼容。此外,我們在函數(shù)表開頭部分也留出來多個 NULL 項,從而可將將來與類有關(guān)的 JNI 操作添加到 FindClass 后面,而非函數(shù)表的末尾。注意,函數(shù)表可在所有 JNI 接口指針間共享。代碼示例 4-1 const struct JNINativeInterface . = NULL, NULL, NULL, NULL, GetVersion, DefineClass, FindClass, NULL, NULL, NULL, GetSuperclass, IsAssignableFrom, NULL

47、, Throw, ThrowNew, ExceptionOccurred, ExceptionDescribe, ExceptionClear, FatalError, NULL, NULL, NewGlobalRef, DeleteGlobalRef, DeleteLocalRef, IsSameObject, NULL, NULL, AllocObject, NewObject, NewObjectV, NewObjectA, GetObjectClass, IsInstanceOf, GetMethodID, CallObjectMethod, CallObjectMethodV, Ca

48、llObjectMethodA, CallBooleanMethod, CallBooleanMethodV, CallBooleanMethodA, CallByteMethod, CallByteMethodV, CallByteMethodA, CallCharMethod, CallCharMethodV, CallCharMethodA, CallShortMethod, CallShortMethodV, CallShortMethodA, CallIntMethod, CallIntMethodV, CallIntMethodA, CallLongMethod, CallLong

49、MethodV, CallLongMethodA, CallFloatMethod, CallFloatMethodV, CallFloatMethodA, CallDoubleMethod, CallDoubleMethodV, CallDoubleMethodA, CallVoidMethod, CallVoidMethodV, CallVoidMethodA, CallNonvirtualObjectMethod, CallNonvirtualObjectMethodV, CallNonvirtualObjectMethodA, CallNonvirtualBooleanMethod,

50、CallNonvirtualBooleanMethodV, CallNonvirtualBooleanMethodA, CallNonvirtualByteMethod, CallNonvirtualByteMethodV, CallNonvirtualByteMethodA, CallNonvirtualCharMethod, CallNonvirtualCharMethodV, CallNonvirtualCharMethodA, CallNonvirtualShortMethod, CallNonvirtualShortMethodV, CallNonvirtualShortMethodA, CallNonvirtualIntMethod, CallNonvirtualIntMethodV, CallNonvirtualIntMethodA, CallNonvirtualLongMethod, CallNonvirtualLongMethodV, CallNonvirtualLongMet

溫馨提示

  • 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)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論