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

下載本文檔

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

文檔簡介

1、【精品文檔】如有侵權(quán),請(qǐng)聯(lián)系網(wǎng)站刪除,僅供學(xué)習(xí)與交流JNI API完全手冊(cè).精品文檔.1 - 簡介 本章介紹 Java 本地接口(Java Native Interface,JNI)。JNI 是本地編程接口。它使得在 Java 虛擬機(jī) (VM) 內(nèi)部運(yùn)行的 Java 代碼能夠與用其它編程語言(如 C、C+ 和匯編語言)編寫的應(yīng)用程序和庫進(jìn)行互操作。JNI 最重要的好處是它沒有對(duì)底層 Java 虛擬機(jī)的實(shí)現(xiàn)施加任何限制。因此,Java 虛擬機(jī)廠商可以在不影響虛擬機(jī)其它部分的情況下添加對(duì) JNI 的支持。程序員只需編寫一種版本的本地應(yīng)用程序或庫,就能夠與所有支持 JNI 的 Java 虛擬機(jī)協(xié)同工

2、作。本章論及以下主題: · Java 本地接口概述 · 背景 · 目標(biāo) · Java 本地接口方法 · 利用 JNI 編程 · JDK 1.1.2 中的變化 Java 本地接口概述 盡管可以完全用 Java 編寫應(yīng)用程序,但是有時(shí)單獨(dú)用 Java 不能滿足應(yīng)用程序的需要。程序員使用 JNI 來編寫 Java 本地方法,可以處理那些不能完全用 Java 編寫應(yīng)用程序的情況。以下示例說明了何時(shí)需要使用 Java 本地方法: · 標(biāo)準(zhǔn) Java 類庫不支持與平臺(tái)相關(guān)的應(yīng)用程序所需的功能。 · 已經(jīng)擁有了一個(gè)用另一種語言編

3、寫的庫,而又希望通過 JNI 使 Java 代碼能夠訪問該庫。 · 想用低級(jí)語言(如匯編語言)實(shí)現(xiàn)一小段時(shí)限代碼。通過用 JNI 編程,可以將本地方法用于: · 創(chuàng)建、檢查及更新 Java 對(duì)象(包括數(shù)組和字符串)。 · 調(diào)用 Java 方法。 · 捕捉和拋出異常。 · 加載類和獲得類信息。 · 執(zhí)行運(yùn)行時(shí)類型檢查。也可以與調(diào)用 API 一起使用 JNI,以允許任意本地應(yīng)用程序嵌入到 Java 虛擬機(jī)中。這樣使得程序員能夠輕易地讓已有應(yīng)用程序支持 Java,而不必與虛擬機(jī)源代碼相鏈接。背景 目前,不同廠商的虛擬機(jī)提供了不同的本地方法接

4、口。這些不同的接口使程序員不得不在給定平臺(tái)上編寫、維護(hù)和分發(fā)多種版本的本地方法庫。下面簡要分析一下部分已有本地方法接口,例如: · JDK 1.0 本地方法接口 · Netscape 的 Java 運(yùn)行時(shí)接口 · Microsoft 的原始本地接口和 Java/COM 接口 JDK 1.0 本地方法接口 JDK 1.0 附帶有本地方法接口。遺憾的是,有兩點(diǎn)原因使得該接口不適合于其它 Java 虛擬機(jī)。第一,平臺(tái)相關(guān)代碼將 Java 對(duì)象中的域作為 C 結(jié)構(gòu)的成員來進(jìn)行訪問。但是,Java 語言規(guī)范沒有規(guī)定在內(nèi)存中對(duì)象是如何布局的。如果 Java 虛擬機(jī)在內(nèi)存中布局

5、對(duì)象的方式有所不同,程序員就不得不重新編譯本地方法庫。第二,JDK 1.0 的本地方法接口依賴于保守的垃圾收集器。例如,無限制地使用 unhand 宏使得有必要以保守方式掃描本地堆棧。Java 運(yùn)行時(shí)接口 Netscape 建議使用 Java 運(yùn)行時(shí)接口 (JRI),它是 Java 虛擬機(jī)所提供服務(wù)的通用接口。JRI 的設(shè)計(jì)融入了可移植性-它幾乎沒有對(duì)底層 Java 虛擬機(jī)的實(shí)現(xiàn)細(xì)節(jié)作任何假設(shè)。JRI 提出了各種各樣的問題,包括本地方法、調(diào)試、反射、嵌入(調(diào)用)等等。原始本地接口和 Java/COM 接口 Microsoft Java 虛擬機(jī)支持兩種本地方法接口。在低一級(jí),它提供了高效的原始本

6、地接口 (RNI)。RNI 提供了與 JDK 本地方法接口有高度源代碼級(jí)的向后兼容性,盡管它們之間還有一個(gè)主要區(qū)別,即平臺(tái)相關(guān)代碼必須用 RNI 函數(shù)來與垃圾收集器進(jìn)行顯式的交互,而不是依賴于保守的垃圾收集。在高一級(jí),Microsoft 的 Java/COM 接口為 Java 虛擬機(jī)提供了與語言無關(guān)的標(biāo)準(zhǔn)二進(jìn)制接口。Java 代碼可以象使用 Java 對(duì)象一樣來使用 COM 對(duì)象。Java 類也可以作為 COM 類顯示給系統(tǒng)的其余部分。目標(biāo) 我們認(rèn)為統(tǒng)一的,經(jīng)過細(xì)致考慮的標(biāo)準(zhǔn)接口能夠向每個(gè)用戶提供以下好處: · 每個(gè)虛擬機(jī)廠商都可以支持更多的平臺(tái)相關(guān)代碼。 · 工具構(gòu)造器不

7、必維護(hù)不同的本地方法接口。 · 應(yīng)用程序設(shè)計(jì)人員可以只編寫一種版本的平臺(tái)相關(guān)代碼就能夠在不同的虛擬機(jī)上運(yùn)行。獲得標(biāo)準(zhǔn)本地方法接口的最佳途徑是聯(lián)合所有對(duì) Java 虛擬機(jī)有興趣的當(dāng)事方。因此,我們?cè)?Java 獲得許可方之間組織了一系列研討會(huì),對(duì)設(shè)計(jì)統(tǒng)一的本地方法接口進(jìn)行了討論。從研討會(huì)可以明確地看出標(biāo)準(zhǔn)本地方法接口必須滿足以下要求: · 二進(jìn)制兼容性 - 主要的目標(biāo)是在給定平臺(tái)上的所有 Java 虛擬機(jī)實(shí)現(xiàn)之間實(shí)現(xiàn)本地方法庫的二進(jìn)制兼容性。對(duì)于給定平臺(tái),程序員只需要維護(hù)一種版本的本地方法庫。 · 效率 - 若要支持時(shí)限代碼,本地方法接口必須增加一點(diǎn)系統(tǒng)開銷。所有已

8、知的用于確保虛擬機(jī)無關(guān)性(因而具有二進(jìn)制兼容性)的技術(shù)都會(huì)占用一定的系統(tǒng)開銷。我們必須在效率與虛擬機(jī)無關(guān)性之間進(jìn)行某種折衷。 · 功能 - 接口必須顯示足夠的 Java 虛擬機(jī)內(nèi)部情況以使本地方法能夠完成有用的任務(wù)。Java 本地接口方法 我們希望采用一種已有的方法作為標(biāo)準(zhǔn)接口,因?yàn)檫@樣程序員(程序員不得不學(xué)習(xí)在不同虛擬機(jī)中的多種接口)的工作負(fù)擔(dān)最輕。遺憾的是,已有解決方案中沒有任何方案能夠完全地滿足我們的目標(biāo)。Netscape 的 JRI 最接近于我們所設(shè)想的可移植本地方法接口,因而我們采用它作為設(shè)計(jì)起點(diǎn)。熟悉 JRI 的讀者將會(huì)注意到在 API 命名規(guī)則、方法和域 ID 的使用、

9、局部和全局引用的使用,等等中的相似點(diǎn)。雖然我們進(jìn)行了最大的努力,但是 JNI 并不具有對(duì) JRI 的二進(jìn)制兼容性,不過虛擬機(jī)既可以支持 JRI,又可以支持 JNI。Microsoft 的 RNI 是對(duì) JDK 1.0 的改進(jìn),因?yàn)樗梢越鉀Q使用非保守的垃圾收集器的本地方法的問題。然而,RNI 不適合用作與虛擬機(jī)無關(guān)的本地方法接口。與 JDK 類似,RNI 本地方法將 Java 對(duì)象作為 C 結(jié)構(gòu)來訪問。這將導(dǎo)致兩個(gè)問題: · RNI 將內(nèi)部 Java 對(duì)象的布局暴露給了平臺(tái)相關(guān)代碼。 · 將 Java 對(duì)象作為 C 結(jié)構(gòu)直接進(jìn)行訪問使得不可能有效地加入“寫屏障”,寫屏障是高

10、級(jí)的垃圾收集算法所必需的。作為二進(jìn)制標(biāo)準(zhǔn),COM 確保了不同虛擬機(jī)之間的完全二進(jìn)制兼容性。調(diào)用 COM 方法只要求間接調(diào)用,而這幾乎不會(huì)占用系統(tǒng)開銷。另外,COM 對(duì)象對(duì)動(dòng)態(tài)鏈接庫解決版本問題的方式也有很大的改進(jìn)。然而,有幾個(gè)因素阻礙了將 COM 用作標(biāo)準(zhǔn) Java 本地方法接口: · 第一,Java/COM 接口缺少某些必需功能,例如訪問私有域和拋出普通異常。 · 第二,Java/COM 接口自動(dòng)為 Java 對(duì)象提供標(biāo)準(zhǔn)的 IUnknown 和 IDispatch COM 接口,因而平臺(tái)相關(guān)代碼能夠訪問公有方法和域。遺憾的是,IDispatch 接口不能處理重載的 Ja

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

