天天看點

《Java 本地接口規範》- 簡介

1 - 簡介

本章介紹 java 本地接口(java native interface,jni)。jni 是本地程式設計接口。它使得在 java 虛拟機 (vm) 内部運作的 java 代碼能夠與用其它程式設計語言(如 c、c++ 和彙編語言)編寫的應用程式和庫進行互操作。

jni 最重要的好處是它沒有對底層 java 虛拟機的實作施加任何限制。是以,java虛拟機廠商可以在不影響虛拟機其它部分的情況下添加對 jni 的支援。程式員隻需編寫一種版本的本地應用程式或庫,就能夠與所有支援 jni 的 java 虛拟機協同工作。

本章論及以下主題:

java 本地接口概述

背景

目标

java 本地接口方法

利用 jni 程式設計

jdk 1.1.2 中的變化

盡管可以完全用 java 編寫應用程式,但是有時單獨用 java 不能滿足應用程式的需要。程式員使用 jni 來編寫 java 本地方法,可以處理那些不能完全用 java編寫應用程式的情況。

以下示例說明了何時需要使用 java 本地方法:

标準 java 類庫不支援與平台相關的應用程式所需的功能。

已經擁有了一個用另一種語言編寫的庫,而又希望通過 jni 使 java 代碼能夠通路該庫。

想用低級語言(如彙編語言)實作一小段時限代碼。

通過用 jni 程式設計,可以将本地方法用于:

建立、檢查及更新 java 對象(包括數組和字元串)。

調用 java 方法。

捕捉和抛出異常。

加載類和獲得類資訊。

執行運作時類型檢查。

也可以與調用 api 一起使用 jni,以允許任意本地應用程式嵌入到 java 虛拟機中。這樣使得程式員能夠輕易地讓已有應用程式支援 java,而不必與虛拟機源代碼相連結。

目前,不同廠商的虛拟機提供了不同的本地方法接口。這些不同的接口使程式員不得不在給定平台上編寫、維護和分發多種版本的本地方法庫。

下面簡要分析一下部分已有本地方法接口,例如:

jdk 1.0 本地方法接口

netscape 的 java 運作時接口

microsoft 的原始本地接口和 java/com 接口

jdk 1.0 附帶有本地方法接口。遺憾的是,有兩點原因使得該接口不适合于其它 java虛拟機。

第一,平台相關代碼将 java 對象中的域作為 c 結構的成員來進行通路。但是,java 語言規範沒有規定在記憶體中對象是如何布局的。如果java 虛拟機在記憶體中布局對象的方式有所不同,程式員就不得不重新編譯本地方法庫。

第二,jdk 1.0 的本地方法接口依賴于保守的垃圾收集器。例如,無限制地使用 unhand 宏使得有必要以保守方式掃描本地堆棧。

java 運作時接口

netscape 建議使用 java 運作時接口 (jri),它是 java 虛拟機所提供服務的通用接口。jri 的設計融入了可移植性---它幾乎沒有對底層 java 虛拟機的實作細節作任何假設。jri 提出了各種各樣的問題,包括本地方法、調試、反射、嵌入(調用)等等。

原始本地接口和 java/com 接口

microsoft java 虛拟機支援兩種本地方法接口。在低一級,它提供了高效的原始本地接口 (rni)。rni 提供了與 jdk 本地方法接口有高度源代碼級的向後相容性,盡管它們之間還有一個主要差別,即平台相關代碼必須用 rni 函數來與垃圾收集器進行顯式的互動,而不是依賴于保守的垃圾收集。

在高一級,microsoft 的 java/com 接口為 java 虛拟機提供了與語言無關的标準二進制接口。java 代碼可以象使用 java 對象一樣來使用 com 對象。java 類也可以作為 com 類顯示給系統的其餘部分。

我們認為統一的,經過細緻考慮的标準接口能夠向每個使用者提供以下好處:

每個虛拟機廠商都可以支援更多的平台相關代碼。

工具構造器不必維護不同的本地方法接口。

應用程式設計人員可以隻編寫一種版本的平台相關代碼就能夠在不同的虛拟機上運作。

獲得标準本地方法接口的最佳途徑是聯合所有對 java 虛拟機有興趣的當事方。是以,我們在 java 獲得許可方之間組織了一系列研讨會,對設計統一的本地方法接口進行了讨論。從研讨會可以明确地看出标準本地方法接口必須滿足以下要求:

二進制相容性 - 主要的目标是在給定平台上的所有 java 虛拟機實作之間實作本地方法庫的二進制相容性。對于給定平台,程式員隻需要維護一種版本的本地方法庫。

效率 - 若要支援時限代碼,本地方法接口必須增加一點系統開銷。所有已知的用于確定虛拟機無關性(因而具有二進制相容性)的技術都會占用一定的系統開銷。我們必須在效率與虛拟機無關性之間進行某種折衷。

功能 - 接口必須顯示足夠的 java 虛拟機内部情況以使本地方法能夠完成有用的任務。

