JNI 接口規範

1. 簡介html

Java 本地接口概述 java

背景程序員

JDK 1.0 本地方法接口算法

Java 運行時接口編程

原始本地接口和 Java/COM 接口數組

 

目標安全

Java 本地接口方法數據結構

利用 JNI 編程異步

JDK 1.1.2 中的變化jvm

 

2. 設計概述

JNI 接口函數和指針

加載和連接本地方法

解析本地方法名

本地方法的參數

 

引用 Java 對象

全局和局部引用

實現局部引用

 

訪問 Java 對象

訪問基本類型數組

訪問域和方法

 

報告編程錯誤

Java 異常

異常和錯誤代碼

異步異常

異常的處理

 

3. JNI 的類型和數據結構

基本類型

引用類型

域 ID 和方法 ID

值類型

類型簽名

UTF-8 字符串

 

4. JNI 函數

接口函數表

版本信息

GetVersion

 

類操做

DefineClass

FindClass

GetSuperclass

IsAssignableFrom

 

異常

Throw

ThrowNew

ExceptionOccurred

ExceptionDescribe

ExceptionClear

FatalError

 

全局及局部引用

NewGlobalRef

DeleteGlobalRef

DeleteLocalRef

 

對象操做

AllocObject

 

GetObjectClass

IsInstanceOf

IsSameObject

 

訪問對象的域

GetFieldID

Get<type>Field 例程

Set<type>Field 例程

 

調用實例方法

GetMethodID

Call<type>Method

CallNonvirtual<type>Method

 

訪問靜態域

GetStaticFieldID

GetStatic<type>Field 例程

SetStatic<type>Field 例程

 

調用靜態方法

GetStaticMethodID

CallStatic<type>Method

 

字符串操做

NewString

GetStringLength

GetStringChars

ReleaseStringChars

NewStringUTF

GetStringUTFLength

GetStringUTFChars

ReleaseStringUTFChars

 

數組操做

GetArrayLength

NewObjectArray

GetObjectArrayElement

SetObjectArrayElement

New<PrimitiveType>Array 例程

Get<PrimitiveType>ArrayElements 例程

Release<PrimitiveType>ArrayElements 例程

Get<PrimitiveType>ArrayRegion 例程

Set<PrimitiveType>ArrayRegion 例程

 

註冊本地方法

RegisterNatives

UnregisterNatives

 

監視程序操做

MonitorEnter

MonitorExit

 

Java 虛擬機接口

GetJavaVM

 

5. 調用 API

 

概述

建立虛擬機

鏈接虛擬機

卸載虛擬機

 

初始化結構

調用 API 函數

JNI_GetDefaultJavaVMInitArgs

JNI_GetCreatedJavaVMs

JNI_CreateJavaVM

DestroyJavaVM

AttachCurrentThread

DetachCurrentThread

 

 

1 -


本章介紹 Java 本地接口(Java Native Interface,JNI)。JNI 是本地編程接口。它使得在 Java 虛擬機 (VM) 內部運行的 Java 代碼可以與用其它編程語言(如 C、C++ 和彙編語言)編寫的應用程序和庫進行互操做。

JNI 最重要的好處是它沒有對底層 Java 虛擬機的實現施加任何限制。所以,Java 虛擬機廠商能夠在不影響虛擬機其它部分的狀況下添加對 JNI 的支持。程序員只需編寫一種版本的本地應用程序或庫,就可以與全部支持 JNI 的 Java 虛擬機協同工做。

本章論及如下主題:


Java 本地接口概述

儘管能夠徹底用 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 本地方法接口

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 虛擬機內部狀況以使本地方法可以完成有用的任務。

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 編程隔離了一些未知條件,例如終端用戶可能正在運行的廠商的虛擬機。遵照 JNI 標準是本地庫能在給定 Java 虛擬機上運行的最好保證。例如,雖然 JDK 1.1 將繼續支持 JDK 1.0 中所實現的舊式的本地方法接口,可是能夠確定的是 JDK 的將來版本將中止支持舊式的本地方法接口。依賴於舊式接口的本地方法將不得不從新編寫。

若是您正在實現 Java 虛擬機,則應該實現 JNI。咱們(Javasoft 和得到許可方)盡力確保 JNI 不會佔用虛擬機實現的系統開銷或施加任何限制,包括對象表示,垃圾收集機制等。若是您遇到了咱們可能忽視了的問題,請告知咱們。


JDK 1.1.2 中的

爲了更好地支持 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 時有多個用戶線程,則虛擬機將等待直到當前線程成爲惟一的用戶線程,而後銷燬本身。

2 - 設計概述


本章着重討論 JNI 中的主要設計問題,其中的大部分問題都與本地方法有關。調用 API 的設計將在 第 5 章 「調用 API」 中討論。


JNI 接口函數和指針

平臺相關代碼是經過調用 JNI 函數來訪問 Java 虛擬機功能的。JNI 函數可經過接口指來得到。接口指針是指針的指針,它指向一個指針數組,而指針數組中的每一個元素又指向一個接口函數。每一個接口函數都處在數組的某個預約偏移量中。圖 2-1 說明了接口指針的組織結構。

 

圖 2-1 接口指針

JNI 接口的組織相似於 C++ 虛擬函數表或 COM 接口。使用接口表而不使用硬性編入的函數表的好處是使 JNI 名字空間與平臺相關代碼分開。虛擬機能夠很容易地提供多個版本的 JNI 函數表。例如,虛擬機可支持如下兩個 JNI 函數表:

  • 一個表對非法參數進行全面檢查,適用於調試程序;
  • 另外一個表只進行 JNI 規範所要求的最小程度的檢查,所以效率較高。

JNI 接口指針只在當前線程中有效。所以,本地方法不能將接口指針從一個線程傳遞到另外一個線程中。實現 JNI 的虛擬機可將本地線程的數據分配和儲存在 JNI 接口指針所指向的區域中。

本地方法將 JNI 接口指針看成參數來接受。虛擬機在從相同的 Java 線程中對本地方法進行屢次調用時,保證傳遞給該本地方法的接口指針是相同的。可是,一個本地方法可被不一樣的 Java 線程所調用,所以能夠接受不一樣的 JNI 接口指針。


加載和連接本地方法

對本地方法的加載經過 System.loadLibrary 方法實現。下例中,類初始化方法加載了一個與平臺有關的本地庫,在該本地庫中給出了本地方法 f 的定義:

    package pkg;
    class Cls {
         native double f(int i, String s);
         static {
             System.loadLibrary("pkg_Cls");
         }
    }

System.loadLibrary 的參數是程序員任意選取的庫名。系統按照標準的但與平臺有關的處理方法將該庫名轉換爲本地庫名。例如,Solaris 系統將名稱 pkg_Cls 轉換爲 libpkg_Cls.so,而 Win32 系統將相同的名稱 pkg_Cls 轉換爲 pkg_Cls.dll

程序員可用單個庫來存聽任意數量的類所需的全部本地方法,只要這些類將被相同的類加載器所加載。虛擬機在其內部爲每一個類加載器保護其所加載的本地庫清單。提供者應該儘可能選擇可以避免名稱衝突的本地庫名。

若是底層操做系統不支持動態連接,則必須事先將全部的本地方法連接到虛擬機上。這種狀況下,虛擬機實際上不須要加載庫便可完成 System.loadLibrary 調用。

程序員還可調用 JNI 函數 RegisterNatives() 來註冊與類關聯的本地方法。在與靜態連接的函數一塊兒使用時,RegisterNatives() 函數將特別有用。

 

解析本地方法名

動態連接程序是根據項的名稱來解析各項的。本地方法名由如下幾部分串接而成:

  • 前綴 Java_
  • mangled 全限定的類名
  • 下劃線(「_」)分隔符
  • mangled 方法名
  • 對於重載的本地方法,加上兩個下劃線(「__」),後跟 mangled 參數簽名

虛擬機將爲本地庫中的方法查找匹配的方法名。它首先查找短名(沒有參數簽名的名稱),而後再查找帶參數簽名的長名稱。只有當某個本地方法被另外一個本地方法重載時程序員纔有必要使用長名。但若是本地方法的名稱與非本地方法的名稱相同,則不會有問題。由於非本地方法(Java 方法)並不放在本地庫中。

下例中,沒必要用長名來連接本地方法 g,由於另外一個方法 g 不是本地方法,於是它並不在本地庫中。

    class Cls1 {
      int g(int i);
      native int g(double d);
    }

咱們採起簡單的名字攪亂方案,以保證全部的 Unicode 字符都能被轉換爲有效的 C 函數名。咱們用下劃線(「_」) 字符來代替全限定的類名中的斜槓(「/」)。因爲名稱或型描述符歷來不會以數字打,我_0..._9來代替轉義字符序列,如表 2-1所示:

表 2-1 Unicode 字符轉換

轉義字符序列

表示

_0XXXX

Unicode 字符XXXX

_1

字符「_」

_2

簽名中的字符「;」

_3

簽名中的字符「[」

本地方法和接口 API 都要遵照定平臺上的庫調定。例如,UNIX 系使用 C 調定,而 Win32 系使用 __stdcall。

 

本地方法的參數

JNI 接口指針是本地方法的第一個參數。其類型是 JNIEnv。第二個參數隨本地方法是靜態仍是非靜態而有所不一樣。非靜態本地方法的第二個參數是對對象的引用,而靜態本地方法的第二個參數是對其 Java 類的引用。

其他的參數對應於一般 Java 方法的參數。本地方法調用利用返回值將結果傳回調用程序中。第 3 章 「JNI 的類型和數據結構」 將描述 Java 類型和 C 類型之間的映射。

代碼示例 2-1 說明了如何用 C 函數來實現本地方法 f。對本地方法 f 的聲明以下:

    package pkg;
    class Cls {
         native double f(int i, String s);
         ...
    }

具備長 mangled 名稱 Java_pkg_Cls_f_ILjava_lang_String_2 的 C 函數實現本地方法f

代碼示例 2-1: 用 C 實現本地方法

    jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
         JNIEnv *env,        /* 接口指針 */
         jobject obj,        /* 「this」指針 */
         jint i,             /* 第一個參數 */
         jstring s)          /* 第二個參數 */
    {
         /* 取得 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++ 實現本地方法

    extern "C" /* 指定 C 調用約定 */
    jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
         JNIEnv *env,        /* 接口指針 */
         jobject obj,        /* 「this」指針 */
         jint i,             /* 第一個參數 */
         jstring s)          /* 第二個參數  */
    {
         const char *str = env->GetStringUTFChars(s, 0);
         ...
         env->ReleaseStringUTFChars(s, str);
         return ...
    }

