JNI 接口規范


1. 簡介

Java 本地接口概述

背景

JDK 1.0 本地方法接口

Java 運行時接口

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

 

目標

Java 本地接口方法

利用 JNI 編程

JDK 1.1.2 中的變化

 

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”;失敗則返回負數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM