《Java 本地接口規範》- 簡介
1 - 簡介
本章介紹 Java 本地接口(Java Native Interface,JNI)。JNI 是本地編程接口。它使得在 Java 虛擬機 (VM) 內部運行的 Java 代碼能夠與用其它編程語言(如 C、C++ 和匯編語言)編寫的應用程序和庫進行互操作。
JNI 最重要的好處是它沒有對底層 Java 虛擬機的實現施加任何限製。因此,Java虛擬機廠商可以在不影響虛擬機其它部分的情況下添加對 JNI 的支持。程序員隻需編寫一種版本的本地應用程序或庫,就能夠與所有支持 JNI 的 Java 虛擬機協同工作。
盡管可以完全用 Java 編寫應用程序,但是有時單獨用 Java 不能滿足應用程序的需要。程序員使用 JNI 來編寫 Java 本地方法,可以處理那些不能完全用 Java編寫應用程序的情況。
也可以與調用 API 一起使用 JNI,以允許任意本地應用程序嵌入到 Java 虛擬機中。這樣使得程序員能夠輕易地讓已有應用程序支持 Java,而不必與虛擬機源代碼相鏈接。
目前,不同廠商的虛擬機提供了不同的本地方法接口。這些不同的接口使程序員不得不在給定平台上編寫、維護和分發多種版本的本地方法庫。
JDK 1.0 本地方法接口
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 結構來訪問。這將導致兩個問題:
作為二進製標準,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 時有多個用戶線程,則虛擬機將等待直到當前線程成為唯一的用戶線程,然後銷毀自己。最後更新:2017-04-02 06:51:59