使用 C++ 後,源代碼變得更爲直接,且接口指針參數消失。可是,C++ 的內在機制與 C 的徹底同樣。在 C++ 中,JNI 函數被定義爲內聯成員函數,它們將擴展爲相應的 C 對應函數。


引用 Java 對象

基本類型(如整型、字符型等)在 Java 和平臺相關代碼之間直接進行復制。而 Java 對象由引用來傳遞。虛擬機必須跟蹤傳到平臺相關代碼中的對象,以使這些對象不會被垃圾收集器釋放。反之,平臺相關代碼必須能用某種方式通知虛擬機它再也不須要那些對象,同時,垃圾收集器必須可以移走被平臺相關代碼引用過的對象。

 

全局和局部引用

JNI 將平臺相關代碼使用的對象引用分紅兩類:局部引用全局引用。局部引用在本地方法調用期間有效,並在本地方法返回後被自動釋放掉。全局引用將一直有效,直到被顯式釋放。

對象是被做爲局部引用傳遞給本地方法的,由 JNI 函數返回的全部 Java 對象也都是局部引用。JNI 容許程序員從局部引用建立全局引用。要求 Java 對象的 JNI 函數既可接受全局引用也可接受局部引用。本地方法將局部引用或全局引用做爲結果返回。

大多數狀況下,程序員應該依靠虛擬機在本地方法返回後釋放全部局部引用。可是,有時程序員必須顯式釋放某個局部引用。例如,考慮如下的情形:

  • 本地方法要訪問一個大型 Java 對象,因而建立了對該 Java 對象的局部引用。而後,本地方法要在返回調用程序以前執行其它計算。對這個大型 Java 對象的局部引用將防止該對象被看成垃圾收集,即便在剩餘的運算中並再也不須要該對象。
  • 本地方法建立了大量的局部引用,但這些局部引用並非要同時使用。因爲虛擬機須要必定的空間來跟蹤每一個局部引用,建立太多的局部引用將可能使系統耗盡內存。例如,本地方法要在一個大型對象數組中循環,把取回的元素做爲局部引用,並在每次迭代時對一個元素進行操做。每次迭代後,程序員再也不須要對該數組元素的局部引用。

JNI 容許程序員在本地方法內的任何地方對局部引用進行手工刪除。爲確保程序員能夠手工釋放局部引用,JNI 函數將不能建立額外的局部引用,除非是這些 JNI 函數要做爲結果返回的引用。

局部引用僅在建立它們的線程中有效。本地方法不能將局部引用從一個線程傳遞到另外一個線程中。

 

實現局部引用

爲了實現局部引用,Java 虛擬機爲每一個從 Java 到本地方法的控制轉換都建立了註冊服務程序。註冊服務程序將不可移動的局部引用映射爲 Java 對象,並防止這些對象被看成垃圾收集。全部傳給本地方法的 Java 對象(包括那些做爲 JNI 函數調用結果返回的對象)將被自動添加到註冊服務程序中。本地方法返回後,註冊服務程序將被刪除,其中的全部項均可以被看成垃圾來收集。

可用各類不一樣的方法來實現註冊服務程序,例如,使用表、連接列表或 hash 表來實現。雖然引用計數可用來避免註冊服務程序中有重複的項,但 JNI 實現不是必須檢測和消除重複的項。

注意,以保守方式掃描本地堆棧並不能如實地實現局部引用。平臺相關代碼可將局部引用儲存在全局或堆數據結構中。


訪問 Java 對象

JNI 提供了一大批用來訪問全局引用和局部引用的函數。這意味着不管虛擬機在內部如何表示 Java 對象,相同的本地方法實現都能工做。這就是爲何 JNI 可被各類各樣的虛擬機實現所支持的關鍵緣由。

不透明的引用來使用訪問函數的開銷比直接訪問 C 數據構的開銷來得高。我相信,大多數狀況下,Java 程序使用本地方法是了完成一些重要任,此時這種接口的開銷不是首要問題

 

訪問基本類型數組

對於含有大量基本數據類型(如整數數組和字符串)的 Java 對象來講,這種開銷將高得不可接受 (考慮一下用於執行矢量和矩陣運算的本地方法的情形便知)。對 Java 數組進行迭代而且要經過函數調用取回數組的每一個元素,其效率是很是低的。

一個解決辦法是引入「釘住」概念,以使本地方法可以要求虛擬機釘住數組內容。然後,該本地方法將接受指向數值元素的直接指針。可是,這種方法包含如下兩個前提:

  • 垃圾收集器必須支持釘住。
  • 虛擬機必須在內存中連續存放基本類型數組。雖然大多數基本類型數組都是連續存放的,但布爾數組能夠壓縮或不壓縮存儲。所以,依賴於布爾數組確切存儲方式的本地方法將是不可移植的。

咱們將採起折衷方法來克服上述兩個問題。

首先,咱們提供了一套函數,用於在 Java 數組的一部分和本地內存緩衝之間複製基本類型數組元素。這些函數只有在本地方法只需訪問大型數組中的一小部分元素時才使用。

其次,程序員可用另外一套函數來取回數組元素的受約束版本。記住,這些函數可能要求 Java 虛擬機分配存儲空間和進行復制。虛擬機實現將決定這些函數是否真正複製該數組,以下所示:

  • 若是垃圾收集器支持釘住,且數組的佈局符合本地方法的要求,則不須要進行復制。
  • 不然,該數組將被複制到不可移動的內存塊中(例如,複製到 C 堆中),並進行必要的格式轉換,而後返回指向該副本的指針。

最後,接口提供了一些函數,用以通知虛擬機本地方法已再也不須要訪問這些數組元素。當調用這些函數時,系統或者釋放數組,或者在原始數組與其不可移動副本之間進行協調並將副本釋放。

這種處理方法具備靈活性。垃圾收集器的算法可對每一個給定的數組分別做出複製或釘住的決定。例如,垃圾收集器可能複製小型對象而釘住大型對象。

JNI 實現必須確保多個線程中運行的本地方法可同時訪問同一數組。例如,JNI 能夠爲每一個被釘住的數組保留一個內部計數器,以便某個線程不會解開同時被另外一個線程釘住的數組。注意,JNI 沒必要將基本類型數組鎖住以專供某個本地方法訪問。同時從不一樣的線程對 Java 數組進行更新將致使不肯定的結果。

 

訪問域和方法

JNI 容許本地方法訪問 Java 對象的域或調用其方法。JNI 用符號名稱和類型簽名來識別方法和域。從名稱和簽名來定位域或對象的過程可分爲兩步。例如,爲調用類 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 或域 ID 亦變成無效。所以,若是平臺相關代碼要長時間使用某個方法 ID 或域 ID,則它必須確保:

  • 保留對所涉及類的活引用,或
  • 從新計算該方法 ID 或域 ID。

JNI 對域 ID 和方法 ID 的內部實現並不施加任何限制。


報告編程錯誤

JNI 不檢查諸如傳遞 NULL 指針或非法參數類型之類的編程錯誤。非法的參數類型包括諸如要用 Java 類對象時卻用了普通 Java 對象這樣的錯誤。JNI 不檢查這些編程錯誤的理由以下:

  • 強迫 JNI 函數去檢查全部可能的錯誤狀況將下降正常(正確)的本地方法的性能。
  • 在許多狀況下,沒有足夠的運行時的類型信息可供這種檢查使用。

大多數 C 庫函數對編程錯誤不進行防範。例如,printf() 函數在接到一個無效地址時一般是引發運行錯而不是返回錯誤代碼。強迫 C 庫函數檢查全部可能的錯誤狀況將有可能引發這種檢查被重複進行--先是在用戶代碼中進行,而後又在庫函數中再次進行。

程序員不得將非法指針或錯誤類型的參數傳遞給 JNI 函數。不然,可能產生意想不到的後果,包括可能使系統狀態受損或使虛擬機崩潰。


Java 異常

JNI 容許本地方法拋出任何 Java 異常。本地方法也能夠處理突出的 Java 異常。未被處理的 Java 異常將被傳回虛擬機中。

 

異常和錯誤代碼

一些 JNI 函數使用 Java 異常機制來報告錯誤狀況。大多數狀況下,JNI 函數經過返回錯誤代碼拋出 Java 異常來報告錯誤狀況。錯誤代碼一般是特殊的返回值(如 NULL),這種特殊的返回值在正常返回值範圍以外。所以,程序員能夠:

  • 快速檢查上一個 JNI 調用所返回的值以肯定是否出錯,並
  • 經過調用函數 ExceptionOccurred() 來得到異常對象,它含有對錯誤狀況的更詳細說明。

在如下兩種狀況中,程序員須要先查出異常,而後才能檢查錯誤代碼:

  • 調用 Java 方法的 JNI 函數返回該 Java 方法的結果。程序員必須調用 ExceptionOccurred() 以檢查在執行 Java 方法期間可能發生的異常。
  • 某些用於訪問 JNI 數組的函數並不返回錯誤代碼,但可能會拋出 ArrayIndexOutOfBoundsExceptionArrayStoreException

在全部其它狀況下,返回值若是不是錯誤代碼值就可確保沒有拋出異常。

 

異步異常

在多個線程的狀況下,當前線程之外的其它線程可能會拋出異步異常。異步異常並不當即影響當前線程中平臺相關代碼的執行,直到出現下列狀況:

  • 該平臺相關代碼調用某個有可能拋出同步異常的 JNI 函數,或者
  • 該平臺相關代碼用 ExceptionOccurred() 顯式檢查同步異常或異步異常。

注意,只有那些有可能拋出同步異常的 JNI 函數才檢查異步異常。

本地方法應在必要的地方(例如,在一個沒有其它異常檢查的緊密循環中)插入 ExceptionOccurred() 檢查以確保當前線程可在適當時間內對異步異常做出響應。

 

異常的處理

可用兩種方法來處理平臺相關代碼中的異常:

  • 本地方法可選擇當即返回,使異常在啓動該本地方法調用的 Java 代碼中拋出。
  • 平臺相關代碼可經過調用 ExceptionClear() 來清除異常,而後執行本身的異常處理代碼。

拋出了某個異常以後,平臺相關代碼必須先清除異常,而後才能進行其它的 JNI 調用。當有待定異常時,只有如下這些 JNI 函數可被安全地調用:ExceptionOccurred()、ExceptionDescribe()ExceptionClear()ExceptionDescribe() 函數將打印有關待定異常的調試消息。