12、M 具有二進(jìn)制兼容性。我們采用與 COM 一樣的跳轉(zhuǎn)表和調(diào)用約定。這意味著,一旦具有對(duì) COM 的跨平臺(tái)支持,JNI 就能成為 Java 虛擬機(jī)的 COM 接口。我們認(rèn)為 JNI 不應(yīng)該是給定 Java 虛擬機(jī)所支持的唯一的本地方法接口。標(biāo)準(zhǔn)接口的好處在于程序員可以將自己的平臺(tái)相關(guān)代碼庫加載到不同的 Java 虛擬機(jī)上。在某些情況下,程序員可能不得不使用低層且與虛擬機(jī)有關(guān)的接口來獲得較高的效率。但在其它情況下,程序員可能使用高層接口來建立軟件組件。實(shí)際上,我們希望隨著 Java 環(huán)境和組件軟件技術(shù)發(fā)展得越來越成熟,本地方法將變得越來越不重要。利用 JNI 編程 本地方法程序設(shè)計(jì)人員應(yīng)開始利用

13、JNI 進(jìn)行編程。利用 JNI 編程隔離了一些未知條件,例如終端用戶可能正在運(yùn)行的廠商的虛擬機(jī)。遵守 JNI 標(biāo)準(zhǔn)是本地庫能在給定 Java 虛擬機(jī)上運(yùn)行的最好保證。例如,雖然 JDK 1.1 將繼續(xù)支持 JDK 1.0 中所實(shí)現(xiàn)的舊式的本地方法接口,但是可以肯定的是 JDK 的未來版本將停止支持舊式的本地方法接口。依賴于舊式接口的本地方法將不得不重新編寫。如果您正在實(shí)現(xiàn) Java 虛擬機(jī),則應(yīng)該實(shí)現(xiàn) JNI。我們(Javasoft 和獲得許可方)盡力確保 JNI 不會(huì)占用虛擬機(jī)實(shí)現(xiàn)的系統(tǒng)開銷或施加任何限制,包括對(duì)象表示,垃圾收集機(jī)制等。如果您遇到了我們可能忽視了的問題,請(qǐng)告知我們。JDK 1

14、.1.2 中的變化 為了更好地支持 Java 運(yùn)行時(shí)環(huán)境 (JRE),在 JDK 1.1.2 中對(duì)調(diào)用 API 在幾個(gè)方面作了擴(kuò)展。這些變化沒有破壞任何已有代碼,JNI 本地方法接口也沒有改變。 · JDK1_1InitArgs 結(jié)構(gòu)中的 reserved0 域已被重新命名為 version。JDK1_1InitArgs 結(jié)構(gòu)保存 JNI_CreateJavaVM 的初始化參數(shù)。JNI_GetDefaultJavaVMInitArgs 和 JNI_CreateJavaVM 的調(diào)用者必須將版本域設(shè)置為 0x00010001。JNI_GetDefaultJavaVMInitArgs 被更

15、改為返回 jint,用于表示是否支持所請(qǐng)求的版本。· JDK1_1InitArgs 結(jié)構(gòu)中的 reserved1 域已被重新命名為 properties。這是一個(gè) NULL-終結(jié)的字符串?dāng)?shù)組。每個(gè)字符串具有以下格式: name=value 表示系統(tǒng)屬性(該功能對(duì)應(yīng)于 Java 命令行中的 -D 選項(xiàng))。 · 在 JDK 1.1.1 中,調(diào)用 DestroyJavaVM 的線程必須是虛擬機(jī)中的唯一用戶線程。JDK 1.1.2 放松了這一限制。如果調(diào)用 DestroyJavaVM 時(shí)有多個(gè)用戶線程,則虛擬機(jī)將等待直到當(dāng)前線程成為唯一的用戶線程,然后銷毀自己。2 - 設(shè)計(jì)概述 本

16、章著重討論 JNI 中的主要設(shè)計(jì)問題,其中的大部分問題都與本地方法有關(guān)。調(diào)用 API 的設(shè)計(jì)將在 第 5 章 “調(diào)用 API” 中討論。JNI 接口函數(shù)和指針 平臺(tái)相關(guān)代碼是通過調(diào)用 JNI 函數(shù)來訪問 Java 虛擬機(jī)功能的。JNI 函數(shù)可通過接口指針來獲得。接口指針是指針的指針,它指向一個(gè)指針數(shù)組,而指針數(shù)組中的每個(gè)元素又指向一個(gè)接口函數(shù)。每個(gè)接口函數(shù)都處在數(shù)組的某個(gè)預(yù)定偏移量中。圖 2-1 說明了接口指針的組織結(jié)構(gòu)。圖 2-1 接口指針 JNI 接口的組織類似于 C+ 虛擬函數(shù)表或 COM 接口。使用接口表而不使用硬性編入的函數(shù)表的好處是使 JNI 名字空間與平臺(tái)相關(guān)代碼分開。虛擬機(jī)可以

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