我們希望采用一種已有的方法作為标準接口,因為這樣程式員(程式員不得不學習在不同虛拟機中的多種接口)的工作負擔最輕。遺憾的是,已有解決方案中沒有任何方案能夠完全地滿足我們的目标。

netscape 的 jri 最接近于我們所設想的可移植本地方法接口,因而我們采用它作為設計起點。熟悉 jri 的讀者将會注意到在 api 命名規則、方法和域 id 的使用、局部和全局引用的使用,等等中的相似點。雖然我們進行了最大的努力,但是jni 并不具有對 jri 的二進制相容性,不過虛拟機既可以支援 jri,又可以支援 jni。

microsoft 的 rni 是對 jdk 1.0 的改進,因為它可以解決使用非保守的垃圾收集器的本地方法的問題。然而,rni 不适合用作與虛拟機無關的本地方法接口。與 jdk類似,rni 本地方法将 java 對象作為 c 結構來通路。這将導緻兩個問題:

rni 将内部 java 對象的布局暴露給了平台相關代碼。

将 java 對象作為 c 結構直接進行通路使得不可能有效地加入“寫屏障”,寫屏障是進階的垃圾收集算法所必需的。

作為二進制标準,com 確定了不同虛拟機之間的完全二進制相容性。調用 com 方法隻要求間接調用,而這幾乎不會占用系統開銷。另外,com 對象對動态連結庫解決版本問題的方式也有很大的改進。

然而,有幾個因素阻礙了将 com 用作标準 java 本地方法接口:

第一,java/com 接口缺少某些必需功能,例如通路私有域和抛出普通異常。

第二,java/com 接口自動為 java 對象提供标準的 iunknown 和 idispatch com 接口,因而平台相關代碼能夠通路公有方法和域。遺憾的是,idispatch 接口不能處理重載的 java 方法,而且在比對方法名稱時不差別大小寫。另外,通過 idispatch 接口暴露的所有 java 方法被打包在一起來執行動态類型檢查和強制轉換。這是因為 idispatch 接口的設計隻考慮到了弱類型的語言(例如 basic)。

第三,com 允許軟體元件(包括完全成熟的應用程式)一起工作,而不是處理單個低層函數。我們認為将所有 java 類或低層本地方法都當作軟體元件是不恰當的。

第四,在 unix 平台上由于缺少對 com 的支援,是以阻礙了直接采用 com。

雖然我們沒有将 java 對象作為 com 對象暴露給平台相關代碼,但是 jni 接口自身與 com 具有二進制相容性。我們采用與 com 一樣的跳轉表和調用約定。這意味着,一旦具有對 com 的跨平台支援,jni 就能成為 java 虛拟機的 com 接口。

我們認為 jni 不應該是給定 java 虛拟機所支援的唯一的本地方法接口。标準接口的好處在于程式員可以将自己的平台相關代碼庫加載到不同的 java 虛拟機上。在某些情況下,程式員可能不得不使用低層且與虛拟機有關的接口來獲得較高的效率。但在其它情況下,程式員可能使用高層接口來建立軟體元件。實際上,我們希望随着 java 環境群組件軟體技術發展得越來越成熟,本地方法将變得越來越不重要。

本地方法程式設計人員應開始利用 jni 進行程式設計。利用 jni 程式設計隔離了一些未知條件,例如終端使用者可能正在運作的廠商的虛拟機。遵守 jni 标準是本地庫能在給定 java 虛拟機上運作的最好保證。例如,雖然 jdk 1.1 将繼續支援 jdk 1.0 中所實作的舊式的本地方法接口,但是可以肯定的是 jdk 的未來版本将停止支援舊式的本地方法接口。依賴于舊式接口的本地方法将不得不重新編寫。

如果您正在實作 java 虛拟機,則應該實作 jni。我們(javasoft 和獲得許可方)盡力確定 jni 不會占用虛拟機實作的系統開銷或施加任何限制,包括對象表示,垃圾收集機制等。如果您遇到了我們可能忽視了的問題,請告知我們。

為了更好地支援 java 運作時環境 (jre),在 jdk 1.1.2 中對調用 api 在幾個方面作了擴充。這些變化沒有破壞任何已有代碼,jni 本地方法接口也沒有改變。

jdk1_1initargs 結構中的 reserved0 域已被重新命名為 version。jdk1_1initargs 結構儲存 jni_createjavavm 的初始化參數。jni_getdefaultjavavminitargs 和 jni_createjavavm 的調用者必須将版本域設定為 0x00010001。jni_getdefaultjavavminitargs 被更改為傳回 jint,用于表示是否支援所請求的版本。

jdk1_1initargs 結構中的 reserved1 域已被重新命名為 properties。這是一個 null-終結的字元串數組。每個字元串具有以下格式:

name=value

表示系統屬性(該功能對應于 java 指令行中的 -d 選項)。

在 jdk 1.1.1 中,調用 destroyjavavm 的線程必須是虛拟機中的唯一使用者線程。jdk 1.1.2 放松了這一限制。如果調用 destroyjavavm 時有多個使用者線程,則虛拟機将等待直到目前線程成為唯一的使用者線程,然後銷毀自己。