3 - JNI 型和數據


本章討論 JNI 如何將 Java 類型映射到本地 C 類型。


基本類型

 

表 3-1 描述 Java 基本類型及其與計算機相關的本地等效類型。

表 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   1

jsize 整數類型用於描述主要指數和大小:

    typedef jint jsize;

引用類型

JNI 包含了不少對應於不一樣 Java 對象的引用類型。JNI 引用類型的組織層次如圖 3-1 所示。

    

圖 3-1 引用類型層次

在 C 中,全部其它 JNI 引用類型都被定義爲與 jobject 同樣。例如:

    typedef jobject jclass;

在 C++ 中,JNI 引入了虛構類以增強子類關係。例如:

    class _jobject {};
    class _jclass : public _jobject {};
    ...
    typedef _jobject *jobject;
    typedef _jclass *jclass;

域 ID 和方法 ID

方法 ID 和域 ID 是常規的 C 指針類型:

    struct _jfieldID;              /*不透明結構 */
    typedef struct _jfieldID *jfieldID;   /* 域 ID */
 
    struct _jmethodID;              /* 不透明結構 */
    typedef struct _jmethodID *jmethodID; /* 方法 ID */

值類型

jvalue 聯合類型在參數數組中用做單元類型。其聲明方式以下:

    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

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)J

UTF-8 字符串

JNI 用 UTF-8 字符串來表示各類字符串類型。UTF-8 字符串和 Java 虛擬機所使用的同樣。UTF-8 字符串的編碼方式使得僅包含非空 ASCII 字符的字符序列可以按每字符一個字節表示,可是最多隻能表示 16 位的字符。全部在 \u0001\u007F 範圍內的字符都用單字節表示,以下所示:

 

字節中的七位數據肯定了所表示字符的值。空字符 (\u000) 和 \u0080\u07FF 範圍內的字符用一對字節表示, 即 xy,以下所示:

 

值爲 ((x&0x1f)<<6)+(y&0x3f) 的字符需用兩個字節表示。

\u0800\uFFFF 範圍內的字符用三個字節表示,即 xy,和 z

 

值爲 ((x&0xf)<<12)+(y&0x3f)<<6)+(z&0x3f) 的字符需用三個字節表示。

此格式與「標準」 UTF-8 格式之間有兩個區別。第一,空字節 (byte)0 使用雙字節格式進行編碼,而不是單字節格式。這意味着 Java 虛擬機的 UTF-8 字符串不可能有嵌入的空值。第二,只使用單字節、雙字節和三字節格式。Java 虛擬機不能識別更長的 UTF-8 格式。

 

4 - JNI 函數


本章爲 JNI 函數提供參考信息。其中列出了所有 JNI 函數,同時也給出了 JNI 函數表的準確佈局。

注意:「必須」一詞用於約束 JNI 編程人員。例如,當說明某個 JNI 函數接收非空對象時,就應確保不要向該 JNI 函數傳遞 NULL。這時,JNI 實現將無需在該 JNI 函數中執行 NULL 指針檢查。

本章的部分資料改編自 Netscape 的 JRI 文檔。

該參考資料按用法對函數進行組織。參考部分按下列函數區域進行組織:


接口函數表

每一個函數都可經過 JNIEnv 參數以固定偏移量進行訪問。JNIEnv 的類型是一個指針,指向存儲所有 JNI 函數指針的結構。其定義以下:

注意:前三項留做未來與 COM 兼容。此外,咱們在函數表開頭部分也留出來多個 NULL 項,從而可將未來與類有關的 JNI 操做添加到 FindClass 後面,而非函數表的末尾。

注意,函數表可在全部 JNI 接口指針間共享。

代碼示例 4-1

    const struct JNINativeInterface ... = {
        NULL,
        NULL,
        NULL,
        NULL,
        GetVersion,
    
        DefineClass,
        FindClass,
        NULL,
        NULL,
        NULL,
        GetSuperclass,
        IsAssignableFrom,
        NULL,
    
        Throw,
        ThrowNew,
        ExceptionOccurred,
        ExceptionDescribe,
        ExceptionClear,
        FatalError,
        NULL,
        NULL,
    
        NewGlobalRef,
        DeleteGlobalRef,
        DeleteLocalRef,
        IsSameObject,
        NULL,
        NULL,
    
        AllocObject,
        NewObject,
        NewObjectV,
        NewObjectA,
    
        GetObjectClass,
        IsInstanceOf,
    
        GetMethodID,
    
        CallObjectMethod,
        CallObjectMethodV,
        CallObjectMethodA,
        CallBooleanMethod,
        CallBooleanMethodV,
        CallBooleanMethodA,
        CallByteMethod,
        CallByteMethodV,
        CallByteMethodA,
        CallCharMethod,
        CallCharMethodV,
        CallCharMethodA,
        CallShortMethod,
        CallShortMethodV,
        CallShortMethodA,
        CallIntMethod,
        CallIntMethodV,
        CallIntMethodA,
        CallLongMethod,
        CallLongMethodV,
        CallLongMethodA,
        CallFloatMethod,
        CallFloatMethodV,
        CallFloatMethodA,
        CallDoubleMethod,
        CallDoubleMethodV,
        CallDoubleMethodA,
        CallVoidMethod,
        CallVoidMethodV,
        CallVoidMethodA,
    
        CallNonvirtualObjectMethod,
        CallNonvirtualObjectMethodV,
        CallNonvirtualObjectMethodA,
        CallNonvirtualBooleanMethod,
        CallNonvirtualBooleanMethodV,
        CallNonvirtualBooleanMethodA,
        CallNonvirtualByteMethod,
        CallNonvirtualByteMethodV,
        CallNonvirtualByteMethodA,
        CallNonvirtualCharMethod,
        CallNonvirtualCharMethodV,
        CallNonvirtualCharMethodA,
        CallNonvirtualShortMethod,
        CallNonvirtualShortMethodV,
        CallNonvirtualShortMethodA,
        CallNonvirtualIntMethod,
        CallNonvirtualIntMethodV,
        CallNonvirtualIntMethodA,
        CallNonvirtualLongMethod,
        CallNonvirtualLongMethodV,
        CallNonvirtualLongMethodA,
        CallNonvirtualFloatMethod,
        CallNonvirtualFloatMethodV,
        CallNonvirtualFloatMethodA,
        CallNonvirtualDoubleMethod,
        CallNonvirtualDoubleMethodV,
        CallNonvirtualDoubleMethodA,
        CallNonvirtualVoidMethod,
        CallNonvirtualVoidMethodV,
        CallNonvirtualVoidMethodA,
    
        GetFieldID,
    
        GetObjectField,
        GetBooleanField,
        GetByteField,
        GetCharField,
        GetShortField,
        GetIntField,
        GetLongField,
        GetFloatField,
        GetDoubleField,
        SetObjectField,
        SetBooleanField,
        SetByteField,
        SetCharField,
        SetShortField,
        SetIntField,
        SetLongField,
        SetFloatField,
        SetDoubleField,
    
        GetStaticMethodID,
    
        CallStaticObjectMethod,
        CallStaticObjectMethodV,
        CallStaticObjectMethodA,
        CallStaticBooleanMethod,
        CallStaticBooleanMethodV,
        CallStaticBooleanMethodA,
        CallStaticByteMethod,
        CallStaticByteMethodV,
        CallStaticByteMethodA,
        CallStaticCharMethod,
        CallStaticCharMethodV,
        CallStaticCharMethodA,
        CallStaticShortMethod,
        CallStaticShortMethodV,
        CallStaticShortMethodA,
        CallStaticIntMethod,
        CallStaticIntMethodV,
        CallStaticIntMethodA,
        CallStaticLongMethod,
        CallStaticLongMethodV,
        CallStaticLongMethodA,
        CallStaticFloatMethod,
        CallStaticFloatMethodV,
        CallStaticFloatMethodA,
        CallStaticDoubleMethod,
        CallStaticDoubleMethodV,
        CallStaticDoubleMethodA,
        CallStaticVoidMethod,
        CallStaticVoidMethodV,
        CallStaticVoidMethodA,
    
        GetStaticFieldID,
    
        GetStaticObjectField,
        GetStaticBooleanField,
        GetStaticByteField,
        GetStaticCharField,
        GetStaticShortField,
        GetStaticIntField,
        GetStaticLongField,
        GetStaticFloatField,
        GetStaticDoubleField,
    
        SetStaticObjectField,
        SetStaticBooleanField,
        SetStaticByteField,
        SetStaticCharField,
        SetStaticShortField,
        SetStaticIntField,
        SetStaticLongField,
        SetStaticFloatField,
        SetStaticDoubleField,
    
        NewString,
        GetStringLength,
        GetStringChars,
        ReleaseStringChars,
    
        NewStringUTF,
        GetStringUTFLength,
        GetStringUTFChars,
        ReleaseStringUTFChars,
    
        GetArrayLength,
     
        NewObjectArray,
        GetObjectArrayElement,
        SetObjectArrayElement,
    
        NewBooleanArray,
        NewByteArray,
        NewCharArray,
        NewShortArray,
        NewIntArray,
        NewLongArray,
        NewFloatArray,
        NewDoubleArray,
    
        GetBooleanArrayElements,
        GetByteArrayElements,
        GetCharArrayElements,
        GetShortArrayElements,
        GetIntArrayElements,
        GetLongArrayElements,
        GetFloatArrayElements,
        GetDoubleArrayElements,
    
        ReleaseBooleanArrayElements,
        ReleaseByteArrayElements,
        ReleaseCharArrayElements,
        ReleaseShortArrayElements,
        ReleaseIntArrayElements,
        ReleaseLongArrayElements,
        ReleaseFloatArrayElements,
        ReleaseDoubleArrayElements,
    
        GetBooleanArrayRegion,
        GetByteArrayRegion,
        GetCharArrayRegion,
        GetShortArrayRegion,
        GetIntArrayRegion,
        GetLongArrayRegion,
        GetFloatArrayRegion,
        GetDoubleArrayRegion,
        SetBooleanArrayRegion,
        SetByteArrayRegion,
        SetCharArrayRegion,
        SetShortArrayRegion,
        SetIntArrayRegion,
        SetLongArrayRegion,
        SetFloatArrayRegion,
        SetDoubleArrayRegion,
    
        RegisterNatives,
        UnregisterNatives,
    
        MonitorEnter,
        MonitorExit,
    
        GetJavaVM,
    };