18、用,因此可以接受不同的 JNI 接口指針。加載和鏈接本地方法 對(duì)本地方法的加載通過 System.loadLibrary 方法實(shí)現(xiàn)。下例中,類初始化方法加載了一個(gè)與平臺(tái)有關(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)的但與平臺(tái)有關(guān)的處理方法將該庫名轉(zhuǎn)換為本地庫名。例如,Solaris 系統(tǒng)將名稱 pkg

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

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

21、名稱相同,則不會(huì)有問題。因?yàn)榉潜镜胤椒ǎ↗ava 方法)并不放在本地庫中。下例中,不必用長名來鏈接本地方法 g,因?yàn)榱硪粋€(gè)方法 g 不是本地方法,因而它并不在本地庫中。 class Cls1 int g(int i); native int g(double d);我們采取簡單的名字?jǐn)噥y方案,以保證所有的 Unicode 字符都能被轉(zhuǎn)換為有效的 C 函數(shù)名。我們用下劃線(“_”) 字符來代替全限定的類名中的斜杠(“/”)。由于名稱或類型描述符從來不會(huì)以數(shù)字打頭,我們用 _0、.、_9 來代替轉(zhuǎn)義字符序列,如表 2-1 所示: 表 2-1 Unicode 字符轉(zhuǎn)換 轉(zhuǎn)義字符序列 表示 _0XXX

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

23、 將描述 Java 類型和 C 類型之間的映射。代碼示例 2-1 說明了如何用 C 函數(shù)來實(shí)現(xiàn)本地方法 f。對(duì)本地方法 f 的聲明如下: package pkg; class Cls native double f(int i, String s);具有長 mangled 名稱 Java_pkg_Cls_f_ILjava_lang_String_2 的 C 函數(shù)實(shí)現(xiàn)本地方法f: 代碼示例 2-1: 用 C 實(shí)現(xiàn)本地方法 jdouble Java_pkg_Cls_f_ILjava_lang_String_2 ( JNIEnv *env, /* 接口指針 */ jobject obj, /* “t

24、his”指針 */ jint i, /* 第一個(gè)參數(shù) */ jstring s) /* 第二個(gè)參數(shù) */ /* 取得 Java 字符串的 C 版本 */ const char *str = (*env)->GetStringUTFChars(env, s, 0); /* 處理該字符串 */ /* 至此完成對(duì) str 的處理 */ (*env)->ReleaseStringUTFChars(env, s, str); return .注意,我們總是用接口指針 env 來操作 Java 對(duì)象。可用 C+ 將此代碼寫得稍微簡潔一些,如代碼示例 2-2 所示: 代碼示例 2-2: 用 C+

25、 實(shí)現(xiàn)本地方法 extern "C" /* 指定 C 調(diào)用約定 */ jdouble Java_pkg_Cls_f_ILjava_lang_String_2 ( JNIEnv *env, /* 接口指針 */ jobject obj, /* “this”指針 */ jint i, /* 第一個(gè)參數(shù) */ jstring s) /* 第二個(gè)參數(shù) */ const char *str = env->GetStringUTFChars(s, 0); env->ReleaseStringUTFChars(s, str); return .使用 C+ 后,源代碼變得更為直

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

27、后被自動(dòng)釋放掉。全局引用將一直有效,直到被顯式釋放。對(duì)象是被作為局部引用傳遞給本地方法的,由 JNI 函數(shù)返回的所有 Java 對(duì)象也都是局部引用。JNI 允許程序員從局部引用創(chuàng)建全局引用。要求 Java 對(duì)象的 JNI 函數(shù)既可接受全局引用也可接受局部引用。本地方法將局部引用或全局引用作為結(jié)果返回。大多數(shù)情況下,程序員應(yīng)該依靠虛擬機(jī)在本地方法返回后釋放所有局部引用。但是,有時(shí)程序員必須顯式釋放某個(gè)局部引用。例如,考慮以下的情形: · 本地方法要訪問一個(gè)大型 Java 對(duì)象,于是創(chuàng)建了對(duì)該 Java 對(duì)象的局部引用。然后,本地方法要在返回調(diào)用程序之前執(zhí)行其它計(jì)算。對(duì)這個(gè)大型 Java

28、 對(duì)象的局部引用將防止該對(duì)象被當(dāng)作垃圾收集,即使在剩余的運(yùn)算中并不再需要該對(duì)象。 · 本 地方法創(chuàng)建了大量的局部引用,但這些局部引用并不是要同時(shí)使用。由于虛擬機(jī)需要一定的空間來跟蹤每個(gè)局部引用,創(chuàng)建太多的局部引用將可能使系統(tǒng)耗盡內(nèi)存。 例如,本地方法要在一個(gè)大型對(duì)象數(shù)組中循環(huán),把取回的元素作為局部引用,并在每次迭代時(shí)對(duì)一個(gè)元素進(jìn)行操作。每次迭代后,程序員不再需要對(duì)該數(shù)組元素的局 部引用。JNI 允許程序員在本地方法內(nèi)的任何地方對(duì)局部引用進(jìn)行手工刪除。為確保程序員可以手工釋放局部引用,JNI 函數(shù)將不能創(chuàng)建額外的局部引用,除非是這些 JNI 函數(shù)要作為結(jié)果返回的引用。局部引用僅在創(chuàng)建它

