




版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
1、1 - 簡介 本章介紹 Java 本地接口(Java Native Interface,JNI)。JNI 是本地編程接口。它使得在 Java 虛擬機 (VM) 內(nèi)部運行的 Java 代碼能夠與用其它編程語言(如 C、C+ 和匯編語言)編寫的應用程序和庫進行互操作。JNI 最重要的好處是它沒有對底層 Java 虛擬機的實現(xiàn)施加任何限制。因此,Java 虛擬機廠商可以在不影響虛擬機其它部分的情況下添加對 JNI 的支持。程序員只需編寫一種版本的本地應用程序或庫,就能夠與所有支持 JNI 的 Java 虛擬機協(xié)同工作。本章論及以下主題: · Java 本地接口概述 · 背景
2、183; 目標 · Java 本地接口方法 · 利用 JNI 編程 · JDK 1.1.2 中的變化 Java 本地接口概述 盡管可以完全用 Java 編寫應用程序,但是有時單獨用 Java 不能滿足應用程序的需要。程序員使用 JNI 來編寫 Java 本地方法,可以處理那些不能完全用 Java 編寫應用程序的情況。以下示例說明了何時需要使用 Java 本地方法: · 標準 Java 類庫不支持與平臺相關的應用程序所需的功能。 · 已經(jīng)擁有了一個用另一種語言編寫的庫,而又希望通過 JNI 使 Java 代碼能夠訪問該庫。 · 想用低級
3、語言(如匯編語言)實現(xiàn)一小段時限代碼。通過用 JNI 編程,可以將本地方法用于: · 創(chuàng)建、檢查及更新 Java 對象(包括數(shù)組和字符串)。 · 調(diào)用 Java 方法。 · 捕捉和拋出異常。 · 加載類和獲得類信息。 · 執(zhí)行運行時類型檢查。也可以與調(diào)用 API 一起使用 JNI,以允許任意本地應用程序嵌入到 Java 虛擬機中。這樣使得程序員能夠輕易地讓已有應用程序支持 Java,而不必與虛擬機源代碼相鏈接。背景 目前,不同廠商的虛擬機提供了不同的本地方法接口。這些不同的接口使程序員不得不在給定平臺上編寫、維護和分發(fā)多種版本的本地方法庫。下面
4、簡要分析一下部分已有本地方法接口,例如: · JDK 1.0 本地方法接口 · Netscape 的 Java 運行時接口 · Microsoft 的原始本地接口和 Java/COM 接口 JDK 1.0 本地方法接口 JDK 1.0 附帶有本地方法接口。遺憾的是,有兩點原因使得該接口不適合于其它 Java 虛擬機。第一,平臺相關代碼將 Java 對象中的域作為 C 結構的成員來進行訪問。但是,Java 語言規(guī)范沒有規(guī)定在內(nèi)存中對象是如何布局的。如果 Java 虛擬機在內(nèi)存中布局對象的方式有所不同,程序員就不得不重新編譯本地方法庫。第二,JDK 1.0 的本地方法
5、接口依賴于保守的垃圾收集器。例如,無限制地使用 unhand 宏使得有必要以保守方式掃描本地堆棧。Java 運行時接口 Netscape 建議使用 Java 運行時接口 (JRI),它是 Java 虛擬機所提供服務的通用接口。JRI 的設計融入了可移植性-它幾乎沒有對底層 Java 虛擬機的實現(xiàn)細節(jié)作任何假設。JRI 提出了各種各樣的問題,包括本地方法、調(diào)試、反射、嵌入(調(diào)用)等等。原始本地接口和 Java/COM 接口 Microsoft Java 虛擬機支持兩種本地方法接口。在低一級,它提供了高效的原始本地接口 (RNI)。RNI 提供了與 JDK 本地方法接口有高度源代碼級的向后兼容性,
6、盡管它們之間還有一個主要區(qū)別,即平臺相關代碼必須用 RNI 函數(shù)來與垃圾收集器進行顯式的交互,而不是依賴于保守的垃圾收集。在高一級,Microsoft 的 Java/COM 接口為 Java 虛擬機提供了與語言無關的標準二進制接口。Java 代碼可以象使用 Java 對象一樣來使用 COM 對象。Java 類也可以作為 COM 類顯示給系統(tǒng)的其余部分。目標 我們認為統(tǒng)一的,經(jīng)過細致考慮的標準接口能夠向每個用戶提供以下好處: · 每個虛擬機廠商都可以支持更多的平臺相關代碼。 · 工具構造器不必維護不同的本地方法接口。 · 應用程序設計人員可以只編寫一種版本的平臺相關
7、代碼就能夠在不同的虛擬機上運行。獲得標準本地方法接口的最佳途徑是聯(lián)合所有對 Java 虛擬機有興趣的當事方。因此,我們在 Java 獲得許可方之間組織了一系列研討會,對設計統(tǒng)一的本地方法接口進行了討論。從研討會可以明確地看出標準本地方法接口必須滿足以下要求: · 二進制兼容性 - 主要的目標是在給定平臺上的所有 Java 虛擬機實現(xiàn)之間實現(xiàn)本地方法庫的二進制兼容性。對于給定平臺,程序員只需要維護一種版本的本地方法庫。 · 效率 - 若要支持時限代碼,本地方法接口必須增加一點系統(tǒng)開銷。所有已知的用于確保虛擬機無關性(因而具有二進制兼容性)的技術都會占用一定的系統(tǒng)開銷。我們必須
8、在效率與虛擬機無關性之間進行某種折衷。 · 功能 - 接口必須顯示足夠的 Java 虛擬機內(nèi)部情況以使本地方法能夠完成有用的任務。Java 本地接口方法 我們希望采用一種已有的方法作為標準接口,因為這樣程序員(程序員不得不學習在不同虛擬機中的多種接口)的工作負擔最輕。遺憾的是,已有解決方案中沒有任何方案能夠完全地滿足我們的目標。Netscape 的 JRI 最接近于我們所設想的可移植本地方法接口,因而我們采用它作為設計起點。熟悉 JRI 的讀者將會注意到在 API 命名規(guī)則、方法和域 ID 的使用、局部和全局引用的使用,等等中的相似點。雖然我們進行了最大的努力,但是 JNI 并不具有
9、對 JRI 的二進制兼容性,不過虛擬機既可以支持 JRI,又可以支持 JNI。Microsoft 的 RNI 是對 JDK 1.0 的改進,因為它可以解決使用非保守的垃圾收集器的本地方法的問題。然而,RNI 不適合用作與虛擬機無關的本地方法接口。與 JDK 類似,RNI 本地方法將 Java 對象作為 C 結構來訪問。這將導致兩個問題: · RNI 將內(nèi)部 Java 對象的布局暴露給了平臺相關代碼。 · 將 Java 對象作為 C 結構直接進行訪問使得不可能有效地加入“寫屏障”,寫屏障是高級的垃圾收集算法所必需的。作為二進制標準,COM 確保了不同虛擬機之間的完全二進制兼容
10、性。調(diào)用 COM 方法只要求間接調(diào)用,而這幾乎不會占用系統(tǒng)開銷。另外,COM 對象對動態(tài)鏈接庫解決版本問題的方式也有很大的改進。然而,有幾個因素阻礙了將 COM 用作標準 Java 本地方法接口: · 第一,Java/COM 接口缺少某些必需功能,例如訪問私有域和拋出普通異常。 · 第二,Java/COM 接口自動為 Java 對象提供標準的 IUnknown 和 IDispatch COM 接口,因而平臺相關代碼能夠訪問公有方法和域。遺憾的是,IDispatch 接口不能處理重載的 Java 方法,而且在匹配方法名稱時不區(qū)別大小寫。另外,通過 IDispatch 接口暴露
11、的所有 Java 方法被打包在一起來執(zhí)行動態(tài)類型檢查和強制轉(zhuǎn)換。這是因為 IDispatch 接口的設計只考慮到了弱類型的語言(例如 Basic)。 · 第三,COM 允許軟件組件(包括完全成熟的應用程序)一起工作,而不是處理單個低層函數(shù)。我們認為將所有 Java 類或低層本地方法都當作軟件組件是不恰當?shù)摹?· 第四,在 UNIX 平臺上由于缺少對 COM 的支持,所以阻礙了直接采用 COM。雖然我們沒有將 Java 對象作為 COM 對象暴露給平臺相關代碼,但是 JNI 接口自身與 COM 具有二進制兼容性。我們采用與 COM 一樣的跳轉(zhuǎn)表和調(diào)用約定。這意味著,一旦具有對
12、 COM 的跨平臺支持,JNI 就能成為 Java 虛擬機的 COM 接口。我們認為 JNI 不應該是給定 Java 虛擬機所支持的唯一的本地方法接口。標準接口的好處在于程序員可以將自己的平臺相關代碼庫加載到不同的 Java 虛擬機上。在某些情況下,程序員可能不得不使用低層且與虛擬機有關的接口來獲得較高的效率。但在其它情況下,程序員可能使用高層接口來建立軟件組件。實際上,我們希望隨著 Java 環(huán)境和組件軟件技術發(fā)展得越來越成熟,本地方法將變得越來越不重要。利用 JNI 編程 本地方法程序設計人員應開始利用 JNI 進行編程。利用 JNI 編程隔離了一些未知條件,例如終端用戶可能正在運行的廠商
13、的虛擬機。遵守 JNI 標準是本地庫能在給定 Java 虛擬機上運行的最好保證。例如,雖然 JDK 1.1 將繼續(xù)支持 JDK 1.0 中所實現(xiàn)的舊式的本地方法接口,但是可以肯定的是 JDK 的未來版本將停止支持舊式的本地方法接口。依賴于舊式接口的本地方法將不得不重新編寫。如果您正在實現(xiàn) Java 虛擬機,則應該實現(xiàn) JNI。我們(Javasoft 和獲得許可方)盡力確保 JNI 不會占用虛擬機實現(xiàn)的系統(tǒng)開銷或施加任何限制,包括對象表示,垃圾收集機制等。如果您遇到了我們可能忽視了的問題,請告知我們。JDK 1.1.2 中的變化 為了更好地支持 Java 運行時環(huán)境 (JRE),在 JDK 1.
14、1.2 中對調(diào)用 API 在幾個方面作了擴展。這些變化沒有破壞任何已有代碼,JNI 本地方法接口也沒有改變。 · JDK1_1InitArgs 結構中的 reserved0 域已被重新命名為 version。JDK1_1InitArgs 結構保存 JNI_CreateJavaVM 的初始化參數(shù)。JNI_GetDefaultJavaVMInitArgs 和 JNI_CreateJavaVM 的調(diào)用者必須將版本域設置為 0x00010001。JNI_GetDefaultJavaVMInitArgs 被更改為返回 jint,用于表示是否支持所請求的版本。· JDK1_1InitA
15、rgs 結構中的 reserved1 域已被重新命名為 properties。這是一個 NULL-終結的字符串數(shù)組。每個字符串具有以下格式: name=value 表示系統(tǒng)屬性(該功能對應于 Java 命令行中的 -D 選項)。 · 在 JDK 1.1.1 中,調(diào)用 DestroyJavaVM 的線程必須是虛擬機中的唯一用戶線程。JDK 1.1.2 放松了這一限制。如果調(diào)用 DestroyJavaVM 時有多個用戶線程,則虛擬機將等待直到當前線程成為唯一的用戶線程,然后銷毀自己。2 - 設計概述 本章著重討論 JNI 中的主要設計問題,其中的大部分問題都與本地方法有關。調(diào)用 API
16、的設計將在 第 5 章 “調(diào)用 API” 中討論。JNI 接口函數(shù)和指針 平臺相關代碼是通過調(diào)用 JNI 函數(shù)來訪問 Java 虛擬機功能的。JNI 函數(shù)可通過接口指針來獲得。接口指針是指針的指針,它指向一個指針數(shù)組,而指針數(shù)組中的每個元素又指向一個接口函數(shù)。每個接口函數(shù)都處在數(shù)組的某個預定偏移量中。圖 2-1 說明了接口指針的組織結構。圖 2-1 接口指針 JNI 接口的組織類似于 C+ 虛擬函數(shù)表或 COM 接口。使用接口表而不使用硬性編入的函數(shù)表的好處是使 JNI 名字空間與平臺相關代碼分開。虛擬機可以很容易地提供多個版本的 JNI 函數(shù)表。例如,虛擬機可支持以下兩個 JNI 函數(shù)表:
17、· 一個表對非法參數(shù)進行全面檢查,適用于調(diào)試程序; · 另一個表只進行 JNI 規(guī)范所要求的最小程度的檢查,因此效率較高。JNI 接口指針只在當前線程中有效。因此,本地方法不能將接口指針從一個線程傳遞到另一個線程中。實現(xiàn) JNI 的虛擬機可將本地線程的數(shù)據(jù)分配和儲存在 JNI 接口指針所指向的區(qū)域中。本地方法將 JNI 接口指針當作參數(shù)來接受。虛擬機在從相同的 Java 線程中對本地方法進行多次調(diào)用時,保證傳遞給該本地方法的接口指針是相同的。但是,一個本地方法可被不同的 Java 線程所調(diào)用,因此可以接受不同的 JNI 接口指針。加載和鏈接本地方法 對本地方法的加載通過 S
18、ystem.loadLibrary 方法實現(xiàn)。下例中,類初始化方法加載了一個與平臺有關的本地庫,在該本地庫中給出了本地方法 f 的定義: package pkg; class Cls native double f(int i, String s); static System.loadLibrary("pkg_Cls"); System.loadLibrary 的參數(shù)是程序員任意選取的庫名。系統(tǒng)按照標準的但與平臺有關的處理方法將該庫名轉(zhuǎn)換為本地庫名。例如,Solaris 系統(tǒng)將名稱 pkg_Cls 轉(zhuǎn)換為 libpkg_Cls.so,而 Win32 系統(tǒng)將相同的名稱 pk
19、g_Cls 轉(zhuǎn)換為 pkg_Cls.dll。程序員可用單個庫來存放任意數(shù)量的類所需的所有本地方法,只要這些類將被相同的類加載器所加載。虛擬機在其內(nèi)部為每個類加載器保護其所加載的本地庫清單。提供者應該盡量選擇能夠避免名稱沖突的本地庫名。如果底層操作系統(tǒng)不支持動態(tài)鏈接,則必須事先將所有的本地方法鏈接到虛擬機上。這種情況下,虛擬機實際上不需要加載庫即可完成 System.loadLibrary 調(diào)用。程序員還可調(diào)用 JNI 函數(shù) RegisterNatives() 來注冊與類關聯(lián)的本地方法。在與靜態(tài)鏈接的函數(shù)一起使用時,RegisterNatives() 函數(shù)將特別有用。解析本地方法名 動態(tài)鏈接程序
20、是根據(jù)項的名稱來解析各項的。本地方法名由以下幾部分串接而成: · 前綴 Java_ · mangled 全限定的類名 · 下劃線(“_”)分隔符 · mangled 方法名 · 對于重載的本地方法,加上兩個下劃線(“_”),后跟 mangled 參數(shù)簽名 虛擬機將為本地庫中的方法查找匹配的方法名。它首先查找短名(沒有參數(shù)簽名的名稱),然后再查找?guī)?shù)簽名的長名稱。只有當某個本地方法被另一個本 地方法重載時程序員才有必要使用長名。但如果本地方法的名稱與非本地方法的名稱相同,則不會有問題。因為非本地方法(Java 方法)并不放在本地庫中。下例中,不
21、必用長名來鏈接本地方法 g,因為另一個方法 g 不是本地方法,因而它并不在本地庫中。 class Cls1 int g(int i); native int g(double d); 我們采取簡單的名字攪亂方案,以保證所有的 Unicode 字符都能被轉(zhuǎn)換為有效的 C 函數(shù)名。我們用下劃線(“_”) 字符來代替全限定的類名中的斜杠(“/”)。由于名稱或類型描述符從來不會以數(shù)字打頭,我們用 _0、.、_9 來代替轉(zhuǎn)義字符序列,如表 2-1 所示: 表 2-1 Unicode 字符轉(zhuǎn)換 轉(zhuǎn)義字符序列 表示 _0XXXX Unicode 字符 XXXX。_1 字符“_” _2 簽名中的字符“;” _
22、3 簽名中的字符“” 本地方法和接口 API 都要遵守給定平臺上的庫調(diào)用標準約定。例如,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ù)對應于通常 Java 方法的參數(shù)。本地方法調(diào)用利用返回值將結果傳回調(diào)用程序中。第 3 章 “JNI 的類型和數(shù)據(jù)結構” 將描述 Java 類型和 C 類型之間的映射。代碼示例 2-1 說明了如何用 C
23、 函數(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, /* 第一個參數(shù) */ jstring
24、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" /* 指定
25、C 調(diào)用約定 */ 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 的完全一樣
26、。在 C+ 中,JNI 函數(shù)被定義為內(nèi)聯(lián)成員函數(shù),它們將擴展為相應的 C 對應函數(shù)。引用 Java 對象 基本類型(如整型、字符型等)在 Java 和平臺相關代碼之間直接進行復制。而 Java 對象由引用來傳遞。虛擬機必須跟蹤傳到平臺相關代碼中的對象,以使這些對象不會被垃圾收集器釋放。反之,平臺相關代碼必須能用某種方式通知虛擬機它不再需要那些對象,同時,垃圾收集器必須能夠移走被平臺相關代碼引用過的對象。全局和局部引用 JNI 將平臺相關代碼使用的對象引用分成兩類:局部引用和全局引用。局部引用在本地方法調(diào)用期間有效,并在本地方法返回后被自動釋放掉。全局引用將一直有效,直到被顯式釋放。對象是被作為
27、局部引用傳遞給本地方法的,由 JNI 函數(shù)返回的所有 Java 對象也都是局部引用。JNI 允許程序員從局部引用創(chuàng)建全局引用。要求 Java 對象的 JNI 函數(shù)既可接受全局引用也可接受局部引用。本地方法將局部引用或全局引用作為結果返回。大多數(shù)情況下,程序員應該依靠虛擬機在本地方法返回后釋放所有局部引用。但是,有時程序員必須顯式釋放某個局部引用。例如,考慮以下的情形: · 本地方法要訪問一個大型 Java 對象,于是創(chuàng)建了對該 Java 對象的局部引用。然后,本地方法要在返回調(diào)用程序之前執(zhí)行其它計算。對這個大型 Java 對象的局部引用將防止該對象被當作垃圾收集,即使在剩余的運算中并
28、不再需要該對象。 · 本 地方法創(chuàng)建了大量的局部引用,但這些局部引用并不是要同時使用。由于虛擬機需要一定的空間來跟蹤每個局部引用,創(chuàng)建太多的局部引用將可能使系統(tǒng)耗盡內(nèi)存。 例如,本地方法要在一個大型對象數(shù)組中循環(huán),把取回的元素作為局部引用,并在每次迭代時對一個元素進行操作。每次迭代后,程序員不再需要對該數(shù)組元素的局 部引用。JNI 允許程序員在本地方法內(nèi)的任何地方對局部引用進行手工刪除。為確保程序員可以手工釋放局部引用,JNI 函數(shù)將不能創(chuàng)建額外的局部引用,除非是這些 JNI 函數(shù)要作為結果返回的引用。局部引用僅在創(chuàng)建它們的線程中有效。本地方法不能將局部引用從一個線程傳遞到另一個線程
29、中。實現(xiàn)局部引用 為了實現(xiàn)局部引用,Java 虛擬機為每個從 Java 到本地方法的控制轉(zhuǎn)換都創(chuàng)建了注冊服務程序。注冊服務程序?qū)⒉豢梢苿拥木植恳糜成錇?Java 對象,并防止這些對象被當作垃圾收集。所有傳給本地方法的 Java 對象(包括那些作為 JNI 函數(shù)調(diào)用結果返回的對象)將被自動添加到注冊服務程序中。本地方法返回后,注冊服務程序?qū)⒈粍h除,其中的所有項都可以被當作垃圾來收集??捎酶鞣N不同的方法來實現(xiàn)注冊服務程序,例如,使用表、鏈接列表或 hash 表來實現(xiàn)。雖然引用計數(shù)可用來避免注冊服務程序中有重復的項,但 JNI 實現(xiàn)不是必須檢測和消除重復的項。注意,以保守方式掃描本地堆棧并不能如實
30、地實現(xiàn)局部引用。平臺相關代碼可將局部引用儲存在全局或堆數(shù)據(jù)結構中。訪問 Java 對象 JNI 提供了一大批用來訪問全局引用和局部引用的函數(shù)。這意味著無論虛擬機在內(nèi)部如何表示 Java 對象,相同的本地方法實現(xiàn)都能工作。這就是為什么 JNI 可被各種各樣的虛擬機實現(xiàn)所支持的關鍵原因。通過不透明的引用來使用訪問函數(shù)的開銷比直接訪問 C 數(shù)據(jù)結構的開銷來得高。我們相信,大多數(shù)情況下,Java 程序員使用本地方法是為了完成一些重要任務,此時這種接口的開銷不是首要問題。訪問基本類型數(shù)組 對于含有大量基本數(shù)據(jù)類型(如整數(shù)數(shù)組和字符串)的 Java 對象來說,這種開銷將高得不可接受 (考慮一下用于執(zhí)行矢量
31、和矩陣運算的本地方法的情形便知)。對 Java 數(shù)組進行迭代并且要通過函數(shù)調(diào)用取回數(shù)組的每個元素,其效率是非常低的。一個解決辦法是引入“釘住”概念,以使本地方法能夠要求虛擬機釘住數(shù)組內(nèi)容。而后,該本地方法將接受指向數(shù)值元素的直接指針。但是,這種方法包含以下兩個前提: · 垃圾收集器必須支持釘住。 · 虛擬機必須在內(nèi)存中連續(xù)存放基本類型數(shù)組。雖然大多數(shù)基本類型數(shù)組都是連續(xù)存放的,但布爾數(shù)組可以壓縮或不壓縮存儲。因此,依賴于布爾數(shù)組確切存儲方式的本地方法將是不可移植的。我們將采取折衷方法來克服上述兩個問題。首先,我們提供了一套函數(shù),用于在 Java 數(shù)組的一部分和本地內(nèi)存緩沖之
32、間復制基本類型數(shù)組元素。這些函數(shù)只有在本地方法只需訪問大型數(shù)組中的一小部分元素時才使用。其次,程序員可用另一套函數(shù)來取回數(shù)組元素的受約束版本。記住,這些函數(shù)可能要求 Java 虛擬機分配存儲空間和進行復制。虛擬機實現(xiàn)將決定這些函數(shù)是否真正復制該數(shù)組,如下所示: · 如果垃圾收集器支持釘住,且數(shù)組的布局符合本地方法的要求,則不需要進行復制。 · 否則,該數(shù)組將被復制到不可移動的內(nèi)存塊中(例如,復制到 C 堆中),并進行必要的格式轉(zhuǎn)換,然后返回指向該副本的指針。最后,接口提供了一些函數(shù),用以通知虛擬機本地方法已不再需要訪問這些數(shù)組元素。當調(diào)用這些函數(shù)時,系統(tǒng)或者釋放數(shù)組,或者在
33、原始數(shù)組與其不可移動副本之間進行協(xié)調(diào)并將副本釋放。這種處理方法具有靈活性。垃圾收集器的算法可對每個給定的數(shù)組分別作出復制或釘住的決定。例如,垃圾收集器可能復制小型對象而釘住大型對象。JNI 實現(xiàn)必須確保多個線程中運行的本地方法可同時訪問同一數(shù)組。例如,JNI 可以為每個被釘住的數(shù)組保留一個內(nèi)部計數(shù)器,以便某個線程不會解開同時被另一個線程釘住的數(shù)組。注意,JNI 不必將基本類型數(shù)組鎖住以專供某個本地方法訪問。同時從不同的線程對 Java 數(shù)組進行更新將導致不確定的結果。訪問域和方法 JNI 允許本地方法訪問 Java 對象的域或調(diào)用其方法。JNI 用符號名稱和類型簽名來識別方法和域。從名稱和簽名
34、來定位域或?qū)ο蟮倪^程可分為兩步。例如,為調(diào)用類 cls 中的 f 方法,平臺相關代碼首先要獲得方法 ID,如下所示: jmethodID mid = env->GetMethodID(cls, "f", "(ILjava/lang/String;)D");然后,平臺相關代碼可重復使用該方法 ID 而無須再查找該方法,如下所示: jdouble result = env->CallDoubleMethod(obj, mid, 10, str);域 ID 或方法 ID 并不能防止虛擬機卸載生成該 ID 的類。該類被卸載之后,該方法 ID 或域 I
35、D 亦變成無效。因此,如果平臺相關代碼要長時間使用某個方法 ID 或域 ID,則它必須確保: · 保留對所涉及類的活引用,或 · 重新計算該方法 ID 或域 ID。JNI 對域 ID 和方法 ID 的內(nèi)部實現(xiàn)并不施加任何限制。報告編程錯誤 JNI 不檢查諸如傳遞 NULL 指針或非法參數(shù)類型之類的編程錯誤。非法的參數(shù)類型包括諸如要用 Java 類對象時卻用了普通 Java 對象這樣的錯誤。JNI 不檢查這些編程錯誤的理由如下: · 強迫 JNI 函數(shù)去檢查所有可能的錯誤情況將降低正常(正確)的本地方法的性能。 · 在許多情況下,沒有足夠的運行時的類型信息
36、可供這種檢查使用。大多數(shù) C 庫函數(shù)對編程錯誤不進行防范。例如,printf() 函數(shù)在接到一個無效地址時通常是引起運行錯而不是返回錯誤代碼。強迫 C 庫函數(shù)檢查所有可能的錯誤情況將有可能引起這種檢查被重復進行-先是在用戶代碼中進行,然后又在庫函數(shù)中再次進行。程序員不得將非法指針或錯誤類型的參數(shù)傳遞給 JNI 函數(shù)。否則,可能產(chǎn)生意想不到的后果,包括可能使系統(tǒng)狀態(tài)受損或使虛擬機崩潰。Java 異常 JNI 允許本地方法拋出任何 Java 異常。本地方法也可以處理突出的 Java 異常。未被處理的 Java 異常將被傳回虛擬機中。異常和錯誤代碼 一些 JNI 函數(shù)使用 Java 異常機制來報告錯
37、誤情況。大多數(shù)情況下,JNI 函數(shù)通過返回錯誤代碼并拋出 Java 異常來報告錯誤情況。錯誤代碼通常是特殊的返回值(如 NULL),這種特殊的返回值在正常返回值范圍之外。因此,程序員可以: · 快速檢查上一個 JNI 調(diào)用所返回的值以確定是否出錯,并 · 通過調(diào)用函數(shù) ExceptionOccurred() 來獲得異常對象,它含有對錯誤情況的更詳細說明。在以下兩種情況中,程序員需要先查出異常,然后才能檢查錯誤代碼: · 調(diào)用 Java 方法的 JNI 函數(shù)返回該 Java 方法的結果。程序員必須調(diào)用 ExceptionOccurred() 以檢查在執(zhí)行 Java
38、方法期間可能發(fā)生的異常。 · 某些用于訪問 JNI 數(shù)組的函數(shù)并不返回錯誤代碼,但可能會拋出 ArrayIndexOutOfBoundsException 或 ArrayStoreException。在所有其它情況下,返回值如果不是錯誤代碼值就可確保沒有拋出異常。異步異常 在多個線程的情況下,當前線程以外的其它線程可能會拋出異步異常。異步異常并不立即影響當前線程中平臺相關代碼的執(zhí)行,直到出現(xiàn)下列情況: · 該平臺相關代碼調(diào)用某個有可能拋出同步異常的 JNI 函數(shù),或者 · 該平臺相關代碼用 ExceptionOccurred() 顯式檢查同步異?;虍惒疆惓?。注意,
39、只有那些有可能拋出同步異常的 JNI 函數(shù)才檢查異步異常。本地方法應在必要的地方(例如,在一個沒有其它異常檢查的緊密循環(huán)中)插入 ExceptionOccurred() 檢查以確保當前線程可在適當時間內(nèi)對異步異常作出響應。異常的處理 可用兩種方法來處理平臺相關代碼中的異常: · 本地方法可選擇立即返回,使異常在啟動該本地方法調(diào)用的 Java 代碼中拋出。 · 平臺相關代碼可通過調(diào)用 ExceptionClear() 來清除異常,然后執(zhí)行自己的異常處理代碼。拋出了某個異常之后,平臺相關代碼必須先清除異常,然后才能進行其它的 JNI 調(diào)用。當有待定異常時,只有以下這些 JNI
40、函數(shù)可被安全地調(diào)用:ExceptionOccurred()、ExceptionDescribe() 和 ExceptionClear()。ExceptionDescribe() 函數(shù)將打印有關待定異常的調(diào)試消息。3 - JNI 的類型和數(shù)據(jù)結構 本章討論 JNI 如何將 Java 類型映射到本地 C 類型?;绢愋?表 3-1 描述 Java 基本類型及其與計算機相關的本地等效類型。表 3-1 基本類型和本地等效類型 Java 類型 本地類型 說明 boolean jboolean 無符號,8 位 byte jbyte 無符號,8 位 char jchar 無符號,16 位 short jsh
41、ort 有符號,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ù)類型用于描述主要指數(shù)和大小: typedef jint jsize;引用類型 JNI 包含了很多對應于不同 Java 對象的引用類型。JNI 引用類型的組織層次如圖 3-1 所示。 圖 3-1 引用類型層次 在 C 中,所有其它 JNI 引用類型都被定義為與 jobje
42、ct 一樣。例如: typedef jobject jclass;在 C+ 中,JNI 引入了虛構類以加強子類關系。例如: class _jobject ; class _jclass : public _jobject ; . typedef _jobject *jobject; typedef _jclass *jclass;域 ID 和方法 ID 方法 ID 和域 ID 是常規(guī)的 C 指針類型: struct _jfieldID; /*不透明結構 */ typedef struct _jfieldID *jfieldID; /* 域 ID */ struct _jmethodID; /*
43、 不透明結構 */ typedef struct _jmethodID *jmethodID; /* 方法 ID */值類型 jvalue 聯(lián)合類型在參數(shù)數(shù)組中用作單元類型。其聲明方式如下: typedef union jvalue jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l; jvalue;類型簽名 JNI 使用 Java 虛擬機的類型簽名表述。表 3-2 列出了這些類型簽名。表 3-2 Java 虛擬機類型簽名 類型簽名 Java 類型 Z boolean B
44、 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);具有以下類型簽名: (ILjava/lang/String;I)JUTF-8 字符串 JNI 用 UTF-8 字符串來表示各種字符串類型。UTF-8 字符串和 Java 虛擬機所使用的一樣。UTF-8 字符串的編碼方式使得僅包含非空 ASCII 字符的字符序列能夠按
45、每字符一個字節(jié)表示,但是最多只能表示 16 位的字符。所有在 u0001 到 u007F 范圍內(nèi)的字符都用單字節(jié)表示,如下所示: 字節(jié)中的七位數(shù)據(jù)確定了所表示字符的值。空字符 (u000) 和 u0080 到 u07FF 范圍內(nèi)的字符用一對字節(jié)表示, 即 x 和 y,如下所示: 值為 (x&0x1f)<<6)+(y&0x3f) 的字符需用兩個字節(jié)表示。u0800 到 uFFFF 范圍內(nèi)的字符用三個字節(jié)表示,即 x,y,和 z: 值為 (x&0xf)<<12)+(y&0x3f)<<6)+(z&0x3f) 的字符需用三個字
46、節(jié)表示。此格式與“標準” 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ù)表的準確布局。 注意:“必須”一詞用于約束 JNI 編程人員。例如,當說明某個 JNI 函數(shù)必須接收非空對象時,就應確保不要向該 JNI 函數(shù)傳遞 NULL。這時,JNI 實現(xiàn)將無需在該 JNI 函數(shù)
47、中執(zhí)行 NULL 指針檢查。 本章的部分資料改編自 Netscape 的 JRI 文檔。該參考資料按用法對函數(shù)進行組織。參考部分按下列函數(shù)區(qū)域進行組織: · 版本信息 · 類操作 · 異常 · 全局及局部引用 · 對象操作 · 訪問對象的域 · 調(diào)用實例方法 · 訪問靜態(tài)域 · 調(diào)用靜態(tài)方法 · 字符串操作 · 數(shù)組操作 · 注冊本地方法 · 監(jiān)視程序操作 · Java 虛擬機接口 接口函數(shù)表 每個函數(shù)均可通過 JNIEnv 參數(shù)以固定偏移量進行訪問。JN
48、IEnv 的類型是一個指針,指向存儲全部 JNI 函數(shù)指針的結構。其定義如下:注意:前三項留作將來與 COM 兼容。此外,我們在函數(shù)表開頭部分也留出來多個 NULL 項,從而可將將來與類有關的 JNI 操作添加到 FindClass 后面,而非函數(shù)表的末尾。注意,函數(shù)表可在所有 JNI 接口指針間共享。代碼示例 4-1 const struct JNINativeInterface . = NULL, NULL, NULL, NULL, GetVersion, DefineClass, FindClass, NULL, NULL, NULL, GetSuperclass, IsAssignab
49、leFrom, NULL, Throw, ThrowNew, ExceptionOccurred, ExceptionDescribe, ExceptionClear, FatalError, NULL, NULL, NewGlobalRef, DeleteGlobalRef, DeleteLocalRef, IsSameObject, NULL, NULL, AllocObject, NewObject, NewObjectV, NewObjectA, GetObjectClass, IsInstanceOf, GetMethodID, CallObjectMethod, CallObjec
50、tMethodV, CallObjectMethodA, CallBooleanMethod, CallBooleanMethodV, CallBooleanMethodA, CallByteMethod, CallByteMethodV, CallByteMethodA, CallCharMethod, CallCharMethodV, CallCharMethodA, CallShortMethod, CallShortMethodV, CallShortMethodA, CallIntMethod, CallIntMethodV, CallIntMethodA, CallLongMeth
51、od, CallLongMethodV, CallLongMethodA, CallFloatMethod, CallFloatMethodV, CallFloatMethodA, CallDoubleMethod, CallDoubleMethodV, CallDoubleMethodA, CallVoidMethod, CallVoidMethodV, CallVoidMethodA, CallNonvirtualObjectMethod, CallNonvirtualObjectMethodV, CallNonvirtualObjectMethodA, CallNonvirtualBoo
52、leanMethod, CallNonvirtualBooleanMethodV, CallNonvirtualBooleanMethodA, CallNonvirtualByteMethod, CallNonvirtualByteMethodV, CallNonvirtualByteMethodA, CallNonvirtualCharMethod, CallNonvirtualCharMethodV, CallNonvirtualCharMethodA, CallNonvirtualShortMethod, CallNonvirtualShortMethodV, CallNonvirtualShortMethodA, CallNonvirtualIntMethod, CallNonvirtualIntMethodV, CallNonvirtualIntMethodA, CallNonvirtua
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 公司背景墻策劃方案
- 公司春季放風箏活動方案
- 公司游園小活動策劃方案
- 公司職稱評審策劃方案
- 公司群體互動策劃方案
- 公司群體性運動活動方案
- 公司節(jié)前大掃除活動方案
- 公司知識跨年活動方案
- 公司管理規(guī)范年活動方案
- 公司旅游預熱引流活動方案
- 網(wǎng)絡輿情監(jiān)控管理制度
- 機器試用擔保協(xié)議書范本
- 2025年上海徐匯區(qū)高一(下)信息技術合格考試題及答案
- 國家開放大學《理工英語1》期末機考題庫
- POCT血糖儀項目培訓記錄表、資質(zhì)授權申請表
- 鄉(xiāng)村治理-課件
- 增材制造技術發(fā)展課件
- 少兒財商的培養(yǎng)(課堂)課件
- 暨南大學《馬克思主義基本原理概論》題庫歷年期末考試真題分類匯編及答案
- 青霉素的發(fā)現(xiàn)與作用課件
- 2018年專利代理師資格考試科目三-專利代理實務真題及解析
評論
0/150
提交評論