版本信息

 

GetVersion

jint GetVersion(JNIEnv *env);

返回本地方法接口的版本。

參數

env:JNI 接口指針。

返回值:

高 16 位返回主版本號,低 16 位返回次版本號。

在 JDK1.1 中,GetVersion() 返回 0x00010001。


類操做

 

DefineClass

jclass DefineClass(JNIEnv *env, jobject loader,
const jbyte *buf, jsize bufLen);

從原始類數據的緩衝區中加載類。

參數:

env:JNI 接口指針。

loader:分派給所定義的類的類加載器。

buf:包含 .class 文件數據的緩衝區。

bufLen:緩衝區長度。

返回值:

返回 Java 類對象。若是出錯則返回 NULL

拋出:

ClassFormatError:若是類數據指定的類無效。

ClassCircularityError:若是類或接口是自身的超類或超接口。  

OutOfMemoryError:若是系統內存不足。

 

FindClass

jclass FindClass(JNIEnv *env, const char *name);

該函數用於加載本地定義的類。它將搜索由 CLASSPATH 環境變量爲具備指定名稱的類所指定的目錄和 zip 文件。

參數:

env:JNI 接口指針。

name:類全名(即包名後跟類名,之間由「/」分隔)。若是該名稱以「[」(數組簽名字符)打頭,則返回一個數組類。

返回值:

返回類對象全名。若是找不到該類,則返回 NULL

拋出:

ClassFormatError:若是類數據指定的類無效。

ClassCircularityError:若是類或接口是自身的超類或超接口。

NoClassDefFoundError:若是找不到所請求的類或接口的定義。

OutOfMemoryError:若是系統內存不足。

 

GetSuperclass

jclass GetSuperclass(JNIEnv *env, jclass clazz);

若是 clazz 表明類而非類 object,則該函數返回由 clazz 所指定的類的超類。

若是 clazz 指定類 object 或表明某個接口,則該函數返回 NULL

參數:

env:JNI 接口指針。

clazz:Java 類對象。

返回值:

clazz 所表明的類的超類或 NULL

 

IsAssignableFrom

jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1,
jclass clazz2);

肯定 clazz1 的對象是否可安全地強制轉換爲 clazz2

參數:

env:JNI 接口指針。

clazz1:第一個類參數。

clazz2:第二個類參數。

返回值:

下列某個狀況爲真時返回 JNI_TRUE

  • 第一及第二個類參數引用同一個 Java 類。
  • 第一個類是第二個類的子類。
  • 第二個類是第一個類的某個接口。

異常

 

Throw

jint Throw(JNIEnv *env, jthrowable obj);

拋出 java.lang.Throwable 對象。

參數:

env:JNI 接口指針。

objjava.lang.Throwable 對象。

返回值:

成功時返回 0,失敗時返回負數。

拋出:

java.lang.Throwable 對象 obj

 

ThrowNew

jint ThrowNew(JNIEnv *env, jclass clazz,
const char *message);

利用指定類的消息(由 message 指定)構造異常對象並拋出該異常。

參數:

env:JNI 接口指針。

clazzjava.lang.Throwable 的子類。

message:用於構造 java.lang.Throwable 對象的消息。

返回值:

成功時返回 0,失敗時返回負數。

拋出:

新構造的 java.lang.Throwable 對象。

 

ExceptionOccurred

jthrowable ExceptionOccurred(JNIEnv *env);

肯定是否某個異常正被拋出。在平臺相關代碼調用 ExceptionClear() 或 Java 代碼處理該異常前,異常將始終保持拋出狀態。

參數:

env:JNI 接口指針。

返回值:

返回正被拋出的異常對象,若是當前無異常被拋出,則返回 NULL

 

ExceptionDescribe

void ExceptionDescribe(JNIEnv *env);

將異常及堆棧的回溯輸出到系統錯誤報告信道(例如 stderr)。該例程可便利調試操做。

參數:

env:JNI 接口指針。

 

ExceptionClear

void ExceptionClear(JNIEnv *env);

清除當前拋出的任何異常。若是當前無異常,則此例程不產生任何效果。

參數:

env:JNI 接口指針。

 

FatalError

void FatalError(JNIEnv *env, const char *msg);

拋出致命錯誤而且不但願虛擬機進行修復。該函數無返回值。

參數:

env:JNI 接口指針。

msg:錯誤消息。


全局及局部引用

 

NewGlobalRef

jobject NewGlobalRef(JNIEnv *env, jobject obj);

建立 obj 參數所引用對象的新全局引用。obj 參數既能夠是全局引用,也能夠是局部引用。全局引用經過調用 DeleteGlobalRef() 來顯式撤消。

參數:

env:JNI 接口指針。

obj:全局或局部引用。

返回值:

返回全局引用。若是系統內存不足則返回 NULL

 

DeleteGlobalRef

void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

刪除 globalRef 所指向的全局引用。

參數:

env:JNI 接口指針。

globalRef:全局引用。

 

DeleteLocalRef

void DeleteLocalRef(JNIEnv *env, jobject localRef);

刪除 localRef 所指向的局部引用。

參數:

env:JNI 接口指針。

localRef:局部引用。


對象操做

 

AllocObject

jobject AllocObject(JNIEnv *env, jclass clazz);

分配新 Java 對象而不調用該對象的任何構造函數。返回該對象的引用。

clazz 參數務必不要引用數組類。

參數:

env:JNI 接口指針。

clazz:Java 類對象。

返回值:

返回 Java 對象。若是沒法構造該對象,則返回 NULL

拋出:

InstantiationException:若是該類爲一個接口或抽象類。

OutOfMemoryError:若是系統內存不足。

 

NewObject
NewObjectA
NewObjectV

jobject NewObject(JNIEnv *env, jclass clazz,
jmethodID methodID, ...);

jobject NewObjectA(JNIEnv *env, jclass clazz,
jmethodID methodID, jvalue *args);

jobject NewObjectV(JNIEnv *env, jclass clazz,
jmethodID methodID, va_list args);

構造新 Java 對象。方法 ID指示應調用的構造函數方法。該 ID 必須經過調用 GetMethodID() 得到,且調用時的方法名必須爲 <init>,而返回類型必須爲 void (V)。

clazz 參數務必不要引用數組類。

NewObject

編程人員應將傳遞給構造函數的全部參數緊跟着放在 methodID 參數的後面。NewObject() 收到這些參數後,將把它們傳給編程人員所要調用的 Java 方法。

NewObjectA

編程人員應將傳遞給構造函數的全部參數放在 jvalues 類型的數組 args 中,該數組緊跟着放在 methodID 參數的後面。NewObject() 收到數組中的這些參數後,將把它們傳給編程人員所要調用的 Java 方法。

NewObjectV

編程人員應將傳遞給構造函數的全部參數放在 va_list 類型的參數 args 中,該參數緊跟着放在 methodID 參數的後面。NewObject() 收到這些參數後,將把它們傳給編程人員所要調用的 Java 方法。

參數:

env:JNI 接口指針。

clazz:Java 類對象。

methodID:構造函數的方法 ID。

NewObject 的其它參數:

傳給構造函數的參數。

NewObjectA 的其它參數:

args:傳給構造函數的參數數組。

NewObjectV 的其它參數:

args:傳給構造函數的參數 va_list。

返回值:

返回 Java 對象,若是沒法構造該對象,則返回 NULL

拋出:

InstantiationException:若是該類爲接口或抽象類。

OutOfMemoryError:若是系統內存不足。

構造函數拋出的任何異常。

 

GetObjectClass

jclass GetObjectClass(JNIEnv *env, jobject obj);

返回對象的類。

參數:

env:JNI 接口指針。

obj:Java 對象(不能爲 NULL)。

返回值:

返回 Java 類對象。

 

IsInstanceOf

jboolean IsInstanceOf(JNIEnv *env, jobject obj,
jclass clazz);

測試對象是否爲某個類的實例。

參數:

env:JNI 接口指針。

obj:Java 對象。

clazz:Java 類對象。

返回值:

若是可將 obj 強制轉換爲 clazz,則返回 JNI_TRUE。不然返回 JNI_FALSENULL 對象可強制轉換爲任何類。

 

IsSameObject

jboolean IsSameObject(JNIEnv *env, jobject ref1,
jobject ref2);

測試兩個引用是否引用同一 Java 對象。

參數:

env:JNI 接口指針。

ref1:Java 對象。

ref2:Java 對象。

返回值:

若是 ref1ref2 引用同一 Java 對象或均爲 NULL,則返回 JNI_TRUE。不然返回 JNI_FALSE


訪問對象的域

 

GetFieldID

jfieldID GetFieldID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);

返回類的實例(非靜態)域的域 ID。該域由其名稱及簽名指定。訪問器函數的 Get<type>FieldSet<type>Field 系列使用域 ID 檢索對象域。

GetFieldID() 將未初始化的類初始化。

GetFieldID() 不能用於獲取數組的長度域。應使用 GetArrayLength()

參數:

env:JNI 接口指針。

clazz:Java 類對象。

name: 0 終結的 UTF-8 字符串中的域名。

sig:0 終結的 UTF-8 字符串中的域簽名。

返回值:

域 ID。若是操做失敗,則返回 NULL

拋出:

NoSuchFieldError:若是找不到指定的域。

ExceptionInInitializerError:若是因爲異常而致使類初始化程序失敗。

OutOfMemoryError:若是系統內存不足。

 

Get<type>Field 例程

NativeType Get<type>Field(JNIEnv *env, jobject obj,
jfieldID fieldID);

該訪問器例程系列返回對象的實例(非靜態)域的值。要訪問的域由經過調用 GetFieldID() 而獲得的域 ID 指定。

下表說明了 Get<type>Field 例程名及結果類型。應將 Get<type>Field 中的 type 替換爲域的 Java 類型(或使用表中的某個實際例程名),而後將 NativeType 替換爲該例程對應的本地類型。

表 4-1 Get<type>Field 訪問器例程系列

Get<type>Field 例程名

本地

GetObjectField()

jobject

GetBooleanField()

jboolean

GetByteField()

jbyte

GetCharField()

jchar

GetShortField()

jshort

GetIntField()

jint

GetLongField()

jlong

GetFloatField()

jfloat

GetDoubleField()

jdouble

參數:

env:JNI 接口指針。

obj:Java 對象(不能爲 NULL)。

fieldID:有效的域 ID。

返回值:

域的內容。

 

Set<type>Field 例程

void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,
NativeType value);

該訪問器例程系列設置對象的實例(非靜態)域的值。要訪問的域由經過調用 SetFieldID() 而獲得的域 ID 指定。

下表說明了 Set<type>Field 例程名及結果類型。應將 Set<type>Field 中的 type 替換爲域的 Java 類型(或使用表中的某個實際例程名),而後將 NativeType 替換爲該例程對應的本地類型。

表4-2 Set<type>Field 訪問器例程系列

Set<type>Field 例程名

本地

SetObjectField()

jobject

SetBooleanField()

jboolean

SetByteField()

jbyte

SetCharField()

jchar

SetShortField()

jshort

SetIntField()

jint

SetLongField()

jlong

SetFloatField()

jfloat

SetDoubleField()

jdouble

參數:

env:JNI 接口指針。

obj:Java 對象(不能爲 NULL)。

fieldID:有效的域 ID。

value:域的新值。


調用實例方法

 

GetMethodID

jmethodID GetMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);

返回類或接口實例(非靜態)方法的方法 ID。方法可在某個 clazz 的超類中定義,也可從 clazz 繼承。該方法由其名稱和簽名決定。

GetMethodID() 可以使未初始化的類初始化。

要得到構造函數的方法 ID,應將 <init> 做爲方法名,同時將 void (V) 做爲返回類型。

參數:

env:JNI 接口指針。

clazz:Java 類對象。

name:0 終結的 UTF-8 字符串中的方法名。

sig:0 終結的 UTF-8 字符串中的方法簽名。

返回值:

方法 ID,若是找不到指定的方法,則爲 NULL

拋出:

NoSuchMethodError:若是找不到指定方法。

ExceptionInInitializerError:若是因爲異常而致使類初始化程序失敗。

OutOfMemoryError:若是系統內存不足。

 

Call<type>Method 例程
Call<type>MethodA 例程
Call<type>MethodV 例程

NativeType Call<type>Method(JNIEnv *env, jobject obj,
jmethodID methodID, ...);

NativeType Call<type>MethodA(JNIEnv *env, jobject obj,
jmethodID methodID, jvalue *args);

NativeType Call<type>MethodV(JNIEnv *env, jobject obj,
jmethodID methodID, va_list args);

這三個操做的方法用於從本地方法調用 Java 實例方法。它們的差異僅在於向其所調用的方法傳遞參數時所用的機制。

這三個操做將根據所指定的方法 ID 調用 Java 對象的實例(非靜態)方法參數 methodID 必須經過調用 GetMethodID() 來得到。

當這些函數用於調用私有方法和構造函數時,方法 ID 必須從 obj 的真實類派生而來,而不該從其某個超類派生。

Call<type>Method 例程

編程人員應將要傳給方法的全部參數緊跟着放在 methodID 參數以後。Call<type>Method 例程接受這些參數並將其傳給編程人員所要調用的 Java 方法。

Call<type>MethodA 例程

編程人員應將要傳給方法的全部參數放在緊跟在 methodID 參數以後的 jvalues 類型數組 args 中。Call<type>MethodA routine 接受這些數組中的參數並將其傳給編程人員所要調用的 Java 方法。

Call<type>MethodV 例程

編程人員將方法的全部參數放在緊跟着在 methodID 參數以後的 va_list 類型參數變量中。Call<type>MethodV routine 接受這些參數並將其傳給編程人員所要調用的 Java 方法。

下表根據結果類型說明了各個方法調用例程。用戶應將 Call<type>Method 中的 type 替換爲所調用方法的 Java 類型(或使用表中的實際方法調用例程名),同時將 NativeType 替換爲該例程相應的本地類型。

表 4-3 實例方法調用例程

Call<type>Method 例程名

本地

CallVoidMethod() CallVoidMethodA() CallVoidMethodV()

void

CallObjectMethod() CallObjectMethodA() CallObjectMethodV()

jobject

CallBooleanMethod() CallBooleanMethodA() CallBooleanMethodV()

jboolean

CallByteMethod() CallByteMethodA() CallByteMethodV()

jbyte

CallCharMethod() CallCharMethodA() CallCharMethodV()

jchar

CallShortMethod() CallShortMethodA() CallShortMethodV()

jshort

CallIntMethod() CallIntMethodA() CallIntMethodV()

jint

CallLongMethod() CallLongMethodA() CallLongMethodV()

jlong

CallFloatMethod() CallFloatMethodA() CallFloatMethodV()

jfloat

CallDoubleMethod() CallDoubleMethodA() CallDoubleMethodV()

jdouble

參數:

env:JNI 接口指針。

obj:Java 對象。

methodID:方法 ID。

Call<type>Method 例程的其它參數:

要傳給 Java 方法的參數。

Call<type>MethodA 例程的其它參數:

args:參數數組。

Call<type>MethodV 例程的其它參數:

args:參數的 va_list。

返回值:

返回調用 Java 方法的結果。

拋出:

執行 Java 方法時拋出的異常。

 

CallNonvirtual<type>Method 例程
CallNonvirtual<type>MethodA 例程
CallNonvirtual<type>MethodV 例程

NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj,
jclass clazz, jmethodID methodID, ...);

NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj,
jclass clazz, jmethodID methodID, jvalue *args);

NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj,
jclass clazz, jmethodID methodID, va_list args);

這些操做根據指定的類和方法 ID 調用某 Java 對象的實例(非靜態)方法。參數 methodID 必須經過調用 clazz 類的 GetMethodID() 得到。

CallNonvirtual<type>MethodCall<type>Method 例程系列並不相同。Call<type>Method 例程根據對象的類調用方法,而 CallNonvirtual<type>Method 例程則根據得到方法 ID 的(由 clazz 參數指定)類調用方法。方法 ID 必須從對象的真實類或其某個超類得到。

CallNonvirtual<type>Method 例程

編程人員應將要傳給方法的全部參數緊跟着放在 methodID 參數以後。CallNonvirtual<type>Method routine 接受這些參數並將其傳給編程人員所要調用的 Java 方法。

 

CallNonvirtual<type>MethodA 例程

編程人員應將要傳給方法的全部參數放在緊跟在 methodID 參數以後的 jvalues 類型數組 args 中。CallNonvirtual<type>MethodA routine 接受這些數組中的參數並將其傳給編程人員所要調用的 Java 方法。

CallNonvirtual<type>MethodV 例程

編程人員應將要傳給方法的全部參數放在緊跟在 methodID 參數以後的 va_list 類型參數 args 中。CallNonvirtualMethodV routine 接受這些參數並將其傳給編程人員所要調用的 Java 方法。

下表根據結果類型說明了各個方法調用例程。用戶應將 CallNonvirtual<type>Method 中的 type 替換爲所調用方法的 Java 類型(或使用表中的實際方法調用例程名),同時將 NativeType 替換爲該例程相應的本地類型。

表 4-4 CallNonvirtual<type>Method 例程

CallNonvirtual<type>Method 例程名

本地

CallNonvirtualVoidMethod() CallNonvirtualVoidMethodA() CallNonvirtualVoidMethodV()

void

CallNonvirtualObjectMethod() CallNonvirtualObjectMethodA() CallNonvirtualObjectMethodV()

jobject

CallNonvirtualBooleanMethod() CallNonvirtualBooleanMethodA() CallNonvirtualBooleanMethodV()

jboolean

CallNonvirtualByteMethod() CallNonvirtualByteMethodA() CallNonvirtualByteMethodV()

jbyte

CallNonvirtualCharMethod() CallNonvirtualCharMethodA() CallNonvirtualCharMethodV()

jchar

CallNonvirtualShortMethod() CallNonvirtualShortMethodA() CallNonvirtualShortMethodV()

jshort

CallNonvirtualIntMethod() CallNonvirtualIntMethodA() CallNonvirtualIntMethodV()

jint

CallNonvirtualLongMethod() CallNonvirtualLongMethodA() CallNonvirtualLongMethodV()

jlong

CallNonvirtualFloatMethod() CallNonvirtualFloatMethodA() CallNonvirtualFloatMethodV()

jfloat

CallNonvirtualDoubleMethod() CallNonvirtualDoubleMethodA() CallNonvirtualDoubleMethodV()

jdouble

參數:

env:JNI 接口指針。

clazz:Java 類。

obj:  Java 對象。

methodID:方法 ID。

CallNonvirtual<type>Method 例程的其它參數:

要傳給 Java 方法的參數。

CallNonvirtual<type>MethodA 例程的其它參數:

args:參數數組。

CallNonvirtual<type>MethodV 例程的其它參數:

args:參數的 va_list

返回值:

調用 Java 方法的結果。

拋出:

執行 Java 方法時所拋出的異常。


訪問靜態域

 

GetStaticFieldID

jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);

返回類的靜態域的域 ID。域由其名稱和簽名指定。GetStatic<type>FieldSetStatic<type>Field 訪問器函數系列使用域 ID 檢索靜態域。

GetStaticFieldID() 將未初始化的類初始化。

參數:

env:JNI 接口指針。

clazz:Java 類對象。

name:  0 終結的 UTF-8 字符串中的靜態域名。 

sig:0 終結的 UTF-8 字符串中的域簽名。

返回值:

域 ID。若是找不到指定的靜態域,則爲 NULL

拋出:

NoSuchFieldError:若是找不到指定的靜態域。

ExceptionInInitializerError:若是因爲異常而致使類初始化程序失敗。

OutOfMemoryError:若是系統內存不足。

 

GetStatic<type>Field 例程

NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz,
jfieldID fieldID);

該訪問器例程系列返回對象的靜態域的值。要訪問的域由經過調用 GetStaticFieldID() 而獲得的域 ID 指定。

下表說明了 GetStatic<type>Field 例程名及結果類型。應將 GetStatic<type>Field 中的 type 替換爲域的 Java 類型(或使用表中的某個實際例程名),而後將 NativeType 替換爲該例程對應的本地類型。

 

表 4-5 GetStatic<type>Field 訪問器例程系列

GetStatic<type>Field 例程名

本地

GetStaticObjectField()

jobject

GetStaticBooleanField()

jboolean

GetStaticByteField()

jbyte

GetStaticCharField()

jchar

GetStaticShortField()

jshort

GetStaticIntField()

jint

GetStaticLongField()

jlong

GetStaticFloatField()

jfloat

GetStaticDoubleField()

jdouble

參數:

env:JNI 接口指針。