29、們的線程中有效。本地方法不能將局部引用從一個(gè)線程傳遞到另一個(gè)線程中。實(shí)現(xiàn)局部引用 為了實(shí)現(xiàn)局部引用,Java 虛擬機(jī)為每個(gè)從 Java 到本地方法的控制轉(zhuǎn)換都創(chuàng)建了注冊(cè)服務(wù)程序。注冊(cè)服務(wù)程序?qū)⒉豢梢苿?dòng)的局部引用映射為 Java 對(duì)象,并防止這些對(duì)象被當(dāng)作垃圾收集。所有傳給本地方法的 Java 對(duì)象(包括那些作為 JNI 函數(shù)調(diào)用結(jié)果返回的對(duì)象)將被自動(dòng)添加到注冊(cè)服務(wù)程序中。本地方法返回后,注冊(cè)服務(wù)程序?qū)⒈粍h除,其中的所有項(xiàng)都可以被當(dāng)作垃圾來收集??捎酶鞣N不同的方法來實(shí)現(xiàn)注冊(cè)服務(wù)程序,例如,使用表、鏈接列表或 hash 表來實(shí)現(xiàn)。雖然引用計(jì)數(shù)可用來避免注冊(cè)服務(wù)程序中有重復(fù)的項(xiàng),但 JNI 實(shí)現(xiàn)不

30、是必須檢測和消除重復(fù)的項(xiàng)。注意,以保守方式掃描本地堆棧并不能如實(shí)地實(shí)現(xiàn)局部引用。平臺(tái)相關(guān)代碼可將局部引用儲(chǔ)存在全局或堆數(shù)據(jù)結(jié)構(gòu)中。訪問 Java 對(duì)象 JNI 提供了一大批用來訪問全局引用和局部引用的函數(shù)。這意味著無論虛擬機(jī)在內(nèi)部如何表示 Java 對(duì)象,相同的本地方法實(shí)現(xiàn)都能工作。這就是為什么 JNI 可被各種各樣的虛擬機(jī)實(shí)現(xiàn)所支持的關(guān)鍵原因。通過不透明的引用來使用訪問函數(shù)的開銷比直接訪問 C 數(shù)據(jù)結(jié)構(gòu)的開銷來得高。我們相信,大多數(shù)情況下,Java 程序員使用本地方法是為了完成一些重要任務(wù),此時(shí)這種接口的開銷不是首要問題。訪問基本類型數(shù)組 對(duì)于含有大量基本數(shù)據(jù)類型(如整數(shù)數(shù)組和字符串)的 J

31、ava 對(duì)象來說,這種開銷將高得不可接受 (考慮一下用于執(zhí)行矢量和矩陣運(yùn)算的本地方法的情形便知)。對(duì) Java 數(shù)組進(jìn)行迭代并且要通過函數(shù)調(diào)用取回?cái)?shù)組的每個(gè)元素,其效率是非常低的。一個(gè)解決辦法是引入“釘住”概念,以使本地方法能夠要求虛擬機(jī)釘住數(shù)組內(nèi)容。而后,該本地方法將接受指向數(shù)值元素的直接指針。但是,這種方法包含以下兩個(gè)前提: · 垃圾收集器必須支持釘住。 · 虛擬機(jī)必須在內(nèi)存中連續(xù)存放基本類型數(shù)組。雖然大多數(shù)基本類型數(shù)組都是連續(xù)存放的,但布爾數(shù)組可以壓縮或不壓縮存儲(chǔ)。因此,依賴于布爾數(shù)組確切存儲(chǔ)方式的本地方法將是不可移植的。我們將采取折衷方法來克服上述兩個(gè)問題。首先,我

32、們提供了一套函數(shù),用于在 Java 數(shù)組的一部分和本地內(nèi)存緩沖之間復(fù)制基本類型數(shù)組元素。這些函數(shù)只有在本地方法只需訪問大型數(shù)組中的一小部分元素時(shí)才使用。其次,程序員可用另一套函數(shù)來取回?cái)?shù)組元素的受約束版本。記住,這些函數(shù)可能要求 Java 虛擬機(jī)分配存儲(chǔ)空間和進(jìn)行復(fù)制。虛擬機(jī)實(shí)現(xiàn)將決定這些函數(shù)是否真正復(fù)制該數(shù)組,如下所示: · 如果垃圾收集器支持釘住,且數(shù)組的布局符合本地方法的要求,則不需要進(jìn)行復(fù)制。 · 否則,該數(shù)組將被復(fù)制到不可移動(dòng)的內(nèi)存塊中(例如,復(fù)制到 C 堆中),并進(jìn)行必要的格式轉(zhuǎn)換,然后返回指向該副本的指針。最后,接口提供了一些函數(shù),用以通知虛擬機(jī)本地方法已不再