clazz:Java 類對象。

fieldID:靜態域 ID。

返回值:

靜態域的內容。

 

SetStatic<type>Field 例程

void SetStatic<type>Field(JNIEnv *env, jclass clazz,
jfieldID fieldID, NativeType value);

該訪問器例程系列設置對象的靜態域的值。要訪問的域由經過調用 GetStaticFieldID() 而獲得的域 ID 指定。

下表說明了 SetStatic<type>Field 例程名及結果類型。應將 SetStatic<type>Field 中的 type 替換爲域的 Java 類型(或使用表中的某個實際例程名),而後將 NativeType 替換爲該例程對應的本地類型。

表4-6 SetStatic<type>Field 訪問器例程系列

SetStatic<type>Field 例程名

本地

SetStaticObjectField()

jobject

SetStaticBooleanField()

jboolean

SetStaticByteField()

jbyte

SetStaticCharField()

jchar

SetStaticShortField()

jshort

SetStaticIntField()

jint

SetStaticLongField()

jlong

SetStaticFloatField()

jfloat

SetStaticDoubleField()

jdouble

參數:

env:JNI 接口指針。

clazz:Java 類對象。

fieldID:靜態域 ID。

value:域的新值。


調用靜態方法

 

GetStaticMethodID

jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);

返回類的靜態方法的方法 ID。方法由其名稱和簽名指定。

GetStaticMethodID() 將未初始化的類初始化。

參數:

env:JNI 接口指針。

clazz:Java 類對象。

name:0 終結 UTF-8 字符串中的靜態方法名。

sig:0 終結 UTF-8 字符串中的方法簽名。

返回值:

方法 ID,若是操做失敗,則爲 NULL

拋出:

NoSuchMethodError:若是找不到指定的靜態方法。

ExceptionInInitializerError:若是因爲異常而致使類初始化程序失敗。

OutOfMemoryError:若是系統內存不足。

 

CallStatic<type>Method 例程
CallStatic<type>MethodA 例程
CallStatic<type>MethodV 例程

NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz,
jmethodID methodID, ...);

NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz,
jmethodID methodID, jvalue *args);

NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz,
jmethodID methodID, va_list args);

這些操做將根據指定的方法 ID 調用 Java 對象的靜態方法。methodID 參數必須經過調用 GetStaticMethodID() 獲得。

方法 ID 必須從 clazz 派生,而不能從其超類派生。

CallStatic<type>Method 例程

編程人員應將要傳給方法的全部參數緊跟着放在 methodID 參數以後。 CallStatic<type>Method routine 接受這些參數並將其傳給編程人員所要調用的 Java 方法。

 

CallStatic<type>MethodA 例程

編程人員應將要傳給方法的全部參數放在緊跟在 methodID 參數以後的 jvalues 類型數組 args 中。CallStaticMethodA routine 接受這些數組中的參數並將其傳給編程人員所要調用的 Java 方法。

CallStatic<type>MethodV 例程

編程人員應將要傳給方法的全部參數放在緊跟在 methodID 參數以後的 va_list 類型參數 args 中。CallStaticMethodV routine 接受這些參數並將其傳給編程人員所要調用的 Java 方法。

下表根據結果類型說明了各個方法調用例程。用戶應將 CallStatic<type>Method 中的 type 替換爲所調用方法的 Java 類型(或使用表中的實際方法調用例程名),同時將 NativeType 替換爲該例程相應的本地類型。

表 4-7 CallStatic<type>Method 調用例程

CallStatic<type>Method 例程名

本地

CallStaticVoidMethod() CallStaticVoidMethodA() CallStaticVoidMethodV()

void

CallStaticObjectMethod() CallStaticObjectMethodA() CallStaticObjectMethodV()

jobject

CallStaticBooleanMethod() CallStaticBooleanMethodA() CallStaticBooleanMethodV()

jboolean

CallStaticByteMethod() CallStaticByteMethodA() CallStaticByteMethodV()

jbyte

CallStaticCharMethod() CallStaticCharMethodA() CallStaticCharMethodV()

jchar

CallStaticShortMethod() CallStaticShortMethodA() CallStaticShortMethodV()

jshort

CallStaticIntMethod() CallStaticIntMethodA() CallStaticIntMethodV()

jint

CallStaticLongMethod() CallStaticLongMethodA() CallStaticLongMethodV()

jlong

CallStaticFloatMethod() CallStaticFloatMethodA() CallStaticFloatMethodV()

jfloat

CallStaticDoubleMethod() CallStaticDoubleMethodA() CallStaticDoubleMethodV()

jdouble

參數:

env:JNI 接口指針。

clazz:Java 類對象。

methodID:靜態方法 ID。

CallStatic<type>Method 例程的其它參數:

要傳給靜態方法的參數。

CallStatic<type>MethodA 例程的其它參數:

args:參數數組。

CallStatic<type>MethodV 例程的其它參數:

args:參數的 va_list

返回值:

返回調用靜態 Java 方法的結果。

拋出:

執行 Java 方法時拋出的異常。


字符串操做

 

NewString

jstring NewString(JNIEnv *env, const jchar *unicodeChars,
jsize len);

利用 Unicode 字符數組構造新的 java.lang.String 對象。

參數:

env:JNI 接口指針。

unicodeChars:指向 Unicode 字符串的指針。

len:Unicode 字符串的長度。

返回值:

Java 字符串對象。若是沒法構造該字符串,則爲 NULL

拋出:

OutOfMemoryError:若是系統內存不足。

 

GetStringLength

jsize GetStringLength(JNIEnv *env, jstring string);

返回 Java 字符串的長度(Unicode 字符數)。

參數:

env:JNI 接口指針。

string:Java 字符串對象。

返回值:

Java 字符串的長度。

 

GetStringChars

const jchar * GetStringChars(JNIEnv *env, jstring string,
jboolean *isCopy);

返回指向字符串的 Unicode 字符數組的指針。該指針在調用 ReleaseStringchars() 前一直有效。

若是 isCopy 非空,則在複製完成後將 *isCopy 設爲 JNI_TRUE。若是沒有複製,則設爲 JNI_FALSE

參數:

env:JNI 接口指針。

string:Java 字符串對象。

isCopy:指向布爾值的指針。

返回值:

指向 Unicode 字符串的指針,若是操做失敗,則返回 NULL

 

ReleaseStringChars

void ReleaseStringChars(JNIEnv *env, jstring string,
const jchar *chars);

通知虛擬機平臺相關代碼無需再訪問 chars。參數 chars 是一個指針,可經過 GetStringChars()string 得到。

參數:

env:JNI 接口指針。

string:Java 字符串對象。

chars:指向 Unicode 字符串的指針。

 

NewStringUTF

jstring NewStringUTF(JNIEnv *env, const char *bytes);

利用 UTF-8 字符數組構造新 java.lang.String 對象。

參數:

env:JNI 接口指針。若是沒法構造該字符串,則爲 NULL

bytes:指向 UTF-8 字符串的指針。

返回值:

Java 字符串對象。若是沒法構造該字符串,則爲 NULL

拋出:

OutOfMemoryError:若是系統內存不足。

 

GetStringUTFLength

jsize GetStringUTFLength(JNIEnv *env, jstring string);

以字節爲單位返回字符串的 UTF-8 長度。

參數:

env:JNI 接口指針。

string:Java 字符串對象。

返回值:

返回字符串的 UTF-8 長度。

 

GetStringUTFChars

const char* GetStringUTFChars(JNIEnv *env, jstring string,
jboolean *isCopy);

返回指向字符串的 UTF-8 字符數組的指針。該數組在被 ReleaseStringUTFChars() 釋放前將一直有效。

若是 isCopy 不是 NULL*isCopy 在複製完成後即被設爲 JNI_TRUE。若是未複製,則設爲 JNI_FALSE

參數:

env:JNI 接口指針。

string:Java 字符串對象。

isCopy:指向布爾值的指針。

返回值:

指向 UTF-8 字符串的指針。若是操做失敗,則爲 NULL

 

ReleaseStringUTFChars

void ReleaseStringUTFChars(JNIEnv *env, jstring string,
const char *utf);

通知虛擬機平臺相關代碼無需再訪問 utfutf 參數是一個指針,可利用 GetStringUTFChars()string 得到。

參數:

env:JNI 接口指針。

string:Java 字符串對象。

utf:指向 UTF-8 字符串的指針。


數組操做

 

GetArrayLength

jsize GetArrayLength(JNIEnv *env, jarray array);

返回數組中的元素數。

參數:

env:JNI 接口指針。

array:Java 數組對象。

返回值:

數組的長度。

 

NewObjectArray

jarray NewObjectArray(JNIEnv *env, jsize length,
jclass elementClass, jobject initialElement);

構造新的數組,它將保存類 elementClass 中的對象。全部元素初始值均設爲 initialElement

參數:

env:JNI 接口指針。

length:數組大小。

elementClass:數組元素類。

initialElement:初始值。

返回值:

Java 數組對象。若是沒法構造數組,則爲 NULL

拋出:

OutOfMemoryError:若是系統內存不足。

 

GetObjectArrayElement

jobject GetObjectArrayElement(JNIEnv *env,
jobjectArray array, jsize index);

返回 Object 數組的元素。

參數:

env:JNI 接口指針。

array:Java 數組。

index:數組下標。

返回值:

Java 對象。

拋出:

ArrayIndexOutOfBoundsException:若是 index 不是數組中的有效下標。

 

SetObjectArrayElement

void SetObjectArrayElement(JNIEnv *env, jobjectArray array,
jsize index, jobject value);

設置 Object 數組的元素。

參數:

env:JNI 接口指針。

array:Java 數組。

index:數組下標。

value:新值。

拋出:

ArrayIndexOutOfBoundsException:若是 index 不是數組中的有效下標。

ArrayStoreException:若是 value 的類不是數組元素類的子類。

 

New<PrimitiveType>Array 例程

ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);

用於構造新基本類型數組對象的一系列操做。表 4-8 說明了特定的基本類型數組構造函數。用戶應把 New<PrimitiveType>Array 替換爲某個實際的基本類型數組構造函數例程名(見下表),而後將 ArrayType 替換爲該例程相應的數組類型。

表 4-8 New<PrimitiveType>Array 數組構造函數系列

New<PrimitiveType>Array 例程

組類

NewBooleanArray()

jbooleanArray

NewByteArray()

jbyteArray

NewCharArray()

jcharArray

NewShortArray()

jshortArray

NewIntArray()

jintArray

NewLongArray()

jlongArray

NewFloatArray()

jfloatArray

NewDoubleArray()

jdoubleArray

參數:

env:JNI 接口指針。

length:數組長度。

返回值:

Java 數組。若是沒法構造該數組,則爲 NULL

 

Get<PrimitiveType>ArrayElements 例程

NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env,
ArrayType array, jboolean *isCopy);

一組返回基本類型數組體的函數。結果在調用相應的 Release<PrimitiveType>ArrayElements() 函數前將一直有效。因爲返回的數多是 Java 的副本,所以返回數的更改沒必要在基本型數中反映出來,直到調用了 Release<PrimitiveType>ArrayElements()

若是 isCopy 不是 NULL*isCopy 在複製完成後即被設爲 JNI_TRUE。若是未複製,則設爲 JNI_FALSE

下表說明了特定的基本類型數組元素訪問器。應進行下列替換;

  • Get<PrimitiveType>ArrayElements 替換爲表中某個實際的基本類型元素訪問器例程名。
  • ArrayType 替換爲對應的數組類型。
  • NativeType 替換爲該例程對應的本地類型。

無論布爾數組在 Java 虛擬機中如何表示,GetBooleanArrayElements() 將始終返回一個 jbooleans 類型的指針,其中每一字節表明一個元素(開包表示)。內存中將確保全部其它類型的數組爲連續的。

表4-9 Get<PrimitiveType>ArrayElements 訪問器例程系列

Get<PrimitiveType>ArrayElements 例程

組類

本地

GetBooleanArrayElements()

jbooleanArray

jboolean

GetByteArrayElements()

jbyteArray

jbyte

GetCharArrayElements()

jcharArray

jchar

GetShortArrayElements()

jshortArray

jshort

GetIntArrayElements()

jintArray

jint

GetLongArrayElements()

jlongArray

jlong

GetFloatArrayElements()

jfloatArray

jfloat

GetDoubleArrayElements()

jdoubleArray

jdouble

參數:

env:JNI 接口指針。

array:Java 字符串對象。

isCopy:指向布爾值的指針。

返回值:

返回指向數組元素的指針,若是操做失敗,則爲 NULL

 

Release<PrimitiveType>ArrayElements 例程

void Release<PrimitiveType>ArrayElements(JNIEnv *env,
ArrayType array, NativeType *elems, jint mode);

通知虛擬機平臺相關代碼無需再訪問 elems 的一組函數。elems 參數是一個經過使用對應的 Get<PrimitiveType>ArrayElements() 函數由 array 導出的指針。必要時,該函數將把對 elems 的修改複製回基本類型數組。

mode 參數將提供有關如何釋放數組緩衝區的信息。若是 elems 不是 array 中數組元素的副本,mode 將無效。不然,mode 將具備下表所述的功能:

表 4-10 基本類型數組釋放模式

模式

0

複製回內容並釋放 elems 緩衝區

JNI_COMMIT

複製回內容但不釋放 elems 緩衝區

JNI_ABORT

釋放緩衝區但不復制回變化

多數狀況下,編程人員將把「0」傳給 mode 參數以確保固定的數組和複製的數組保持一致。其它選項可使編程人員進一步控制內存管理,但使用時務必慎重。

下表說明了構成基本類型數組撤消程序系列的特定例程。應進行以下替換;

  • Release<PrimitiveType>ArrayElements 替換爲表 4-11 中的某個實際基本類型數組撤消程序例程名。 
  • ArrayType 替換爲對應的數組類型。
  • NativeType 替換爲該例程對應的本地類型。

表 4-11 Release<PrimitiveType>ArrayElements 數組例程系列

Release<PrimitiveType>ArrayElements 例程

組類

本地

ReleaseBooleanArrayElements()

jbooleanArray

jboolean

ReleaseByteArrayElements()

jbyteArray

jbyte

ReleaseCharArrayElements()

jcharArray

jchar

ReleaseShortArrayElements()

jshortArray

jshort

ReleaseIntArrayElements()

jintArray

jint

ReleaseLongArrayElements()

jlongArray

jlong

ReleaseFloatArrayElements()

jfloatArray

jfloat

ReleaseDoubleArrayElements()

jdoubleArray

jdouble

參數:

env:JNI 接口指針。

array:Java 數組對象。

elems:指向數組元素的指針。

mode:釋放模式。

 

Get<PrimitiveType>ArrayRegion 例程

void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
jsize start, jsize len, NativeType *buf);

將基本類型數組某一區域複製到緩衝區中的一組函數。

下表說明了特定的基本類型數組元素訪問器。應進行以下替換:

  • Get<PrimitiveType>ArrayRegion 替換爲表 4-12 中的某個實際基本類型元素訪問器例程名。
  • ArrayType 替換爲對應的數組類型。
  • NativeType 替換爲該例程對應的本地類型。

表 4-12 Get<PrimitiveType>ArrayRegion 數組訪問器例程系列

Get<PrimitiveType>ArrayRegion 例程

組類

本地

GetBooleanArrayRegion()

jbooleanArray

jboolean

GetByteArrayRegion()

jbyteArray

jbyte

GetCharArrayRegion()

jcharArray

jchar

GetShortArrayRegion()

jshortArray

jhort

GetIntArrayRegion()

jintArray

jint

GetLongArrayRegion()

jlongArray

jlong

GetFloatArrayRegion()

jfloatArray

jloat

GetDoubleArrayRegion()

jdoubleArray

jdouble

參數:

env:JNI 接口指針。

array:Java 指針。

start:起始下標。

len:要複製的元素數。

buf:目的緩衝區。

拋出:

ArrayIndexOutOfBoundsException:若是區域中的某個下標無效。

 

Set<PrimitiveType>ArrayRegion 例程

void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
jsize start, jsize len, NativeType *buf);

將基本類型數組的某一區域從緩衝區中複製回來的一組函數。

下表說明了特定的基本類型數組元素訪問器。應進行以下替換:

  • Set<PrimitiveType>ArrayRegion 替換爲表中的實際基本類型元素訪問器例程名。
  • ArrayType 替換爲對應的數組類型。
  • NativeType 替換爲該例程對應的本地類型。

表 4-13 Set<PrimitiveType>ArrayRegion 數組訪問器例程系列

Set<PrimitiveType>ArrayRegion 例程

組類

本地

SetBooleanArrayRegion()

jbooleanArray

jboolean

SetByteArrayRegion()

jbyteArray

jbyte

SetCharArrayRegion()

jcharArray

jchar

SetShortArrayRegion()

jshortArray

jshort

SetIntArrayRegion()

jintArray

jint

SetLongArrayRegion()

jlongArray

jlong

SetFloatArrayRegion()

jfloatArray

jfloat

SetDoubleArrayRegion()

jdoubleArray

jdouble

參數:

env:JNI 接口指針。

array:  Java 數組。

start:起始下標。

len:要複製的元素數。

buf:源緩衝區。

拋出:

ArrayIndexOutOfBoundsException:若是區域中的某個下標無效。


註冊本地方法

 

RegisterNatives

jint RegisterNatives(JNIEnv *env, jclass clazz,
const JNINativeMethod *methods, jint nMethods);

clazz 參數指定的類註冊本地方法。methods 參數將指定 JNINativeMethod 結構的數組,其中包含本地方法的名稱、簽名和函數指針。nMethods 參數將指定數組中的本地方法數。JNINativeMethod 結構定義以下所示:

    typedef struct {
        char *name;
        char *signature;
        void *fnPtr;
    } JNINativeMethod;

函數指針一般必須有下列簽名:

    ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);

參數:

env:JNI 接口指針。

clazz:Java 類對象。

methods:類中的本地方法。

nMethods:類中的本地方法數。

返回值:

成功時返回 "0";失敗時返回負數。

拋出:

NoSuchMethodError:若是找不到指定的方法或方法不是本地方法。

 

UnregisterNatives

jint UnregisterNatives(JNIEnv *env, jclass clazz);

取消註冊類的本地方法。類將返回到連接或註冊了本地方法函數前的狀態。

該函數不該在常規平臺相關代碼中使用。相反,它能夠爲某些程序提供一種從新加載和從新連接本地庫的途徑。

參數:

env:JNI 接口指針。

clazz:Java 類對象。

返回值:

成功時返回「0」;失敗時返回負數。


監視程序操做

 

MonitorEnter

jint MonitorEnter(JNIEnv *env, jobject obj);

進入與 obj 所引用的基本 Java 對象相關聯的監視程序。

每一個 Java 對象都有一個相關聯的監視程序。若是當前線程已經擁有與 obj 相關聯的監視程序,它將使指示該線程進入監視程序次數的監視程序計數器增 1。若是與 obj 相關聯的監視程序並不是由某個線程所擁有,則當前線程將變爲該監視程序的全部者,同時將該監視程序的計數器設置爲 1。若是另外一個線程已擁有與 obj 關聯的監視程序,則在監視程序被釋放前當前線程將處於等待狀態。監視程序被釋放後,當前線程將嘗試從新得到全部權。

參數:

env:JNI 接口指針。

obj:常規 Java 對象或類對象。

返回值:

成功時返回「0」;失敗時返回負數。

 

MonitorExit

jint MonitorExit(JNIEnv *env, jobject obj);

當前線程必須是與 obj 所引用的基本 Java 對象相關聯的監視程序的全部者。線程將使指示進入監視程序次數的計數器減 1。若是計數器的值變爲 0,當前線程釋放監視程序。

參數:

env:JNI 接口指針。

obj:常規 Java 對象或類對象。

返回值:

成功時返回「0」;失敗時返回負數。


Java 虛擬機接口

 

GetJavaVM

jint GetJavaVM(JNIEnv *env, JavaVM **vm);

返回與當前線程相關聯的 Java 虛擬機接口(用於調用 API 中)。結果將放在第二個參數 vm 所指向的位置。

參數:

env:JNI 接口指針。

vm:指向放置結果的位置的指針。

返回值:

成功時返回「0」;失敗時返回負數。


 

5 - 調 API


調用 API 容許軟件廠商將 Java 虛擬機加載到任意的本地程序中。廠商能夠交付支持 Java 的應用程序,而沒必要連接 Java 虛擬機源代碼。

本章首先概述了調用 API。而後是全部調用 API 函數的引用頁。

若要加強 Java 虛擬機的嵌入性,能夠用幾種方式來擴展 JDK 1.1.2 中的調用 API。


概述