33、需要訪問這些數(shù)組元素。當(dāng)調(diào)用這些函數(shù)時(shí),系統(tǒng)或者釋放數(shù)組,或者在原始數(shù)組與其不可移動(dòng)副本之間進(jìn)行協(xié)調(diào)并將副本釋放。這種處理方法具有靈活性。垃圾收集器的算法可對(duì)每個(gè)給定的數(shù)組分別作出復(fù)制或釘住的決定。例如,垃圾收集器可能復(fù)制小型對(duì)象而釘住大型對(duì)象。JNI 實(shí)現(xiàn)必須確保多個(gè)線程中運(yùn)行的本地方法可同時(shí)訪問同一數(shù)組。例如,JNI 可以為每個(gè)被釘住的數(shù)組保留一個(gè)內(nèi)部計(jì)數(shù)器,以便某個(gè)線程不會(huì)解開同時(shí)被另一個(gè)線程釘住的數(shù)組。注意,JNI 不必將基本類型數(shù)組鎖住以專供某個(gè)本地方法訪問。同時(shí)從不同的線程對(duì) Java 數(shù)組進(jìn)行更新將導(dǎo)致不確定的結(jié)果。訪問域和方法 JNI 允許本地方法訪問 Java 對(duì)象的域或調(diào)用

34、其方法。JNI 用符號(hào)名稱和類型簽名來識(shí)別方法和域。從名稱和簽名來定位域或?qū)ο蟮倪^程可分為兩步。例如,為調(diào)用類 cls 中的 f 方法,平臺(tái)相關(guān)代碼首先要獲得方法 ID,如下所示: jmethodID mid = env->GetMethodID(cls, "f", "(ILjava/lang/String;)D");然后,平臺(tái)相關(guān)代碼可重復(fù)使用該方法 ID 而無須再查找該方法,如下所示: jdouble result = env->CallDoubleMethod(obj, mid, 10, str);域 ID 或方法 ID 并不能防止虛擬

35、機(jī)卸載生成該 ID 的類。該類被卸載之后,該方法 ID 或域 ID 亦變成無效。因此,如果平臺(tái)相關(guān)代碼要長時(shí)間使用某個(gè)方法 ID 或域 ID,則它必須確保: · 保留對(duì)所涉及類的活引用,或 · 重新計(jì)算該方法 ID 或域 ID。JNI 對(duì)域 ID 和方法 ID 的內(nèi)部實(shí)現(xiàn)并不施加任何限制。報(bào)告編程錯(cuò)誤 JNI 不檢查諸如傳遞 NULL 指針或非法參數(shù)類型之類的編程錯(cuò)誤。非法的參數(shù)類型包括諸如要用 Java 類對(duì)象時(shí)卻用了普通 Java 對(duì)象這樣的錯(cuò)誤。JNI 不檢查這些編程錯(cuò)誤的理由如下: · 強(qiáng)迫 JNI 函數(shù)去檢查所有可能的錯(cuò)誤情況將降低正常(正確)的本地方法

36、的性能。 · 在許多情況下,沒有足夠的運(yùn)行時(shí)的類型信息可供這種檢查使用。大多數(shù) C 庫函數(shù)對(duì)編程錯(cuò)誤不進(jìn)行防范。例如,printf() 函數(shù)在接到一個(gè)無效地址時(shí)通常是引起運(yùn)行錯(cuò)而不是返回錯(cuò)誤代碼。強(qiáng)迫 C 庫函數(shù)檢查所有可能的錯(cuò)誤情況將有可能引起這種檢查被重復(fù)進(jìn)行-先是在用戶代碼中進(jìn)行,然后又在庫函數(shù)中再次進(jìn)行。程序員不得將非法指針或錯(cuò)誤類型的參數(shù)傳遞給 JNI 函數(shù)。否則,可能產(chǎn)生意想不到的后果,包括可能使系統(tǒng)狀態(tài)受損或使虛擬機(jī)崩潰。Java 異常 JNI 允許本地方法拋出任何 Java 異常。本地方法也可以處理突出的 Java 異常。未被處理的 Java 異常將被傳回虛擬機(jī)中。異

37、常和錯(cuò)誤代碼 一些 JNI 函數(shù)使用 Java 異常機(jī)制來報(bào)告錯(cuò)誤情況。大多數(shù)情況下,JNI 函數(shù)通過返回錯(cuò)誤代碼并拋出 Java 異常來報(bào)告錯(cuò)誤情況。錯(cuò)誤代碼通常是特殊的返回值(如 NULL),這種特殊的返回值在正常返回值范圍之外。因此,程序員可以: · 快速檢查上一個(gè) JNI 調(diào)用所返回的值以確定是否出錯(cuò),并 · 通過調(diào)用函數(shù) ExceptionOccurred() 來獲得異常對(duì)象,它含有對(duì)錯(cuò)誤情況的更詳細(xì)說明。在以下兩種情況中,程序員需要先查出異常,然后才能檢查錯(cuò)誤代碼: · 調(diào)用 Java 方法的 JNI 函數(shù)返回該 Java 方法的結(jié)果。程序員必須調(diào)用

38、ExceptionOccurred() 以檢查在執(zhí)行 Java 方法期間可能發(fā)生的異常。 · 某些用于訪問 JNI 數(shù)組的函數(shù)并不返回錯(cuò)誤代碼,但可能會(huì)拋出 ArrayIndexOutOfBoundsException 或 ArrayStoreException。在所有其它情況下,返回值如果不是錯(cuò)誤代碼值就可確保沒有拋出異常。異步異常 在多個(gè)線程的情況下,當(dāng)前線程以外的其它線程可能會(huì)拋出異步異常。異步異常并不立即影響當(dāng)前線程中平臺(tái)相關(guān)代碼的執(zhí)行,直到出現(xiàn)下列情況: · 該平臺(tái)相關(guān)代碼調(diào)用某個(gè)有可能拋出同步異常的 JNI 函數(shù),或者 · 該平臺(tái)相關(guān)代碼用 Excep

39、tionOccurred() 顯式檢查同步異?;虍惒疆惓?。注意,只有那些有可能拋出同步異常的 JNI 函數(shù)才檢查異步異常。本地方法應(yīng)在必要的地方(例如,在一個(gè)沒有其它異常檢查的緊密循環(huán)中)插入 ExceptionOccurred() 檢查以確保當(dāng)前線程可在適當(dāng)時(shí)間內(nèi)對(duì)異步異常作出響應(yīng)。異常的處理 可用兩種方法來處理平臺(tái)相關(guān)代碼中的異常: · 本地方法可選擇立即返回,使異常在啟動(dòng)該本地方法調(diào)用的 Java 代碼中拋出。 · 平臺(tái)相關(guān)代碼可通過調(diào)用 ExceptionClear() 來清除異常,然后執(zhí)行自己的異常處理代碼。拋出了某個(gè)異常之后,平臺(tái)相關(guān)代碼必須先清除異常,然后才能

40、進(jìn)行其它的 JNI 調(diào)用。當(dāng)有待定異常時(shí),只有以下這些 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 描述 Java 基本類型及其與計(jì)算機(jī)相關(guān)的本地等效類型。表 3-1 基本類型和本地等效類型 Java 類型 本地類型 說明 boolean jboolean 無符號(hào),8 位 byte jbyte 無符號(hào),8

41、 位 char jchar 無符號(hào),16 位 short jshort 有符號(hào),16 位 int jint 有符號(hào),32 位 long jlong 有符號(hào),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 包含了很多對(duì)應(yīng)于不同 Java 對(duì)象的引用類型。JNI 引用類型的組織層次如圖 3-1 所示。圖 3-1 引用類型層次