如下代碼示例說明了如何使用調用 API 中的函數。在本例中,C++ 代碼建立 Java 虛擬機而且調用名爲 Main.test 的靜態方法。爲清楚起見,咱們略去了錯誤檢查。

        #include <jni.h>       /* 其中定義了全部的事項 */
 
        ...
 
        JavaVM *jvm;       /* 表示 Java 虛擬機*/
        JNIEnv *env;       /* 指向本地方法接口的指針 */
 
        JDK1_1InitArgs vm_args; /* JDK 1.1 虛擬機初始化參數 */
 
        vm_args.version = 0x00010001; /* 1.1.2 中新增的:虛擬機版本 */
        /* 得到缺省的初始化參數而且設置類
         * 路徑 */
        JNI_GetDefaultJavaVMInitArgs(&vm_args);
        vm_args.classpath = ...;
 
        /* 加載並初始化 Java 虛擬機,返回 env 中的
         * JNI 接口指針 */
        JNI_CreateJavaVM(&jvm, &env, &vm_args);
 
        /* 用 JNI 調用 Main.test 方法 */
        jclass cls = env->FindClass("Main");
        jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
        env->CallStaticVoidMethod(cls, mid, 100);
 
        /* 結束。*/
        jvm->DestroyJavaVM();

本例使用了 API 中的三個函數。調用 API 容許本地應用程序用 JNI 接口指針來訪問虛擬機特性。其設計相似於 Netscape 的 JRI 嵌入式接口。

 

建立虛擬機

JNI_CreateJavaVM() 函數加載並初始化 Java 虛擬機,而後將指針返回到 JNI 接口指針。調用 JNI_CreateJavaVM() 的線程被看做

 

鏈接虛擬機

JNI 接口指針 (JNIEnv) 僅在當前線程中有效。若是另外一個線程須要訪問 Java 虛擬機,則該線程首先必須調用 AttachCurrentThread() 以將自身鏈接到虛擬機而且得到 JNI 接口指針。鏈接到虛擬機以後,本地線程的工做方式就與在本地方法內運行的普通 Java 線程同樣了。本地線程保持與虛擬機的鏈接,直到調用 DetachCurrentThread() 時才斷開鏈接。

 

卸載虛擬機

主線程不能本身斷開與虛擬機的鏈接。而是必須調用 DestroyJavaVM() 來卸載整個虛擬機。

虛擬機等到主線程成爲惟一的用戶線程時才真正地卸載。用戶線程包括 Java 線程和附加的本地線程。之因此存在這種限制是由於 Java 線程或附加的本地線程可能正佔用着系統資源,例如鎖,窗口等。虛擬機不能自動釋放這些資源。卸載虛擬機時,經過將主線程限制爲惟一的運行線程,使釋聽任意線程所佔用系統資源的負擔落到程序員身上。


初始化結構

不一樣的 Java 虛擬機實現可能會須要不一樣的初始化參數。很難提出適合於全部現有和未來的 Java 虛擬機的標準初始化結構。做爲一種折衷方式,咱們保留了第一個域 (version) 來識別初始化結構的內容。嵌入到 JDK 1.1.2 中的本地應用程序必須將版本域設置爲 0x00010001。儘管其它實現可能會忽略某些由 JDK 所支持的初始化參數,咱們仍然鼓勵虛擬機實現使用與 JDK 同樣的初始化結構。

0x800000000xFFFFFFFF 之間的版本號需保留,而且不爲任何虛擬機實現所識別。

如下代碼顯示了初始化 JDK 1.1.2 中的 Java 虛擬機所用的結構。

    typedef struct JavaVMInitArgs {
       /* 前兩個域在 JDK 1.1 中保留,並
          在 JDK 1.1.2 中正式引入。*/
       /* Java 虛擬機版本 */
        jint version;
 
       /* 系統屬性。*/
        char **properties;
 
       /* 是否檢查 Java 源文件與已編譯的類文件
        *之間的新舊關係。*/
        jint checkSource;
 
       /* Java 建立的線程的最大本地堆棧大小。*/
        jint nativeStackSize;
 
       /* 最大 Java 堆棧大小。*/
        jint javaStackSize;
 
       /* 初始堆大小。*/
        jint minHeapSize;
 
       /* 最大堆大小。*/
        jint maxHeapSize;
 
       /* 控制是否校驗 Java 字節碼:
        * 0 無,1 遠程加載的代碼,2 全部代碼。*/
        jint verifyMode;
 
       /* 類加載的本地目錄路徑。*/
        const char *classpath;
 
       /* 重定向全部虛擬機消息的函數的鉤子。*/
        jint (*vfprintf)(FILE *fp, const char *format,
                         va_list args);
 
       /* 虛擬機退出鉤子。*/
        void (*exit)(jint code);
 
       /* 虛擬機放棄鉤子。*/
        void (*abort)();
 
       /* 是否啓用類 GC。*/
        jint enableClassGC;
 
       /* GC 消息是否出現。*/
        jint enableVerboseGC;
 
       /* 是否容許異步 GC。*/
        jint disableAsyncGC;
 
       /* 三個保留的域。*/
        jint reserved0;
        jint reserved1;
        jint reserved2;
    } JDK1_1InitArgs;

在 JDK 1.1.2 中,初始化結構提供了鉤子,這樣在虛擬機終止時,本地應用程序能夠重定向虛擬機消息並得到控制權。

當本地線程與 JDK 1.1.2 中的 Java 虛擬機鏈接時,如下結構將做爲參數進行傳遞。實際上,本地線程與 JDK 1.1.2 鏈接時不須要任何參數。JDK1_1AttachArgs 結構僅由 C 編譯器的填充槽組成,而 C 編譯器不容許空結構。

    typedef struct JDK1_1AttachArgs {
       /*
        * JDK 1.1 不須要任何參數來附加
        * 本地線程。此處填充的做用是爲了知足不容許空結構的 C 
        * 編譯器的要求。
        */
        void *__padding;
    } JDK1_1AttachArgs;

調用 API 函數

JavaVM 類型是指向調用 API 函數表的指針。如下代碼示例顯示了這種函數表。

    typedef const struct JNIInvokeInterface *JavaVM;
 
    const struct JNIInvokeInterface ... = {
        NULL,
        NULL,
        NULL,
 
        DestroyJavaVM,
        AttachCurrentThread,
        DetachCurrentThread,
    };

注意,JNI_GetDefaultJavaVMInitArgs()、JNI_GetCreatedJavaVMs() 和 JNI_CreateJavaVM() 這三個調用 API 函數不是 JavaVM 函數表的一部分。沒必要先有 JavaVM 結構,就可使用這些函數。

 

JNI_GetDefaultJavaVMInitArgs

jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);

返回 Java 虛擬機的缺省配置。在調用該函數以前,平臺相關代碼必須將 vm_args->version 域設置爲它所指望虛擬機支持的 JNI 版本。在 JDK 1.1.2 中,必須將 vm_args->version 設置爲 0x00010001。(JDK 1.1 不要求平臺相關代碼設置版本域。爲了向後兼容性,若是沒有設置版本域,則 JDK 1.1.2 假定所請求的版本爲 0x00010001。JDK 的將來版本將要求把版本域設置爲適當的值。) 該函數返回後,將把 vm_args->version 設置爲虛擬機支持的實際 JNI 版本。

參數:

vm_args:指向 VM-specific initialization(特定於虛擬機的初始化)結構的指針,缺省參數填入該結構。

返回值:

若是所請求的版本獲得支持,則返回「0」;若是所請求的版本未獲得支持,則返回負數。

 

JNI_GetCreatedJavaVMs

jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen,
jsize *nVMs);

返回全部已建立的 Java 虛擬機。將指向虛擬機的指針依據其建立順序寫入 vmBuf 緩衝區。最多寫入 bufLen 項。在 *nVMs 中返回所建立虛擬機的總數。

JDK 1.1 不支持在單個進程中建立多個虛擬機。

參數:

vmBuf:指向將放置虛擬機結構的緩衝區的指針。

bufLen:緩衝區的長度。

nVMs:指向整數的指針。

返回值:

成功時返回「0」;失敗則返回負數。

 

JNI_CreateJavaVM

jint JNI_CreateJavaVM(JavaVM **p_vm, JNIEnv **p_env,
void *vm_args);

加載並初始化 Java 虛擬機。當前線程成爲主線程。將 env 參數設置爲主線程的 JNI 接口指針。

JDK 1.1.2 不支持在單個進程中建立多個虛擬機。必須將 vm_args 中的版本域設置爲 0x00010001

參數:

p_vm:指向位置(其中放置所獲得的虛擬機結構)的指針。

p_env:指向位置(其中放置主線程的 JNI 接口指針)的指針。

vm_args: Java 虛擬機初始化參數。

返回值:

成功時返回「0」;失敗則返回負數。

 

DestroyJavaVM

jint DestroyJavaVM(JavaVM *vm);

卸載 Java 虛擬機並回收資源。只有主線程可以卸載虛擬機。調用 DestroyJavaVM() 時,主線程必須是惟一的剩餘用戶線程。

參數:

vm:將銷燬的 Java 虛擬機。

返回值:

成功時返回「0」;失敗則返回負數。

JDK 1.1.2 不支持卸載虛擬機。

 

AttachCurrentThread

jint AttachCurrentThread(JavaVM *vm, JNIEnv **p_env,
void *thr_args);

將當前線程鏈接到 Java 虛擬機。在 JNIEnv 參數中返回 JNI 接口指針。

試圖鏈接已經鏈接的線程將不執行任何操做。

本地線程不能同時鏈接到兩個 Java 虛擬機上。

參數:

vm:當前線程所要鏈接到的虛擬機。

p_env:指向位置(其中放置當前線程的 JNI 接口指針)的指針。

thr_args:特定於虛擬機的線程鏈接參數。

返回值:

成功時返回「0」;失敗則返回負數。

 

DetachCurrentThread

jint DetachCurrentThread(JavaVM *vm);

斷開當前線程與 Java 虛擬機之間的鏈接。釋放該線程佔用的全部 Java 監視程序。通知全部等待該線程終止的 Java 線程。

主線程(即建立 Java 虛擬機的線程)不能斷開與虛擬機之間的鏈接。做爲替代,主線程必須調用 JNI_DestroyJavaVM() 來卸載整個虛擬機。

參數:

vm:當前線程將斷開鏈接的虛擬機。

返回值:

成功時返回「0」;失敗則返回負數。

相關文章
相關標籤/搜索