42、在 C 中,所有其它 JNI 引用類型都被定義為與 jobject 一樣。例如: typedef jobject jclass;在 C+ 中,JNI 引入了虛構(gòu)類以加強(qiáng)子類關(guān)系。例如: class _jobject ; class _jclass : public _jobject ; typedef _jobject *jobject; typedef _jclass *jclass;域 ID 和方法 ID 方法 ID 和域 ID 是常規(guī)的 C 指針類型: struct _jfieldID; /*不透明結(jié)構(gòu) */ typedef struct _jfieldID *jfieldID; /*

43、域 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 j; jfloat f; jdouble d; jobject l; jvalue;類型簽名 JNI 使用 Java 虛擬機(jī)的類型簽名表述。表 3-2 列出了這些類型簽名。表 3-2 Java 虛擬機(jī)

44、類型簽名 類型簽名 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);具有以下類型簽名: (ILjava/lang/String;I)JUTF-8 字符串 JNI 用 UTF-8 字符串來表示各種字符串類型。UTF-8 字符串和 Java 虛擬機(jī)所使用的一樣。UTF-8 字符串

45、的編碼方式使得僅包含非空 ASCII 字符的字符序列能夠按每字符一個(gè)字節(jié)表示,但是最多只能表示 16 位的字符。所有在 u0001 到 u007F 范圍內(nèi)的字符都用單字節(jié)表示,如下所示: 字節(jié)中的七位數(shù)據(jù)確定了所表示字符的值??兆址?(u000) 和 u0080 到 u07FF 范圍內(nèi)的字符用一對(duì)字節(jié)表示, 即 x 和 y,如下所示: 值為 (x&0x1f)<<6)+(y&0x3f) 的字符需用兩個(gè)字節(jié)表示。u0800 到 uFFFF 范圍內(nèi)的字符用三個(gè)字節(jié)表示,即 x,y,和 z: 值為 (x&0xf)<<12)+(y&0x3f)<

46、;<6)+(z&0x3f) 的字符需用三個(gè)字節(jié)表示。此格式與“標(biāo)準(zhǔn)” UTF-8 格式之間有兩個(gè)區(qū)別。第一,空字節(jié) (byte)0 使用雙字節(jié)格式進(jìn)行編碼,而不是單字節(jié)格式。這意味著 Java 虛擬機(jī)的 UTF-8 字符串不可能有嵌入的空值。第二,只使用單字節(jié)、雙字節(jié)和三字節(jié)格式。Java 虛擬機(jī)不能識(shí)別更長的 UTF-8 格式。4 - JNI 函數(shù) 本章為 JNI 函數(shù)提供參考信息。其中列出了全部 JNI 函數(shù),同時(shí)也給出了 JNI 函數(shù)表的準(zhǔn)確布局。 注意:“必須”一詞用于約束 JNI 編程人員。例如,當(dāng)說明某個(gè) JNI 函數(shù)必須接收非空對(duì)象時(shí),就應(yīng)確保不要向該 JNI 函數(shù)

47、傳遞 NULL。這時(shí),JNI 實(shí)現(xiàn)將無需在該 JNI 函數(shù)中執(zhí)行 NULL 指針檢查。 本章的部分資料改編自 Netscape 的 JRI 文檔。該參考資料按用法對(duì)函數(shù)進(jìn)行組織。參考部分按下列函數(shù)區(qū)域進(jìn)行組織: · 版本信息 · 類操作 · 異常 · 全局及局部引用 · 對(duì)象操作 · 訪問對(duì)象的域 · 調(diào)用實(shí)例方法 · 訪問靜態(tài)域 · 調(diào)用靜態(tài)方法 · 字符串操作 · 數(shù)組操作 · 注冊(cè)本地方法 · 監(jiān)視程序操作 · Java 虛擬機(jī)接口 接口函數(shù)表 每個(gè)

48、函數(shù)均可通過 JNIEnv 參數(shù)以固定偏移量進(jìn)行訪問。JNIEnv 的類型是一個(gè)指針,指向存儲(chǔ)全部 JNI 函數(shù)指針的結(jié)構(gòu)。其定義如下:注意:前三項(xiàng)留作將來與 COM 兼容。此外,我們?cè)诤瘮?shù)表開頭部分也留出來多個(gè) NULL 項(xiàng),從而可將將來與類有關(guān)的 JNI 操作添加到 FindClass 后面,而非函數(shù)表的末尾。注意,函數(shù)表可在所有 JNI 接口指針間共享。代碼示例 4-1 const struct JNINativeInterface . = NULL, NULL, NULL, NULL, GetVersion, DefineClass, FindClass, NULL, NULL, NU

49、LL, GetSuperclass, IsAssignableFrom, NULL, Throw, ThrowNew, ExceptionOccurred, ExceptionDescribe, ExceptionClear, FatalError, NULL, NULL, NewGlobalRef, DeleteGlobalRef, DeleteLocalRef, IsSameObject, NULL, NULL, AllocObject, NewObject, NewObjectV, NewObjectA, GetObjectClass, IsInstanceOf, GetMethodID

50、, CallObjectMethod, CallObjectMethodV, CallObjectMethodA, CallBooleanMethod, CallBooleanMethodV, CallBooleanMethodA, CallByteMethod, CallByteMethodV, CallByteMethodA, CallCharMethod, CallCharMethodV, CallCharMethodA, CallShortMethod, CallShortMethodV, CallShortMethodA, CallIntMethod, CallIntMethodV,

51、 CallIntMethodA, CallLongMethod, CallLongMethodV, CallLongMethodA, CallFloatMethod, CallFloatMethodV, CallFloatMethodA, CallDoubleMethod, CallDoubleMethodV, CallDoubleMethodA, CallVoidMethod, CallVoidMethodV, CallVoidMethodA, CallNonvirtualObjectMethod, CallNonvirtualObjectMethodV, CallNonvirtualObj

52、ectMethodA, CallNonvirtualBooleanMethod, CallNonvirtualBooleanMethodV, CallNonvirtualBooleanMethodA, CallNonvirtualByteMethod, CallNonvirtualByteMethodV, CallNonvirtualByteMethodA, CallNonvirtualCharMethod, CallNonvirtualCharMethodV, CallNonvirtualCharMethodA, CallNonvirtualShortMethod, CallNonvirtualShortMethodV, CallNonvirtualShortMethodA, CallNonvirtualIntMethod, CallNonvirtualIntMethodV, CallNonvirtualIntMethodA, CallNonvirtualLongMethod, CallNonvirtualLongMethodV, CallNonvirtualLongMethodA, CallNonv

溫馨提示

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

評(píng)論

0/150

提交評(píng)論