Skip to content
jni_cover

离上一段实习结束也有段时间了,在这段实习中最大的收获就是学会了JNI。在这里翻译官网的java8版本的JNI手册,记录一下~

🍒官网先行

🍕JNI技术使用篇

简介

本章介绍 Java 本地接口(Java Native InterfaceJNI)。JNI 是本地编程接口。它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言(如 CC++ 和汇编语言)编写的应用程序和库进行互操作。

JNI 最重要的好处是它没有对底层 Java 虚拟机的实现施加任何限制。因此,Java 虚拟机厂商可以在不影响虚拟机其它部分的情况下添加对 JNI 的支持。程序员只需编写一种版本的本地应用程序或库,就能够与所有支持 JNIJava 虚拟机协同工作。

本章论及以下主题:

Java本地接口概述

尽管可以完全用 Java 编写应用程序,但是有时单独用 Java 不能满足应用程序的需要。程序员使用 JNI 来编写 Java 本地方法,可以处理那些不能完全用Java 编写应用程序的情况。

以下示例说明了何时需要使用 Java 本地方法:

  • 标准 Java 类库不支持与平台相关的应用程序所需的功能。
  • 已经拥有了一个用另一种语言编写的库,而又希望通过 JNI 使 Java 代码能够访问该库。
  • 想用低级语言(如汇编语言)实现一小段有时间要求代码。

通过用 JNI 编程,可以将本地方法用于:

  • 创建、检查及更新 Java 对象(包括数组和字符串)。
  • 调用 Java 方法。
  • 捕获和抛出异常。
  • 加载类和获得类信息。
  • 执行运行时类型检查。

也可以与调用 API 一起使用 JNI,以允许任意本地应用程序嵌入到 Java 虚拟机中。这样使得程序员能够轻易地让已有应用程序支持 Java,而不必与虚拟机源代码相链接。

历史背景

不同厂商的虚拟机提供了不同的本地方法接口。这些不同的接口使程序员不得不在给定平台上编写、维护和分发多种版本的本地方法库。

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

  • JDK 1.0 本地方法接口
  • NetscapeJava 运行时接口
  • Microsoft 的原始本地接口和 Java/COM 接口

JDK1.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 函数来与垃圾收集器进行显式的交互,而不是依赖于保守的垃圾收集。

在高层级上,MicrosoftJava/COM 接口为 Java 虚拟机提供了与语言无关的标准二进制接口。Java 代码可以像使用 Java 对象一样来使用 COM 对象。Java 类也可以作为 COM 类显示给系统的其余部分。

目标

我们认为统一的,经过细致考虑的标准接口能够向每个用户提供以下好处:

  • 每个虚拟机厂商都可以支持更多的平台相关代码。

  • 工具制造商不必维护不同的本地方法接口。

  • 应用程序设计人员可以只编写一种版本的平台相关代码就能够在不同的虚拟机上运行。

获得标准本地方法接口的最佳途径是联合所有对Java虚拟机有兴趣的当事方。因此,我们在Java获得许可方之间组织了一系列研讨会,对设计统一的本地方法接口进行了讨论。从研讨会可以明确地看出标准本地方法接口必须满足以下要求:

  • 二进制兼容性 - 主要的目标是在给定平台上的所有Java虚拟机实现之间实现本地方法库的二进制兼容性。对于给定平台,程序员只需要维护一种版本的本地方法库。

  • 效率 - 若要支持时限代码,本地方法接口必须增加一点系统开销。所有已知的用于确保虚拟机无关性(因而具有二进制兼容性)的技术都会占用一定的系统开销。我们必须以某种方式在效率和VM独立性之间达成妥协。

  • 功能 - 接口必须显示足够的Java虚拟机内部情况以使本地方法能够完成有用的任务。

Java本地接口方法

我们希望采用一种已有的方法作为标准接口,因为这样程序员(程序员不得不学习在不同虚拟机中的多种接口)的工作负担最轻。遗憾的是,已有解决方案中没有任何方案能够完全地满足我们的目标。

NetscapeJRI 最接近于我们所设想的可移植本地方法接口,因而我们采用它作为设计起点。熟悉 JRI 的读者将会注意到在 API 命名规则、方法和域 ID 的使用、局部和全局引用的使用等方面的相似点。虽然我们进行了最大的努力, 但是 JNI 并不具有对 JRI 的二进制兼容性,不过虚拟机既可以支持 JRI,又可以支持 JNI

MicrosoftRNI 是对 JDK 1.0 的改进,因为它可以解决使用非保守的垃圾收集器的本地方法的问题。然而,RNI 不适合用作与虚拟机无关的本地方法接口。与 JDK 类似,RNI 本地方法将 Java 对象作为 C 结构来访问。这将导致两个问题:

  • RNI 将内部 Java 对象的布局暴露给了平台相关代码。

  • Java 对象作为 C 结构直接进行访问使得不可能有效地加入"写屏障",写屏障是高级的垃圾收集算法所必需的。

作为二进制标准,COM 确保了不同虚拟机之间的完全二进制兼容性。调用 COM 方法只要求间接调用,而这占用很少的系统开销。另外,COM 对象在解决动态链接库版本问题方面也有很大的改进。

然而,有几个因素阻碍了将 COM 用作标准 Java 本地方法接口:

  • 第一,Java/COM 接口缺少某些必需功能,例如访问私有域和抛出普通异常。

  • 第二,Java/COM 接口自动为 Java 对象提供标准的 IUnknownIDispatch 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 虚拟机上运行的最好保证。

如果您正在实现 Java 虚拟机,那么您应该实现 JNIJNI已经经过时间的考验,并确保不对您的VM实施施加任何开销或限制,包括对象表示,垃圾收集方案等。 如果您遇到我们可能忽略的任何问题,请将您的反馈发送给我们。

变化

Java SE 6.0开始,删除了过时的结构体JDK1_1InitArgsJDK1_1AttachArgs,而是使用JavaVMInitArgsJavaVMAttachArgs

设计概述

本章着重讨论 JNI 中的主要设计问题,其中的大部分问题都与本地方法有关。调用 API 的设计将在章节[调用 API](#调用 API)中讨论。

本章包含以下的话题:

JNI接口函数和指针

平台相关代码是通过调用 JNI 函数来访问 Java 虚拟机功能的。JNI 函数可通过接口指针来获得。接口指针是指针的指针,它指向一个指针数组,而指针数组中的每个元素又指向一个接口函数。每个接口函数都处在数组的一个预定偏移量中。下图说明了接口指针的组织结构。

designa

JNI 接口的组织类似于 C++ 虚拟函数表或 COM 接口。使用接口表而不使用硬性编入的函数表的好处是使 JNI 名字空间与平台相关代码分开。虚拟机可以很容易地提供多个版本的 JNI 函数表。例如,虚拟机可支持以下两个 JNI 函数表:

  • 一个表对非法参数进行全面检查,适用于调试程序;

  • 另一个表只进行 JNI 规范所要求的最小程度的检查,因此效率较高。

JNI 接口指针只在当前线程中有效。因此,本地方法不能将接口指针从一个线程传递到另一个线程中。实现 JNI 的虚拟机可将本地线程的数据分配和储存在JNI 接口指针所指向的区域中。

本地方法接受 JNI 接口指针当作参数。虚拟机在从相同的 Java 线程中对本地方法进行多次调用时,保证传递给该本地方法的接口指针是相同的。但是, 一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNI 接口指针。

编译,加载和链接本地方法

由于Java VM是多线程的,因此本地库也应该与多线程感知的本地编译器一起编译和链接。 例如,-mt标志应该用于使用Sun Studio编译器编译的C++代码。 对于GNU gcc编译器的代码,应使用标志-D_REENTRANT-D_POSIX_C_SOURCE。 有关更多信息,请参阅本地编译器文档。

对本地方法的加载通过 System.loadLibrary 方法实现。下例中,类初始化方法加载了一个与平台有关的本地库,在该本地库中给出了本地方法 f 的定义:

java
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

程序员可用单个库来存放任意数量的类所需的所有本地方法,只要这些类是被相同的类加载器所加载。虚拟机在其内部为每个类加载器维护一个其所加载的本地库清单。提供者应该尽量选择能够避免名称冲突的本地库名。

本机库可以与VM静态链接。库和虚拟机镜像组合的方式取决于实现。System.loadLibrary方法或等效的API必须成功才能将此库视为已加载。

当且仅当库L导出名为JNI_OnLoad_L的函数时,其镜像已与VM组合的库L被定义为静态链接。

如果静态链接库L导出名为JNI_OnLoad_L的函数和名为JNI_OnLoad的函数,则将忽略JNI_OnLoad函数。

如果库L是静态链接的,那么在第一次调用System.loadLibrary("L")或等效的API时,将使用为JNI_OnLoad函数指定的相同参数和预期返回值调用JNI_OnLoad_L函数。

静态链接的库L将禁止动态加载具有相同名称的库。

当包含静态链接的本地库L的类加载器被垃圾收集时,如果导出了JNI_OnUnload_L函数,VM将调用库的JNI_OnUnload_L函数。

如果静态链接库L导出名为JNI_OnUnLoad_L的函数和名为JNI_OnUnLoad的函数,则将忽略JNI_OnUnLoad函数。

程序员还可调用 JNI 函数 RegisterNatives() 来注册与类关联的本地方法。在与静态链接的函数一起使用时,RegisterNatives() 函数将特别有用。

解析本地方法名

动态链接程序是根据项的名称来解析各项的。本地方法名由以下几部分串接而成:

  • 前缀Java_

  • 修饰的全限定的类名

  • 下划线("_")分隔符

  • 修饰的方法名

  • 对于重载的本地方法,加上两个下划线(" __")后跟修饰的参数签名

虚拟机将为本地库中的方法查找匹配的方法名。它首先查找没有参数签名的短名称,然后再查找带参数签名的长名称。只有当某个本地方法被另一个本地方法重载时程序员才有必要使用长名。但如果本地方法的名称与非本地方法的名称相同,则不会有问题。因为非本地方法(Java 方法)并不放在本地库中。

下例中,不必用长名来链接本地方法 g,因为另一个方法 g 不是本地方法,因而它并不在本地库中。

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

我们采取简单的名字修饰方案,以保证所有的 Unicode 字符都能被转换为有效的 C 函数名。我们用下划线("_") 字符来代替全限定的类名中的斜杠("/")。由于名称或类型描述符从来不会以数字打头,我们用 _0..._9 来代替转义字符序列,如下表所示:

​ Unicode 字符转换

转义字符序列表示
_0XXXXUnicode 字符 XXXX. 请注意,小写用于表示非ASCII Unicode字符,例如_0abcd而不是 _0ABCD.
_1字符"_"
_2签名中的字符";"
_3签名中的字符"["

本地方法和接口 API 都要遵守给定平台上的库调用标准约定。例如,UNIX系统使用 C 调用约定,而Win32系统使用 __stdcall

本地方法的参数

JNI 接口指针是本地方法的第一个参数。其类型是 JNIEnv。第二个参数随本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其 Java 类的引用。

其余的参数对应于通常 Java 方法的参数。本地方法调用利用返回值将结果传回调用程序中。JNI 的类型和数据结构将描述 Java 类型和 C 类型之间的映射。

下面代码示例说明了如何用 C 函数来实现本地方法 f。对本地方法 f 的声明如下:

java
package pkg; 

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

具有长修饰名称 Java_pkg_Cls_f_ILjava_lang_String_2C 函数实现本地方法 f:

c
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
     JNIEnv *env,        /* 接口指针 */
     jobject obj,        /* "this" 指针 */
     jint i,             /* 参数 #1 */
     jstring s)          /* 参数 #2 */
{
     /* Obtain a C-copy of the Java string */
     const char *str = (*env)->GetStringUTFChars(env, s, 0);

     /* process the string */
     ...

     /* Now we are done with str */
     (*env)->ReleaseStringUTFChars(env, s, str);

     return ...
}

注意,我们总是用接口指针 env 来操作 Java 对象。可用 C++ 将此代码写得稍微简洁一些,如下代码所示:

c
extern "C" /* 指定C调用约定 */ 

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

     JNIEnv *env,        /* 接口指针 */
     jobject obj,        /* "this" 指针 */
     jint i,             /* 参数 #1 */
     jstring s)          /* 参数 #2 */

{
     const char *str = env->GetStringUTFChars(s, 0);

     // ...

     env->ReleaseStringUTFChars(s, str);

     // return ...
}

使用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 对象的域和调用Java对象的方法。JNI 用符号名称和类型签名来识别方法和域。从名称和签名来定位域或对象的过程可分为两步。例如,为调用类 cls 中的 f 方法,本地代码首先要获得方法 ID,如下所示:

c++
jmethodID mid = env->GetMethodID(cls, "f","(ILjava/lang/String;)D");

然后,本地代码可重复使用该方法 ID 而无须再查找该方法,如下所示:

c++
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 函数可被安全地调用:

c++
ExceptionOccurred()
ExceptionDescribe()
ExceptionClear()
ExceptionCheck()
ReleaseStringChars()
ReleaseStringUTFChars()
ReleaseStringCritical()
Release<Type>ArrayElements()
ReleasePrimitiveArrayCritical()
DeleteLocalRef()
DeleteGlobalRef()
DeleteWeakGlobalRef()
MonitorExit()
PushLocalFrame()
PopLocalFrame()

JNI 的类型和数据结构

本章讨论 JNI 如何将 Java 类型映射到本地 C 类型。

本章包含以下话题:

基本类型

下表描述 Java 基本类型及其与计算机相关的本地等效类型。

​ 基本类型和本地等效类型

Java类型本地类型说明
booleanjboolean无符号8位
bytejbyte有符号8位
charjchar无符号16位
shortjshort有符号16位
intjint有符号32位
longjlong有符号64位
floatjfloat32位
doublejdouble64位
voidvoidnot applicable

为了使用方便,提供以下定义:

c++
#define JNI_FALSE 0
#define JNI_TRUE 1

jsize 整数类型用于描述基本的索引和大小:

c++
 typedef jint jsize;

引用类型

JNI 包含了很多对应于不同 Java 对象的引用类型。JNI 引用类型的组织层次如下所示:

  • jobject
    • jclass (java.lang.Class objects)
    • jstring (java.lang.String objects)
    • jarray(arrays)
      • jobjectArray (object arrays)
      • jbooleanArray (boolean arrays)
      • jbyteArray (byte arrays)
      • jcharArray (char arrays)
      • jshortArray (short arrays)
      • jintArray (int arrays)
      • jlongArray (long arrays)
      • jfloatArray (float arrays)
      • jdoubleArray (double arrays)
    • jthrowable (java.lang.Throwable objects)

C 中,所有其它 JNI 引用类型都被定义为与 jobject 一样。例如:

c
typedef jobject jclass;

C++ 中,JNI 引入了虚构类以加强子类关系。例如:

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

域ID和方法ID

方法 ID 和域 ID 是常规的 C 指针类型:

c
struct _jfieldID;              /* 不透明结构体 */
typedef struct _jfieldID *jfieldID;   /* 域ID */

struct _jmethodID;              /* 不透明结构体 */
typedef struct _jmethodID *jmethodID; /* 方法ID */

值类型

jvalue 联合类型用作参数数组中的元素类型。其声明方式如下:

c
typedef union jvalue {
    jboolean z;
    jbyte    b;
    jchar    c;
    jshort   s;
    jint     i;
    jlong    j;
    jfloat   f;
    jdouble  d;
    jobject  l;
} jvalue;

类型签名

JNI 使用 Java 虚拟机的类型签名表示。下表列出了这些类型签名。

Java 虚拟机类型签名

类型签名Java类型
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
L fully-qualified-class ;fully-qualified-class
[ typetype[]
( arg-types ) ret-typemethod type

例如,Java 方法:

java
long f (int n, String s, int[] arr);

具有以下类型签名:

java
(ILjava/lang/String;[I)J

修正的UTF-8字符串

JNI 用修正的 UTF-8 字符串来表示各种字符串类型。修正的UTF-8 字符串和 Java 虚拟机所使用的一样。对修正的UTF-8字符串进行编码,以便只包含非空ASCII字符的字符序列可以表示每个字符仅使用一个字节,但可以表示所有Unicode字符。

所有在 \u0001\u007F 范围内的字符都用单字节表示,如下所示:

  • 0xxxxxxx

字节中的七位数据确定了所表示字符的值。

空字符 ('\u0000') 和 '\u0080''\u07FF' 范围内的字符用一对字节x和y表示:

  • x:110xxxxx
  • y:10yyyyyy

值为 ((x&0x1f)<<6)+(y&0x3f) 的字符需用两个字节表示。

'\u0800''\uFFFF' 范围内的字符用三个字节x,y,和z表示:

  • x:1110xxxx
  • y:10yyyyyy
  • z:10zzzzzz

值为 ((x&0xf)<<12)+(y&0x3f)<<6)+(z&0x3f) 的字符需用三个字节表示。

代码点高于U+FFFF的字符(所谓的补充字符)通过分别编码其UTF-16表示的两个代理代码单元来表示。 每个代理代码单元由三个字节表示。 这意味着,补充字符由六个字节u,v,w,x,y和z表示:

  • u: 11101101
  • v: 1010vvvv
  • w: 10wwwwww
  • x: 11101101
  • y: 1011yyyy
  • z: 10zzzzzz

值为0x10000+((v&0x0f)<<16)+((w&0x3f)<<10)+(y&0x0f)<<6)+(z&0x3f)的字符用六个字节表示。

多字节字符的字节以big-endian(高字节优先)顺序存储在class文件中。

此格式与标准的UTF-8 格式之间有两个区别。第一,空字符(char)0 使用双字节格式进行编码,而不是单字节格式。这意味着修正的UTF-8字符串不可能有嵌入的空值。第二,仅使用标准UTF-8的单字节,双字节和三字节格式。 Java VM无法识别标准UTF-8的四字节格式; 它使用自己的两倍三字节格式。

有关标准UTF-8格式的更多信息,请参见3.9 Unicode标准版本4.0的Unicode编码格式

JNI 函数

本章为 JNI 函数提供参考信息。其中列出了全部 JNI 函数,同时也给出了 JNI 函数表的准确布局。

注意:"必须"一词用于约束 JNI 编程人员。例如,当说明某个 JNI 函数必须接收非空对象时,就应确保不要向该 JNI 函数传递 NULL。因此,JNI 实现无需在该 JNI 函数中执行 NULL 指针检查。

本章的部分资料改编自 NetscapeJRI 文档。

该参考资料按用法对函数进行组织。参考部分按下列函数区域进行组织:

接口函数表

每个函数均可通过 JNIEnv 参数以固定偏移量进行访问。JNIEnv 的类型是一个指针,指向存储全部 JNI 函数指针的结构体。其定义如下:

c++
typedef const struct JNINativeInterface *JNIEnv;

VM初始化函数表,如以下代码示例所示。注意:前三项留作将来与 COM 兼容。此外,我们在函数表开头部分也留出来多个 NULL 项,从而可将将来与类有关的 JNI 操作添加到 FindClass 后面,而非函数表的末尾。

注意,函数表可在所有 JNI 接口指针间共享。

c++
const struct JNINativeInterface ... = {

    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,

    DefineClass,
    FindClass,

    FromReflectedMethod,
    FromReflectedField,
    ToReflectedMethod,

    GetSuperclass,
    IsAssignableFrom,

    ToReflectedField,

    Throw,
    ThrowNew,
    ExceptionOccurred,
    ExceptionDescribe,
    ExceptionClear,
    FatalError,

    PushLocalFrame,
    PopLocalFrame,

    NewGlobalRef,
    DeleteGlobalRef,
    DeleteLocalRef,
    IsSameObject,
    NewLocalRef,
    EnsureLocalCapacity,

    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,

    GetStringRegion,
    GetStringUTFRegion,

    GetPrimitiveArrayCritical,
    ReleasePrimitiveArrayCritical,

    GetStringCritical,
    ReleaseStringCritical,

    NewWeakGlobalRef,
    DeleteWeakGlobalRef,

    ExceptionCheck,

    NewDirectByteBuffer,
    GetDirectBufferAddress,
    GetDirectBufferCapacity,

    GetObjectRefType
  };

版本信息

GetVersion

c++
jint GetVersion(JNIEnv *env);

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

链接:

JNIEnv接口函数表索引4。

参数:

envJNI 接口指针。

返回值:

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

JDK/JRE 1.1中, GetVersion() 返回0x00010001.

JDK/JRE 1.2中, GetVersion() 返回0x00010002.

JDK/JRE 1.4中, GetVersion() 返回0x00010004.

JDK/JRE 1.6中, GetVersion() 返回0x00010006.

常量

自从JDK/JRE 1.2:

c++
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002

/* 错误代码 */
#define JNI_EDETACHED    (-2)       /* 线程与虚拟机断开链接错误 */
#define JNI_EVERSION     (-3)       /* JNI版本错误 */

自从JDK/JRE 1.4:

c++
#define JNI_VERSION_1_4 0x00010004

自从JDK/JRE 1.6:

c++
#define JNI_VERSION_1_6 0x00010006

类操作

DefineClass

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

从原始类数据的缓冲区中加载类。在DefineClass调用返回后,VM不会引用包含原始类数据的缓冲区,如果需要,可以将其丢弃。

链接:

JNIEnv接口函数表索引5。

参数:

envJNI 接口指针。

name:被定义的类名或接口名。这个字符串是修正的UTF-8编码。

loader:分派给所定义的类的类加载器。

buf:包含.class文件数据的缓存区

bufLen:缓冲区长度。

返回值:

返回 Java 类对象。如果出错则返回 NULL

抛出:

ClassFormatError:如果类数据指定的类无效。

ClassCircularityError:如果类或接口是自身的超类或超接口。

OutOfMemoryError:如果系统内存不足。

SecurityException:如果调用者试图在“java”包树中定义一个类。

FindClass

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

JDK release 1.1,该函数用于加载本地定义的类。它在 CLASSPATH 环境变量指定的目录和 zip 文件中搜索指定名称的类。

自从 Java 2 SDK release 1.2,Java安全模型允许非系统类加载和调用本地方法。FindClass定位与当前本地方法相关的类加载器。也就是说,声明本地方法的类的类加载器。如果本地方法属于系统类,则不涉及类加载器。否则,将调用适当的类加载器来加载和链接指定的类。

自从 Java 2 SDK release 1.2,当通过调用接口调用FindClass时,当前没有本地方法或其关联的类加载器。这种情况下,使用的是ClassLoader.getSystemClassLoader的结果。这是虚拟机为应用程序创建的类加载器,能够定位java.class.path属性中列出的类。

name参数是全限定类名或者数组类型签名。例如,类java.lang.String的全限定名称是:

c++
"java/lang/String"

java.lang.Object[]数组类型签名是:

c++
"[Ljava/lang/Object;"

链接:

JNIEnv接口函数表索引6。

参数:

envJNI 接口指针。

name:类全名(即包名后跟类名,之间由"/"分隔)。如果该名称以"["(数组签名字符)打头,则返回一个数组类。字符串是modified UTF-8编码

返回值:

返回类对象全名。如果找不到该类,则返回 NULL

抛出:

ClassFormatError:如果类数据指定的类无效。

ClassCircularityError:如果类或接口是自身的超类或超接口

NoClassDefFoundError:如果找不到所请求的类或接口的定义

OutOfMemoryError:如果系统内存不足。

GetSuperclass

c++
jclass GetSuperclass(JNIEnv *env, jclass clazz);

如果 clazz 代表类而非类 object,则该函数返回由 clazz 所指定的类的超类。

如果 clazz 指定类 object 或代表某个接口,则该函数返回 NULL

链接:

JNIEnv接口函数表索引10。

参数:

envJNI 接口指针。

clazzJava 类对象。

返回值:

clazz 所代表的类的超类或 NULL

IsAssignableFrom

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

确定 clazz1 的对象是否可安全地强制转换为 clazz2

链接:

JNIEnv接口函数表索引11。

参数:

envJNI 接口指针。

clazz1:第一个类参数。

clazz2:第二个类参数。

返回值:

下列某个情况为真时返回 JNI_TRUE

  • 第一及第二个类参数引用同一个 Java 类。

  • 第一个类是第二个类的子类。

  • 第二个类是第一个类的某个接口。

异常

Throw

c++
jint Throw(JNIEnv *env, jthrowable obj);

抛出 java.lang.Throwable 对象。

链接:

JNIEnv接口函数表索引13。

参数:

envJNI 接口指针。

objjava.lang.Throwable 对象。

返回值:

成功时返回 0,失败时返回负数。

抛出:

java.lang.Throwable 对象 obj

ThrowNew

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

利用指定类的消息(由 message 指定)构造异常对象并抛出该异常。

链接:

JNIEnv接口函数表索引14。

参数:

envJNI 接口指针。

clazzjava.lang.Throwable 的 子 类 。

message:用于构造 java.lang.Throwable 对象的消息。

返回值:

成功时返回 0,失败时返回负数。

抛出:

新构造的 java.lang.Throwable 对象。

ExceptionOccurred

c++
jthrowable ExceptionOccurred(JNIEnv *env);

确定是否某个异常正被抛出。在本地代码调用 ExceptionClear()Java 代码处理该异常前,异常将始终保持抛出状态。

链接:

JNIEnv接口函数表索引15。

参数:

envJNI 接口指针。

返回值

返回正被抛出的异常对象,如果当前无异常被抛出,则返回 NULL

ExceptionDescribe

c++
void ExceptionDescribe(JNIEnv *env);

将异常及堆栈的回溯输出到系统错误报告信道(例如 stderr)。该例程可便利调试操作。

链接:

JNIEnv接口函数表索引16。

参数:

envJNI 接口指针。

ExceptionClear

c++
void ExceptionClear(JNIEnv *env);

清除当前抛出的任何异常。如果当前无异常,则此例程不产生任何效果。

链接:

JNIEnv接口函数表索引17。

参数:

envJNI 接口指针。

FatalError

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

抛出致命错误并且不希望虚拟机进行修复。该函数无返回值。

链接:

JNIEnv接口函数表索引18。

参数:

envJNI 接口指针。

msg:错误消息。modified UTF-8编码的字符串。

ExceptionCheck

我们引入了便利函数,以检查未决异常,而无需创建对异常对象的本地引用。

c++
jboolean ExceptionCheck(JNIEnv *env);

当存在未决的异常时返回JNI_TRUE,否则返回JNI_FALSE

链接:

JNIEnv接口函数表索引228。

自从:

JDK/JRE 1.2

全局及局部引用

全局引用

NewGlobalRef

c++
jobject NewGlobalRef(JNIEnv *env, jobject obj);

创建 obj 参数所引用对象的新全局引用。obj 参数既可以是全局引用,也可以是局部引用。全局引用通过调用 DeleteGlobalRef() 来显式释放。

链接:

JNIEnv接口函数表索引21。

参数:

envJNI 接口指针。

obj:全局或局部引用。

返回值:

返回给定obj的全局引用。

可能在以下情况返回NULL:

  • obj指向null
  • 系统内存不足。
  • obj是一个弱全局引用,已经被垃圾回收了。

DeleteGlobalRef

c++
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

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

链接:

JNIEnv接口函数表索引22。

参数:

envJNI 接口指针。

globalRef:全局引用。

局部引用

局部引用在本机方法调用期间有效。它们在本机方法返回后自动释放。每个本地引用都会消耗一定数量的Java虚拟机资源。程序员需要确保本地方法不会过度分配本地引用。尽管本地方法返回Java后会自动释放本地引用,但过多分配本地引用可能会导致虚拟机在执行本地方法期间内存耗尽。

DeleteLocalRef

c++
void DeleteLocalRef(JNIEnv *env, jobject localRef);

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

链接:

JNIEnv接口函数表索引23。

参数:

envJNI 接口指针。

localRef:局部引用。

注意:JDK/JRE 1.1提供上述的DeleteLocalRef使得程序员能够手动删除局部引用。例如,如果本地代码遍历一个可能很大的对象数组并在每次迭代中使用一个元素,在下一次迭代中创建新的局部引用之前,删除对不再使用的数组元素的局部引用是一个很好的实践。

从JDK/JRE 1.2开始,提供了一组额外的功能,用于局部引用生命周期管理。它们是下面列出的四个函数。

EnsureLocalCapactiy

c++
jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

确保至少给定数量的本地引用可以在当前线程中被创建。成功返回0;否则返回负数或者抛出OutOfMemoryError

在进入本地方法前,虚拟机自动确保至少可以创建16个本地引用。

为了向后兼容,虚拟机分配的本地引用超过了保证的容量。(作为调试支持,虚拟机可能会警告用户正在创建过多的本地引用。在JDK中,程序员可以提供-verbose:jni命令行选项来打开这些信息。)如果不能在确保的容量之外创建更多的本地引用,虚拟机将调用FatalError

链接:

JNIEnv接口函数表索引26。

自从:

JDK/JRE 1.2

PushLocalFrame

c++
jint PushLocalFrame(JNIEnv *env, jint capacity);

创建一个新的局部引用帧,其中至少可以创建给定数量的局部引用。成功返回0,失败返回负数或者潜在的OutOfMemoryError

注意在以前的局部帧中创建的局部引用在当前的局部帧中仍然有效。

链接:

JNIEnv接口函数表索引19。

自从:

JDK/JRE 1.2

PopLocalFrame

c++
jobject PopLocalFrame(JNIEnv *env, jobject result);

弹出当前局部引用帧,释放所有局部引用,并返回给定result对象前一个局部引用帧的一个局部引用。

如果不需要返回前一帧的引用,则将NULL传递给result

链接:

JNIEnv接口函数表索引20。

自从:

JDK/JRE 1.2

NewLocalRef

c++
jobject NewLocalRef(JNIEnv *env, jobject ref);

创建一个新的局部引用,与ref引用相同的对象。给定的ref可能是全局或者局部引用。如果ref指向null,则返回NULL

链接:

JNIEnv接口函数表索引25。

自从:

JDK/JRE 1.2

弱全局引用

弱全局引用是一种特殊的全局引用。跟普通的全局引用不同的是,弱全局引用允许底层Java对象可以被垃圾回收。弱全局引用可以在任何使用全局或局部引用的情况下使用。

弱全局引用跟Java虚引用(java.lang.ref.PhantomReference)相关。在确定对象是否幻影可达(phantom reachable)时,对具体对象的弱全局引用被视为对该对象的虚引用(参见java.lang.ref)。这样的弱全局引用将在功能上等同于NULL,同时引用同一个对象的PhantomReference将被垃圾收集器清除。

因为垃圾回收可能在本地方法正在允许时发生,由弱全局引用引用的对象可以随时释放。虽然在使用全局引用的地方可以使用弱全局引用,但这样做通常是不合适的,因为它们可能在没有通知的情况下在功能上等同于NULL

IsSameObject能够用于弱全局引用与非NULL的局部或全局引用间的比较。如果对象相同,只要另一个引用没有被删除,弱全局引用在功能上就不会等价于NULL

IsSameObject也能够用于弱全局引用与NULL间的比较,以确定底层对象是否已被释放。但是,程序员不应该依赖此检查来确定在未来的JNI函数调用中是否可以使用弱全局引用(作为非null引用),因为期间的垃圾收集可能会改变弱全局引用。

相反,推荐使用JNI函数NewLocalRefNewGlobalRef来获取底层对象的局部或全局引用。如果对象已经释放,这些函数将返回NULL。否则,新的引用将阻止底层对象的释放。新的引用,如果不为NULL,能够用于访问底层的对象,并且当不在需要访问时删除。

NewWeakGlobalRef

c++
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

创建一个新的弱全局引用。如果obj指向null或者虚拟机内存不足,返回NULL。如果虚拟机内存耗尽将抛出OutOfMemoryError

链接:

JNIEnv接口函数表索引226。

自从:

JDK/JRE 1.2

DeleteWeakGlobalRef

c++
void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

删除给定弱全局引用所需的虚拟机资源。

链接:

JNIEnv接口函数表索引227。

自从:

JDK/JRE 1.2

对象操作

AllocObject

c++
jobject AllocObject(JNIEnv *env, jclass clazz);

分配新 Java 对象而不调用该对象的任何构造函数。返回该对象的引用。

clazz 参数务必不要引用数组类。

链接:

JNIEnv接口函数表索引27。

参数:

envJNI 接口指针。

clazzJava 类对象。

返回值:

返回 Java 对象。如果无法构造该对象,则返回 NULL。

抛出:

InstantiationException:如果该类为一个接口或抽象类。

OutOfMemoryError:如果系统内存不足。

NewObject,NewObjectA,NewObjectV

c++
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, const 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 方法。

链接:

JNIEnv接口函数表索引28。

NewObjectA

编程人员应将传递给构造函数的所有参数放在 jvalues 类型的数组 args 中, 该数组紧跟着放在 methodID 参数的后面。NewObject() 收到数组中的这些参数后,将把它们传给编程人员所要调用的 Java 方法。

链接:

JNIEnv接口函数表索引30。

NewObjectV

编程人员应将传递给构造函数的所有参数放在 va\_list 类型的参数 args 中, 该参数紧跟着放在 methodID 参数的后面。NewObject() 收到这些参数后,将把它们传给编程人员所要调用的 Java 方法。

链接:

JNIEnv接口函数表索引29。

参数:

envJNI 接口指针。

clazzJava 类对象。

methodID:构造函数的方法 ID

NewObject的其它参数:

传给构造函数的参数。

NewObjectA的其它参数:

args:传给构造函数的参数数组。

NewObjectV的其它参数 :

args:传给构造函数的参数 va_list

返回值:

返回 Java 对象,如果无法构造该对象,则返回 NULL

抛出 :

InstantiationException:如果该类为接口或抽象类。

OutOfMemoryError:如果系统内存不足。

构造函数抛出的任何异常。

GetObjectClass

c++
jclass GetObjectClass(JNIEnv *env, jobject obj);

返回对象的类。

链接:

JNIEnv接口函数表索引31。

参数:

envJNI接口指针。

objJava 对象(不能为 NULL)。

返回值:

返回 Java 类对象。

GetObjectRefType

c++
jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);

返回obj参数引用对象的类型。参数obj可以是局部引用、全局引用或弱全局引用。

链接:

JNIEnv接口函数表索引232。

参数:

env: JNI接口指针。

obj: 一个局部引用、全局引用或弱全局引用。

返回值:

GetObjectRefType函数返回jobjectRefType定义的枚举值之一:

c++
JNIInvalidRefType = 0,
JNILocalRefType = 1,
JNIGlobalRefType = 2,
JNIWeakGlobalRefType = 3

如果参数obj是弱全局引用类型,返回将是JNIWeakGlobalRefType

如果参数obj是全局引用类型,返回将是JNIGlobalRefType

如果参数obj是局部引用类型,返回将是JNILocalRefType

如果参数obj不是有效的引用类型,返回将是JNIInvalidRefType

无效引用不是有效句柄的引用。也就是说,obj指针地址不指向从Ref创建函数之一分配或从JNI函数返回的内存位置。

因此,NULL将是一个无效的引用,GetObjectRefType(env,NULL)将返回JNIInvalidRefType

另一方面,null引用是指向null的引用,它将返回null引用最初创建时的引用类型。

GetObjectRefType不能用于已删除的引用。

由于引用通常被实现为指向内存数据结构的指针,这些数据结构可能被虚拟机中的任何引用分配服务重用,因此一旦删除,就不指定GetObjectRefType将返回什么值。

自从:

JDK/JRE 1.6

IsInstanceOf

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

检测对象是否为某个类的实例。

链接:

JNIEnv接口函数表索引32。

参数:

envJNI 接口指针。

objJava 对象。

clazzJava 类对象。

返回值:

如果可将 obj 强制转换为 clazz,则返回 JNI_TRUE。否则返回 JNI_FALSENULL对象可强制转换为任何类。

IsSameObject

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

检测两个引用是否引用同一 Java 对象。

链接:

JNIEnv接口函数表索引24。

参数:

envJNI 接口指针。

ref1Java 对象。

ref2Java 对象。

返回值:

如果 ref1ref2 引用同一 Java 对象或均为 NULL,则返回 JNI_TRUE。否则返回 JNI_FALSE

访问对象的域

GetFieldID

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

返回类的实例(非静态)域的域 ID。该域由其名称及签名指定。访问器函数的Get<type>FieldSet<type>Field 系列使用域 ID 检索对象域。

GetFieldID()将导致未初始化的类初始化。

GetFieldID()不能用于获取数组的长度域。应使用 GetArrayLength()

链接:

JNIEnv接口函数表索引94。

参数:

envJNI 接口指针。

clazzJava 类对象。

name: 域名(0终结的 modifiedUTF-8 字符串)。

sig:域签名(0终结的 modifiedUTF-8 字符串)。

返回值:

域 ID。如果操作失败,则返回 NULL。

抛出:

NoSuchFieldError:如果找不到指定的域。

ExceptionInInitializerError:如果由于异常而导致类初始化程序失败。

OutOfMemoryError:如果系统内存不足。

Get<type>Field例程

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

该访问器例程系列返回对象的实例(非静态)域的值。要访问的域由通过调用GetFieldID() 而得到的域ID指定。

下表说明了 Get<type>Field 例程名及结果类型。应将 Get<type>Field 中的type 替换为域的 Java 类型(或使用表中的某个实际例程名),然后将NativeType 替换为该例程对应的本地类型。

Get<type>Field例程名本地类型
GetObjectField()jobject
GetBooleanField()jboolean
GetByteField()jbyte
GetCharField()jchar
GetShortField()jshort
GetIntField()jint
GetLongField()jlong
GetFloatField()jfloat
GetDoubleField()jdouble

链接:

JNIEnv接口函数表索引如下。

Get<type>Field例程名索引
GetObjectField()95
GetBooleanField()96
GetByteField()97
GetCharField()98
GetShortField()99
GetIntField()100
GetLongField()101
GetFloatField()102
GetDoubleField()103

参数:

envJNI 接口指针。

objJava 对象(不能为 NULL)。

fieldID:有效的域 ID

返回值:

域的内容。

Set<type>Field例程

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

该访问器例程系列设置对象的实例(非静态)域的值。要访问的域由通过调用 GetFieldID() 而得到的域 ID 指定。

下表说明了 Set<type>Field 例程名及结果类型。应将 Set<type>Field 中的type 替换为域的 Java 类型(或使用表中的某个实际例程名),然后将

NativeType 替换为该例程对应的本地类型。

Set<type>Field例程名NativeType
SetObjectField()jobject
SetBooleanField()jboolean
SetByteField()jbyte
SetCharField()jchar
SetShortField()jshort
SetIntField()jint
SetLongField()jlong
SetFloatField()jfloat
SetDoubleField()jdouble

链接:

JNIEnv接口函数表索引如下。

Set<type>Field例程名索引
SetObjectField()104
SetBooleanField()105
SetByteField()106
SetCharField()107
SetShortField()108
SetIntField()109
SetLongField()110
SetFloatField()111
SetDoubleField()112

参数:

envJNI 接口指针。

objJava 对象(不能为 NULL)。

fieldID: 有效的域 ID

value:域的新值。

调用实例方法

GetMethodID

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

返回类或接口实例(非静态)方法的方法 ID。方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。

GetMethodID() 可使未初始化的类初始化。

要获得构造函数的方法 ID,应将 <init> 作为方法名,同时将 void (V) 作为返回类型。

参数:

envJNI 接口指针。

clazzJava 类对象。

name:方法名(0 终结的 modifiedUTF-8 字符串)。

sig:方法签名(0 终结的 modifiedUTF-8 字符串)。

返回值:

方法 ID,如果找不到指定的方法,则为 NULL

抛出:

NoSuchMethodError:如果找不到指定方法。

ExceptionInInitializerError:如果由于异常而导致类初始化程序失败。

OutOfMemoryError:如果系统内存不足。

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

c++
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const 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接受这些数组中的参数并将其传给编程人员所要调用的 Java 方法。

Call<type>MethodV例程

编程人员将方法的所有参数放在紧跟着在 methodID 参数之后的 va_list 类型参数变量中。Call<type>MethodV 接受这些参数并将其传给编程人员所要调用的 Java 方法。

下表根据结果类型说明了各个方法调用例程。用户应将 Call<type>Method 中的type 替换为所调用方法的 Java 类型(或使用表中的实际方法调用例程名), 同时将 NativeType 替换为该例程相应的本地类型。

Call<type>Method例程名NativeType
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

链接:

JNIEnv接口函数表索引如下。

Call<type>Method例程名索引
CallVoidMethod() CallVoidMethodA() CallVoidMethodV()61 63 62
CallObjectMethod() CallObjectMethodA() CallObjectMethodV()34 36 35
CallBooleanMethod() CallBooleanMethodA() CallBooleanMethodV()37 39 38
CallByteMethod() CallByteMethodA() CallByteMethodV()40 42 41
CallCharMethod() CallCharMethodA() CallCharMethodV()43 45 44
CallShortMethod() CallShortMethodA() CallShortMethodV()46 48 47
CallIntMethod() CallIntMethodA() CallIntMethodV()49 51 50
CallLongMethod() CallLongMethodA() CallLongMethodV()52 54 53
CallFloatMethod() CallFloatMethodA() CallFloatMethodV()55 57 56
CallDoubleMethod() CallDoubleMethodA() CallDoubleMethodV()58 60 59

参数:

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例程

c++
NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const 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接受这些参数并将其传给编程人员所要调用的 Java 方法。

CallNonvirtual<type>MethodA例程

编程人员应将要传给方法的所有参数放在紧跟在 methodID 参数之后的jvalues 类型数组 args 中。CallNonvirtual<type>MethodA 接受这些数组中的参数并将其传给编程人员所要调用的 Java 方法。

CallNonvirtual<type>MethodV例程

编程人员应将要传给方法的所有参数放在紧跟在 methodID 参数之后的va_list 类型参数 args 中。CallNonvirtualMethodV 接受这些参数并将其传给编程人员所要调用的 Java 方法。

下表根据结果类型说明了各个方法调用例程。用户应将CallNonvirtual<type>Method 中的 type 替换为所调用方法的 Java 类型(或使用表中的实际方法调用例程名),同时将 NativeType 替换为该例程相应的本地类型。

CallNonvirtual<type>Method例程名Native Type
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

链接:

JNIEnv接口函数表索引如下。

CallNonvirtual<type>Method例程名索引
CallNonvirtualVoidMethod() CallNonvirtualVoidMethodA() CallNonvirtualVoidMethodV()91 93 92
CallNonvirtualObjectMethod() CallNonvirtualObjectMethodA() CallNonvirtualObjectMethodV()64 66 65
CallNonvirtualBooleanMethod() CallNonvirtualBooleanMethodA() CallNonvirtualBooleanMethodV()67 69 68
CallNonvirtualByteMethod() CallNonvirtualByteMethodA() CallNonvirtualByteMethodV()70 72 71
CallNonvirtualCharMethod() CallNonvirtualCharMethodA() CallNonvirtualCharMethodV()73 75 74
CallNonvirtualShortMethod() CallNonvirtualShortMethodA() CallNonvirtualShortMethodV()76 78 77
CallNonvirtualIntMethod() CallNonvirtualIntMethodA() CallNonvirtualIntMethodV()79 81 80
CallNonvirtualLongMethod() CallNonvirtualLongMethodA() CallNonvirtualLongMethodV()82 84 83
CallNonvirtualFloatMethod() CallNonvirtualFloatMethodA() CallNonvirtualFloatMethodV()85 87 86
CallNonvirtualDoubleMethod() CallNonvirtualDoubleMethodA() CallNonvirtualDoubleMethodV()88 90 89

参数:

envJNI 接口指针。

clazzJava 类 。

obj: Java 对象。

methodID:方法 ID

CallNonvirtual<type>Method 例程的其它参数:

要传给 Java 方法的参数。

CallNonvirtual<type>MethodA例程的其它参数:

args:参数数组。

CallNonvirtual<type>MethodV 例程的其它参数:

args:参数的 va_list

返回值

调用 Java 方法的结果。

抛出:

执行 Java 方法时所抛出的异常。

访问静态域

GetStaticFieldID

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

返回类的静态域的域 ID。域由其名称和签名指定。GetStatic<type>FieldSetStatic<type>Field 访问器函数系列使用域 ID 检索静态域。

GetStaticFieldID() 将未初始化的类初始化。

链接:

JNIEnv接口函数表索引144。

参数:

envJNI 接口指针。

clazzJava 类对象。

name: 静态域名(0终结的 modifiedUTF-8 字符串)。

sig:域签名(0终结的 modifiedUTF-8 字符串)。

返回值:

ID,如果找不到指定的静态域,则为 NULL

抛出:

NoSuchFieldError:如果找不到指定的静态域。

ExceptionInInitializerError:如果由于异常而导致类初始化程序失败。

OutOfMemoryError:如果系统内存不足。

GetStatic<type>Field例程

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

该访问器例程系列返回对象的静态域的值。要访问的域由通过调用GetStaticFieldID() 而得到的域 ID 指定。

下表说明了 GetStatic<type>Field 例程名及结果类型。应将GetStatic<type>Field 中的 type 替换为域的 Java 类型(或使用表中的某个实际例程名),然后将 NativeType 替换为该例程对应的本地类型。

GetStatic<type>Field例程名Native Type
GetStaticObjectField()jobject
GetStaticBooleanField()jboolean
GetStaticByteField()jbyte
GetStaticCharField()jchar
GetStaticShortField()jshort
GetStaticIntField()jint
GetStaticLongField()jlong
GetStaticFloatField()jfloat
GetStaticDoubleField()jdouble

链接:

JNIEnv接口函数表索引如下。

GetStatic<type>Field例程名索引
GetStaticObjectField()145
GetStaticBooleanField()146
GetStaticByteField()147
GetStaticCharField()148
GetStaticShortField()149
GetStaticIntField()150
GetStaticLongField()151
GetStaticFloatField()152
GetStaticDoubleField()153

参数:

envJNI 接口指针。

clazzJava 类对象。

fieldID:静态域 ID

返回值:

静态域的内容。

SetStatic<type>Field例程

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

该访问器例程系列设置对象的静态域的值。要访问的域由通过调用GetStaticFieldID() 而得到的域 ID 指定。

下表说明了 SetStatic<type>Field 例程名及结果类型。应将SetStatic<type>Field 中的 type 替换为域的 Java 类型(或使用表中的某个实际例程名),然后将 NativeType 替换为该例程对应的本地类型。

链接:

JNIEnv接口函数表索引如下。

SetStatic<type>Field例程索引
SetStaticObjectField()154
SetStaticBooleanField()155
SetStaticByteField()156
SetStaticCharField()157
SetStaticShortField()158
SetStaticIntField()159
SetStaticLongField()160
SetStaticFloatField()161
SetStaticDoubleField()162

参数:

envJNI 接口指针。

clazzJava 类对象。

fieldID:静态域 ID

value:域的新值。

调用静态方法

GetStaticMethodID

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

返回类的静态方法的方法 ID。方法由其名称和签名指定。

GetStaticMethodID() 将导致未初始化的类初始化。

链接:

JNIEnv接口函数表索引113。

参数:

envJNI 接口指针。

clazzJava 类对象。

name:静态方法名(0 终结的 modifiedUTF-8 字符串)。

sig:方法签名(0 终结的 modifiedUTF-8 字符串)。

返回值:

方法 ID,如果操作失败,则为 NULL

异常:

NoSuchMethodError:如果找不到指定的静态方法。

ExceptionInInitializerError:如果由于异常而导致类初始化程序失败。

OutOfMemoryError:如果系统内存不足。

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

c++
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接受这些参数并将其传给编程人员所要调用的 Java 方法。

CallStatic<type>MethodA例程

编程人员应将要传给方法的所有参数放在紧跟在 methodID 参数之后的jvalues类型数组 args 中。CallStaticMethodA 接受这些数组中的参数并将其传给编程人员所要调用的 Java 方法。

CallStatic<type>MethodV例程

编程人员应将要传给方法的所有参数放在紧跟在 methodID 参数之后的va_list类型参数 args 中。CallStaticMethodV 接受这些参数并将其传给编程人员所要调用的 Java 方法。

下表根据结果类型说明了各个方法调用例程。用户应将CallStatic<type>Method中的 type 替换为所调用方法的 Java 类型(或使用表中的实际方法调用例程名),同时将 NativeType 替换为该例程相应的本地类型。

CallStatic<type>Method例程名NativeType
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

链接:

JNIEnv接口函数表索引如下。

CallStatic<type>Method 例程名索引
CallStaticVoidMethod() CallStaticVoidMethodA() CallStaticVoidMethodV()141 143 142
CallStaticObjectMethod() CallStaticObjectMethodA() CallStaticObjectMethodV()114 116 115
CallStaticBooleanMethod() CallStaticBooleanMethodA() CallStaticBooleanMethodV()117 119 118
CallStaticByteMethod() CallStaticByteMethodA() CallStaticByteMethodV()120 122 121
CallStaticCharMethod() CallStaticCharMethodA() CallStaticCharMethodV()123 125 124
CallStaticShortMethod() CallStaticShortMethodA() CallStaticShortMethodV()126 128 127
CallStaticIntMethod() CallStaticIntMethodA() CallStaticIntMethodV()129 131 130
CallStaticLongMethod() CallStaticLongMethodA() CallStaticLongMethodV()132 134 133
CallStaticFloatMethod() CallStaticFloatMethodA() CallStaticFloatMethodV()135 137 136
CallStaticDoubleMethod() CallStaticDoubleMethodA() CallStaticDoubleMethodV()138 140 139

参数:

envJNI 接口指针。

clazzJava 类对象。

methodID:静态方法 ID

CallStatic<type>Method 例程的其它参数:

要传给静态方法的参数。

CallStatic<type>MethodA 例程的其它参数:

args:参数数组。

CallStatic<type>MethodV 例程的其它参数:

args:参数的 va_list

返回值:

返回调用静态Java方法的结果。

异常:

执行 Java 方法时抛出的异常。

字符串操作

NewString

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

利用 Unicode 字符数组构造新的 java.lang.String 对象。

链接:

JNIEnv接口函数表索引163。

参数:

envJNI 接口指针。

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

lenUnicode 字符串的长度。

返回值:

Java 字符串对象。如果无法构造该字符串,则为 NULL

异常:

如果系统内存不足抛出OutOfMemoryError

GetStringLength

c++
jsize GetStringLength(JNIEnv *env, jstring string);

返回 Java 字符串的长度(Unicode 字符数)。

链接:

JNIEnv接口函数表索引164。

参数:

envJNI 接口指针。

stringJava 字符串对象。

返回值:

Java 字符串的长度。

GetStringChars

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

返回指向字符串的 Unicode 字符数组的指针。该指针在调用ReleaseStringchars()前一直有效。

如果 isCopy 非空,则在复制完成后将*isCopy设为JNI_TRUE。如果没有复制, 则设为JNI_FALSE

链接:

JNIEnv接口函数表索引165。

参数:

envJNI 接口指针。

string:Java 字符串对象。

isCopy:指向布尔值的指针。

返回值:

指向 Unicode 字符串的指针,如果操作失败,则返回 NULL

ReleaseStringChars

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

通知虚拟机本地代码无需再访问 chars。参数 chars 是一个指针,可通过GetStringChars()string 获得。

链接:

JNIEnv接口函数表索引166。

参数:

envJNI 接口指针。

stringJava 字符串对象。

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

NewStringUTF

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

利用 UTF-8 字符数组构造新 java.lang.String 对象。

链接:

JNIEnv接口函数表索引167。

参数:

envJNI 接口指针。

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

返回值:

Java 字符串对象。如果无法构造该字符串,则为 NULL

异常:

如果系统内存不足抛出OutOfMemoryError

GetStringUTFLength

c++
jsize GetStringUTFLength(JNIEnv *env, jstring string);

以字节为单位返回字符串的 UTF-8 长度。

链接:

JNIEnv接口函数表索引168。

参数:

envJNI 接口指针。

stringJava 字符串对象。

返回值:

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

GetStringUTFChars

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

返回指向字符串的 UTF-8 字符数组的指针。该数组在被ReleaseStringUTFChars() 释放前将一直有效。

如果 isCopy 不是 NULL*isCopy 在复制完成后即被设为 JNI_TRUE。如果未复制,则设为 JNI_FALSE

链接:

JNIEnv接口函数表索引169。

参数:

env:JNI 接口指针。

stringJava 字符串对象。

isCopy:指向布尔值的指针。

返回值:

指向 UTF-8 字符串的指针。如果操作失败,则为 NULL

ReleaseStringUTFChars

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

通知虚拟机本地代码无需再访问 utfutf 参数是一个指针,可利用GetStringUTFChars()string 获 得 。

链接:

JNIEnv接口函数表索引170。

参数:

envJNI 接口指针。

stringJava 字符串对象。

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

注意:在JDK/JRE 1.1中,程序员可以在用户提供的缓冲区中获得原始数组元素。从JDK/JRE 1.2开始,提供了额外的函数,允许本地代在用户提供的缓冲区中获取Unicode(UTF-16)modified UTF-8编码的字符。见以下函数。

GetStringRegion

c++
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);

从偏移量start开始拷贝len数的Unicode字符到给定的buf缓冲区。

如果下标越界抛出 StringIndexOutOfBoundsException

链接:

JNIEnv接口函数表索引220。

自从:

JDK/JRE 1.2

GetStringUTFRegion

c++
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);

从偏移量start开始的len 数个Unicode字符转换为 modified UTF-8编码,并将结果放在给定的缓冲区buf中。

如果下标越界抛出 StringIndexOutOfBoundsException

链接:

JNIEnv接口函数表索引221。

自从:

JDK/JRE 1.2

GetStringCritical,ReleaseStringCritical

c++
const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy);
void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray);

这两个函数语义上与现有的 Get/ReleaseStringChars函数相似。如果可能的话,虚拟机返回字符串元素的指针,否则返回制作的副本。但是,如何使用这两个函数有重要的限制。被Get/ReleaseStringCritical调用包含的代码片段中,本地代码不得发布任意的JNI调用,或导致当前线程阻塞。

Get/ReleaseStringCritical上的限制跟Get/ReleasePrimitiveArrayCritical相似。

链接(GetStringCritical):

JNIEnv接口函数表索引224。

链接(ReleaseStringCritical):

JNIEnv接口函数表索引225。

自从:

JDK/JRE 1.2

数组操作

GetArrayLength

c++
jsize GetArrayLength(JNIEnv *env, jarray array);

返回数组中的元素数。

链接:

JNIEnv接口函数表索引171。

参数:

envJNI接口指针。

arrayJava数组对象。

返回值:

数组的长度。

NewObjectArray

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

构造新的保存 elementClass类型对象的数组。所有元素初始值均设为initialElement

链接:

JNIEnv接口函数表索引172。

参数:

envJNI 接口指针。

length:数组大小。

elementClass:数组元素类。

initialElement: 初始值 。

返回值:

Java 数组对象。如果无法构造数组,则为 NULL

异常:

如果系统内存不足抛出OutOfMemoryError

GetObjectArrayElement

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

返回 Object 数组的元素。

链接:

JNIEnv接口函数表索引173。

参数:

envJNI 接口指针。

arrayJava 数组。

index:数组下标。

返回值:

Java 对象。

异常:

如果 index不是数组中的有效下标抛出ArrayIndexOutOfBoundsException

SetObjectArrayElement

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

设置 Object 数组的元素。

链接:

JNIEnv接口函数表索引174。

参数:

envJNI 接口指针。

array:Java 数组。

index:数组下标。

value:新值。

异常:

如果index不是数组中的有效下标抛出ArrayIndexOutOfBoundsException。如果 value 的类不是数组元素类的子类抛出ArrayStoreException

New<PrimitiveType>Array例程

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

用于构造新基本类型数组对象的一系列操作。下表说明了具体的基本类型数组构造函数。用户应把 New<PrimitiveType>Array 替换为某个实际的基本类型数组构造函数例程名(见下表),然后将 ArrayType 替换为该例程相应的数组类型。

New<PrimitiveType>Array 数组构造函数系列

New<PrimitiveType>Array例程名ArrayType
NewBooleanArray()jbooleanArray
NewByteArray()jbyteArray
NewCharArray()jcharArray
NewShortArray()jshortArray
NewIntArray()jintArray
NewLongArray()jlongArray
NewFloatArray()jfloatArray
NewDoubleArray()jdoubleArray

链接:

JNIEnv接口函数表索引如下。

New<PrimitiveType>Array例程名索引
NewBooleanArray()175
NewByteArray()176
NewCharArray()177
NewShortArray()178
NewIntArray()179
NewLongArray()180
NewFloatArray()181
NewDoubleArray()182

参数:

envJNI 接口指针。

length:数组长度。

返回值:

Java 数组。如果无法构造该数组,则为 NULL

Get<PrimitiveType>ArrayElements例程

c++
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 类型的指针,其中每一字节代表一个元素(未包装的表示)。所有其它类型的数组在内存中确保是连续的。

Get<PrimitiveType>ArrayElements例程名ArrayTypeNativeType
GetBooleanArrayElements()jbooleanArrayjboolean
GetByteArrayElements()jbyteArrayjbyte
GetCharArrayElements()jcharArrayjchar
GetShortArrayElements()jshortArrayjshort
GetIntArrayElements()jintArrayjint
GetLongArrayElements()jlongArrayjlong
GetFloatArrayElements()jfloatArrayjfloat
GetDoubleArrayElements()jdoubleArrayjdouble

链接:

JNIEnv接口函数表索引如下。

Get<PrimitiveType>ArrayElements例程名索引
GetBooleanArrayElements()183
GetByteArrayElements()184
GetCharArrayElements()185
GetShortArrayElements()186
GetIntArrayElements()187
GetLongArrayElements()188
GetFloatArrayElements()189
GetDoubleArrayElements()190

参数:

envJNI 接口指针。

arrayJava 字符串对象。

isCopy:指向布尔值的指针。

返回值:

返回指向数组元素的指针,如果操作失败,则为 NULL

Release<PrimitiveType>ArrayElements例程

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

通知虚拟机本地代码无需再访问 elems 的一组函数。elems 参数是一个通过使用对应的 Get<PrimitiveType>ArrayElements() 函数由 array 导出的指针。必要时,该函数将把对 elems 的修改复制回原始数组。

mode 参数将提供有关如何释放数组缓冲区的信息。如果 elems 不是 array 中数组元素的副本,mode 将无效。否则,mode 将具有下表所述的功能:

modeactions
0复制回内容并释放 elems缓冲区
JNI_COMMIT复制回内容但不释放 elems缓冲区
JNI_ABORT释放缓冲区但不复制回可能的更改

多数情况下,编程人员将把"0"传给 mode 参数以确保固定的数组和复制的数组保持一致。其它选项可以使编程人员进一步控制内存管理,但使用时务必慎重。

下表说明了构成基本类型数组处理程序系列的具体例程。应进行如下替换;

  • Release<PrimitiveType>ArrayElements 替换为下表中的某个实际基本类型数组撤处理程序例程名。

  • ArrayType 替换为对应的数组类型。

  • NativeType 替换为该例程对应的本地类型。

Release<PrimitiveType>ArrayElements例程名ArrayTypeNativeType
ReleaseBooleanArrayElements()jbooleanArrayjboolean
ReleaseByteArrayElements()jbyteArrayjbyte
ReleaseCharArrayElements()jcharArrayjchar
ReleaseShortArrayElements()jshortArrayjshort
ReleaseIntArrayElements()jintArrayjint
ReleaseLongArrayElements()jlongArrayjlong
ReleaseFloatArrayElements()jfloatArrayjfloat
ReleaseDoubleArrayElements()jdoubleArrayjdouble

链接:

JNIEnv接口函数表索引如下。

Release<PrimitiveType>ArrayElements例程名索引
ReleaseBooleanArrayElements()191
ReleaseByteArrayElements()192
ReleaseCharArrayElements()193
ReleaseShortArrayElements()194
ReleaseIntArrayElements()195
ReleaseLongArrayElements()196
ReleaseFloatArrayElements()197
ReleaseDoubleArrayElements()198

参数:

envJNI 接口指针。

arrayJava 数组对象。

elems:指向数组元素的指针。

mode:释放模式。

Get<PrimitiveType>ArrayRegion例程

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

将基本类型数组某一区域复制到缓冲区中的一组函数。

下表说明了具体的基本类型数组元素访问器。应进行如下替换:

  • Get<PrimitiveType>ArrayRegion 替换为下表中的某个实际基本类型元素访问器例程名。
  • ArrayType 替换为对应的数组类型。
  • NativeType 替换为该例程对应的本地类型。
Get<PrimitiveType>ArrayRegion例程名ArrayTypeNativeType
GetBooleanArrayRegion()jbooleanArrayjboolean
GetByteArrayRegion()jbyteArrayjbyte
GetCharArrayRegion()jcharArrayjchar
GetShortArrayRegion()jshortArrayjshort
GetIntArrayRegion()jintArrayjint
GetLongArrayRegion()jlongArrayjlong
GetFloatArrayRegion()jfloatArrayjloat
GetDoubleArrayRegion()jdoubleArrayjdouble

链接:

JNIEnv接口函数表索引如下。

Get<PrimitiveType>ArrayRegion例程名索引
GetBooleanArrayRegion()199
GetByteArrayRegion()200
GetCharArrayRegion()201
GetShortArrayRegion()202
GetIntArrayRegion()203
GetLongArrayRegion()204
GetFloatArrayRegion()205
GetDoubleArrayRegion()206

参数:

envJNI 接口指针。

arrayJava 指针。

start:起始下标。

len:要复制的元素数。

buf:目的缓冲区。

异常:

如果区域中的某个下标无效,抛出ArrayIndexOutOfBoundsException

Set<PrimitiveType>ArrayRegion例程

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

将基本类型数组的某一区域从缓冲区中复制回来的一组函数。

下表说明了特定的基本类型数组元素访问器。应进行如下替换:

  • Set<PrimitiveType>ArrayRegion 替换为表中的实际基本类型元素访问器例程名。

  • ArrayType 替换为对应的数组类型。

  • NativeType 替换为该例程对应的本地类型。

Set<PrimitiveType>ArrayRegion例程名ArrayTypeNativeType
SetBooleanArrayRegion()jbooleanArrayjboolean
SetByteArrayRegion()jbyteArrayjbyte
SetCharArrayRegion()jcharArrayjchar
SetShortArrayRegion()jshortArrayjshort
SetIntArrayRegion()jintArrayjint
SetLongArrayRegion()jlongArrayjlong
SetFloatArrayRegion()jfloatArrayjfloat
SetDoubleArrayRegion()jdoubleArrayjdouble

链接:

JNIEnv接口函数表索引如下。

Set<PrimitiveType>ArrayRegion例程名索引
SetBooleanArrayRegion()207
SetByteArrayRegion()208
SetCharArrayRegion()209
SetShortArrayRegion()210
SetIntArrayRegion()211
SetLongArrayRegion()212
SetFloatArrayRegion()213
SetDoubleArrayRegion()214

参数:

envJNI 接口指针。

array: Java 数组。

start:起始下标。

len:要复制的元素数。

buf:源缓冲区。

异常:

如果区域中的某个下标无效抛出ArrayIndexOutOfBoundsException

注意:使用JDK/JRE 1.1的程序员可以使用Get/Release<primitivetype>ArrayElements函数获取基本数组元素的指针。如果虚拟机支持固定,则返回指向原始数据的指针;否则,返回是的副本。JDK/JRE 1.3中引入的新函数允许本地代码获取一个直接指向数组元素的指针,即使虚拟机不支持固定。

GetPrimitiveArrayCritical,ReleasePrimitiveArrayCritical

c++
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

这两个函数的语义与现有的 Get/Release<primitivetype>ArrayElements 函数非常相似。如果可能的话,虚拟机将返回指向原始数组的指针;否则,将制作副本。但是如何使用这些功能有重大限制。

在调用GetPrimitiveArrayCritical之后,本地代码在调用ReleasePrimitiveArrayCritical之前不应长时间运行。我们必须将这对函数间的代码视为在“临界区”中运行。在临界区内,本地代码不得调用其他JNI函数,或任何可能导致当前线程阻止并等待其他Java线程的系统调用。(例如,当前线程不得在另一个Java线程正在写的流上调用read。)

这些限制使得本地代码更有可能获取数组的非复制版本,即使虚拟机不支持固定。例如,当本地代码通过GetPrimitiveArrayCritical获取数组的指针时,虚拟机可能暂时禁用垃圾回收。

可能存在多个GetPrimtiveArrayCriticalReleasePrimitiveArrayCritical嵌套。例如:

c++
  jint len = (*env)->GetArrayLength(env, arr1);
  jbyte *a1 = (*env)->GetPrimitiveArrayCritical(env, arr1, 0);
  jbyte *a2 = (*env)->GetPrimitiveArrayCritical(env, arr2, 0);
  /* We need to check in case the VM tried to make a copy. */
  if (a1 == NULL || a2 == NULL) {
    ... /* out of memory exception thrown */
  }
  memcpy(a1, a2, len);
  (*env)->ReleasePrimitiveArrayCritical(env, arr2, a2, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, arr1, a1, 0);

注意,如果虚拟机内部以不同的格式表示数组,GetPrimitiveArrayCritical仍然可能生成数组的副本。因此,我们需要检查它的返回值是否为NULL,以防出现内存不足的情况。

链接(GetPrimitiveArrayCritical):

JNIEnv接口函数表索引222。

链接(ReleasePrimitiveArrayCritical):

JNIEnv接口函数表索引223。

自从:

JDK/JRE 1.2

注册本地方法

RegisterNatives

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

clazz 参数指定的类注册本地方法。methods 参数将指定 JNINativeMethod 结构的数组,其中包含本地方法的名称、签名和函数指针。JNINativeMethod结构的namesignature域指向modified UTF-8字符串。nMethods 参数将指定数组中的本地方法数。JNINativeMethod 结构定义如下所示:

c++
typedef struct {

    char *name;

    char *signature;

    void *fnPtr;

} JNINativeMethod;

函数指针通常必须有下列签名:

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

链接:

JNIEnv接口函数表索引215。

参数:

envJNI 接口指针。

clazzJava 类对象。

methods:类中的本地方法。

nMethods:类中的本地方法数。

返回值:

成功时返回 "0";失败时返回负数。

异常:

如果找不到指定的方法或方法不是本地方法抛出NoSuchMethodError

UnregisterNatives

c++
jint UnregisterNatives(JNIEnv *env, jclass clazz);

注销类的本地方法。类将回到链接或注册了本地方法函数前的状态。

该函数不应在普通本地代码中使用。相反,它可以为特殊程序提供一种重新加载和重新链接本地库的途径。

链接:

JNIEnv接口函数表索引216。

参数:

envJNI 接口指针。

clazzJava 类对象。

返回值:

成功时返回"0";失败时返回负数。

监视程序操作

MonitorEnter

c++
jint MonitorEnter(JNIEnv *env, jobject obj);

进入与 obj 所引用的基本 Java 对象相关联的监视器。obj引用必须不为NULL

每个 Java 对象都有一个相关联的监视器。如果当前线程已经拥有与 obj 相关联的监视器,它将递增指示该线程进入监视器次数的监视程序计数器。如果与 obj 相关联的监视器并非由某个线程所拥有,则当前线程将变为该监视器的所有者,同时将该监视器的计数器设置为 1。如果另一个线程已拥有与 obj 关联的监视器,则在监视器被释放前当前线程将处于等待状态。监视程序被释放后,当前线程将尝试重新获得所有权。

通过JNI函数MonitorEnter进入的监视器不能使用Java虚拟机指令monitorexit或者synchronized 方法返回退出。JNI函数MonitorEnter调用和Java虚拟机指令monitorenter可能竞争同一个对象相关联的监视器。

为了避免死锁,除非使用DetachCurrentThread调用隐式释放JNI监视器,通过JNI调用函数MonitorEnter进入的监视器必须通过JNI调用函数MonitorExit退出。

链接:

JNIEnv接口函数表索引217。

参数:

envJNI 接口指针。

obj:常规 Java 对象或类对象。

返回值:

成功时返回"0";失败时返回负数。

MonitorExit

c++
jint MonitorExit(JNIEnv *env, jobject obj);

当前线程必须是与 obj 所引用的基本 Java 对象相关联的监视器的所有者。线程将递减指示进入监视程序次数的计数器。如果计数器的值变为 0,当前线程释放监视程序。

本地代码不能使用MonitorExit来退出通过synchronized 方法或者Java虚拟机指令monitorenter进入的监视器。

链接:

JNIEnv接口函数表索引218。

参数:

envJNI 接口指针。

obj:普通 Java 对象或类对象。

返回值:

成功时返回"0";失败时返回负数。

异常:

如果当前线程不拥有该监视器,抛出IllegalMonitorStateException

NIO支持

NIO相关的入口点允许本地代码访问java.nio直接缓冲区。直接缓冲区的内容能够驻留在普通的垃圾回收的堆外的本地内存中。关于直接缓冲区的信息可以查看 New I/O APIsjava.nio.ByteBuffer类的规范。

JDK/JRE 1.4引入的三个新函数允许JNI代码创建,检查和操作直接缓冲区:

每种Java虚拟机实现都应该支持这三个函数,但是并不是每种实现都要求支持JNI访问直接缓冲区。如果JVM不支持这种访问,NewDirectByteBufferGetDirectBufferAddress函数必须总是返回NULL,并且GetDirectBufferCapacity函数必须总是返回-1。如果JVM确实支持这种访问,那必须实现这三个函数,并且返回一个合适的值。

NewDirectByteBuffer

c++
jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity);

分配并返回一个直接java.nio.ByteBuffer引用,指向从内存地址address开始,扩展capacity字节大小的内存块。

调用该函数并且返回所得的字节缓冲区对象到Java层面代码的本地代码应该确保缓冲区指向一个有效的内存区域,该区域可用于读取和(如果合适的话)写入。试图从Java代码访问无效内存位置将返回任意值,没有明显效果,或者导致抛出未指定的异常。

链接:

JNIEnv接口函数表索引229。

参数:

env: JNIEnv接口指针。

address: 内存区域的起始地址(必须不为NULL)。

capacity: 内存区域的字节大小 (必须是正数)。

返回:

返回新实例化java.nio.ByteBuffer对象的本地引用。如果异常发生或者虚拟机不支持JNI访问直接缓冲区,返回NULL

异常:

如果ByteBuffer对象分配失败,抛出OutOfMemoryError

自从:

JDK/JRE 1.4

GetDirectBufferAddress

c++
void* GetDirectBufferAddress(JNIEnv* env, jobject buf);

获取并返回由给定的直接java.nio.Buffer引用的内存区域的起始地址。

该函数允许本地代码访问Java代码通过缓冲区对象访问的相同内存区域。

链接:

JNIEnv接口函数表索引230。

参数:

env: JNIEnv接口指针。

buf: 直接java.nio.Buffer对象(必须不为NULL)。

返回:

返回缓存区引用的内存区域的起始地址。如果内存区域未定义,给定的对象不是直接java.nio.Buffer或者虚拟机不支持JNI访问直接缓冲区,返回NULL

自从:

JDK/JRE 1.4

GetDirectBufferCapacity

c++
jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf);

获取并返回由给定的直接java.nio.Buffer引用的内存区域的容量。容量是指内存区域包含的元数数量。

链接:

JNIEnv接口函数表里索引231。

参数:

env: JNIEnv 接口指针

buf: 一个直接的 java.nio.Buffer 对象(一定不为NULL)

返回:

返回缓冲区相关的内存区域的容量。如果给定的对象不是直接java.nio.Buffer,或者对象是未对齐视图缓冲区并且处理器架构不支持不对齐访问,再或者虚拟机不支持JNI访问直接缓冲区,返回-1。

自从:

JDK/JRE 1.4

反射支持

如果程序员知道方法或者域的名称和类型,他们可以利用JNI调用Java方法或者访问Java域。Java核心反射API允许程序员在运行时访问Java类。JNI提供了一组JNI中域ID、方法IDJava核心反射API中的域和方法对象之间转换函数。

FromReflectedMethod

c++
jmethodID FromReflectedMethod(JNIEnv *env, jobject method);

把一个java.lang.reflect.Method或者java.lang.reflect.Constructor对象转换成一个方法ID

链接:

JNIEnv接口函数表里索引7。

自从:

JDK/JRE 1.2

FromReflectedField

c++
jfieldID FromReflectedField(JNIEnv *env, jobject field);

把一个java.lang.reflect.Field对象转成成一个域ID

链接:

JNIEnv接口函数表里索引8。

自从:

JDK/JRE 1.2

ToReflectedMethod

c++
jobject ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);

把一个从cls产生的方法ID转换成一个java.lang.reflect.Method或者java.lang.reflect.Constructor 对象。如果methodID是静态的,isStatic必须设成JNI_TRUE,反之设成JNI_FALSE

可能抛出OutOfMemoryError,并且如果失败返回0。

链接:

JNIEnv接口函数表里索引9。

自从:

JDK/JRE 1.2

ToReflectedField

c++
jobject ToReflectedField(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);

把一个从cls产生的域ID转换成一个java.lang.reflect.Field对象。如果fieldID是一个静态的域,isStatic必须设成JNI_TRUE,反之设成JNI_FALSE

可能抛出OutOfMemoryError,并且如果失败返回0。

链接:

JNIEnv接口函数表里索引12。

自从:

JDK/JRE 1.2

Java虚拟机接口

GetJavaVM

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

返回与当前线程相关联的 Java 虚拟机接口(用于调用 API 中)。结果将放在第二个参数 vm 所指向的位置。

链接:

JNIEnv接口函数表里索引219。

参数:

envJNI 接 口 指 针 。

vm:指向放置结果的位置的指针。

返回值:

成功时返回"0";失败时返回负数。

调用API

调用 API 允许软件厂商将 Java 虚拟机加载到任意的本地程序中。厂商可以交付支持 Java 的应用程序,而不必链接 Java 虚拟机源代码。

本章首先概述了调用 API,接下来是所有调用 API 函数的参考。包括以下话题:

概述

以下代码示例说明了如何使用调用 API 中的函数。在本例中,C++ 代码创建Java 虚拟机并且调用名为 Main.test 的静态方法。为清楚起见,我们略去了错误检查。

c++
#include <jni.h>       /* 其中定义了所有的函数 */
...
JavaVM *jvm;       /* 表示一个java虚拟机 */
JNIEnv *env;       /* 指向本地方法接口的指针 */
JavaVMInitArgs vm_args; /* JDK/JRE 6 虚拟机初始化参数 */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* 加载并初始化一个Java虚拟机,返回一个JNI接口指针env */
JNI_CreateJavaVM(&jvm, &env, &vm_args);
delete options;
/* 用JNI调用Main.test方法 */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* 结束.卸载Java虚拟机 */
jvm->DestroyJavaVM();

本例使用了 API 中的三个函数。调用 API 允许本地应用程序使用 JNI 接口指针来访问虚拟机特性。其设计类似于 NetscapeJRI 嵌入式接口。

创建虚拟机

JNI_CreateJavaVM() 函数加载和初始化一个 Java 虚拟机,并返回一个JNI接口指针。调用JNI_CreateJavaVM() 的线程被视为主线程。

连接虚拟机

JNI 接口指针 (JNIEnv) 仅在当前线程中有效。如果另一个线程需要访问 Java 虚拟机,那么该线程首先必须调用 AttachCurrentThread()方法,使得自身连接到虚拟机并获得 JNI 接口指针。连接到虚拟机之后,本地线程的工作方式就与在本地方法内运行的普通 Java 线程一样了。本地线程保持与虚拟机的连接,直到调用 DetachCurrentThread() 时才断开连接。

连接虚拟机的线程应当有足够的栈空间来执行合理量的工作。每个线程的栈空间分配是操作系统指定的。例如,使用pthreads线程,栈的大小能通过pthread_create方法传入pthread_attr_t参数来指定。

断开虚拟机连接

连接到虚拟机的本地线程在退出之前必须调用DetachCurrentThread() 方法来断开与虚拟机的连接。如果在调用栈中有Java方法,线程不能够断开与虚拟机的连接。

卸载虚拟机

JNI_DestroyJavaVM()方法卸载一个Java虚拟机。

虚拟机等到主线程成为唯一的非守护用户线程时才真正地卸载。用户线程包括 Java 线程和附加的本地线程。之所以存在这种限制是因为 Java 线程或附加的本地线程可能正占用着系统资源,例如锁,窗口等。虚拟机不能自动释放这些资源。通过限制当前线程是虚拟机卸载时唯一运行的线程,释放任意线程所占用系统资源的负担落到程序员身上。

库和版本管理

一旦一个本地库被加载,该库对于所有类加载器都是可见的。因此,不同加载器中的两个类可能链接同一个本地方法。这导致两个问题:

  • 类可能会错误地与本地库链接,因为本地库被装载到相同类名却不同的类加载器的类中。
  • 本地方法可以轻易地混合或混淆来自不同类加载器的类。 这打破了类加载器提供的命名空间隔离,并导致类型安全问题。

每个类加载器管理它自己的本地库的集合。相同的JNI本地库不能加载到多个类加载器中。如果这样做会导致抛出UnsatisfiedLinkError。例如,当System.loadLibrary用于加载本地库到两个加载器中时,会抛出UnsatisfiedLinkError。这种新方法的好处是:

  • 类加载器提供的命名空间隔离在本地库中得到保持。本地库不会轻易的混合或者混淆来自不同加载器的类。
  • 除之之外,当本地库相关的类加载器被垃圾回收,本地库不能被卸载。

为了促进版本控制和资源管理,JNI库J可选择导出以下两个函数:

JNI_OnLoad

c++
jint JNI_OnLoad(JavaVM *vm, void *reserved);

当本地库被加载时(例如通过System.loadLibrary),虚拟机调用JNI_OnLoad函数。JNI_OnLoad必须返回本地库需要的JNI版本。

为了使用任何新的JNI函数,本地库必须导出返回JNI_VERSION_1_2JNI_OnLoad函数。如果本地库没有导出JNI_OnLoad函数,虚机机会假定本地库仅要求的JNI版本是JNI_VERSION_1_1。如果虚拟机没有识别JNI_OnLoad函数返回的版本号,虚拟机将卸载库并像从未加载过一样。

c++
JNI_Onload_L(JavaVM *vm, void *reserved);

如果一个库L是静态链接,然后通过第一次调用 System.loadLibrary("L")或者同等的API,将使用与JNI_OnLoad函数指定的相同参数和预期返回值调用JNI_OnLoad_L函数。JNI_OnLoad_L必须返回本地库需要的JNI版本。该版本必须是JNI_VERSION_1_8或更高版本。同样,如果虚拟机没有识别JNI_OnLoad_L函数返回的版本号,虚拟机将卸载库并像从未加载过一样。

链接:

从包含本地方法实现的本地库导出。

JNI_OnUnload

c++
void JNI_OnUnload(JavaVM *vm, void *reserved);

当包含本地库的类加载器被垃圾回收时,虚拟机会调用JNI_OnUnload。这个函数用来执行清理操作。由于这个函数在未知的上下文(例如一个终结函数)中被调用,程序员应该对Java虚拟机服务保持保守的态度,避免任意的Java回调。

值得注意的是JNI_OnLoadJNI_OnUnloadJNI库提供的两个可选择的函数,并不是虚拟机暴露导出的。

c++
JNI_OnUnload_L(JavaVM *vm, void *reserved);

当包含一个静态链接的本地库L的类加载器被垃圾回收时,如果库导出了JNI_OnUnload_L函数,虚拟机就会调用该函数。这个函数用来执行清理操作。由于这个函数在未知的上下文(例如一个终结函数)中被调用,程序员应该对Java虚拟机服务保持保守的态度,避免任意的Java回调。

信息说明:

加载本地库的行为是使库及其本地入口点被感知并注册到Java虚拟机和运行时的完整过程。注意简单执行操作系统层面的加载本地库的操作,例如UNIX(R)系统中的dlopen,并不能完全达到这个目标。通常Java类加载器调用本地函数,以执行对主机操作系统的调用,该调用将加载库到内存中并返回一个本地库的句柄。该句柄将被存储,并在后续搜索本地库入口点时使用。一旦句柄成功的返回,表示注册库成功,Java本地类加载器将完成装入过程。

链接:

从包含本地方法实现的本地库导出。

调用API函数

JavaVM 类型是指向调用 API 函数表的指针。以下代码示例显示了这种函数表。

c++
typedef const struct JNIInvokeInterface *JavaVM;

const struct JNIInvokeInterface ... = {
    NULL,
    NULL,
    NULL,

    DestroyJavaVM,
    AttachCurrentThread,
    DetachCurrentThread,

    GetEnv,

    AttachCurrentThreadAsDaemon
};

注意, 这三个调用 API 函数JNI_GetDefaultJavaVMInitArgs()JNI_GetCreatedJavaVMs()JNI_CreateJavaVM()不是 JavaVM 函数表的一部分。不必先有 JavaVM 结构,就可以使用这些函数。

JNI_GetDefaultJavaVMInitArgs

c++
jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);

返回 Java 虚拟机的默认配置。在调用该函数之前,本地代码必须将vm_args->version 域设置为它所期望虚拟机支持的 JNI 版本。该函数返回后,将把 vm_args->version 设置为虚拟机支持的实际 JNI 版本。

链接:

从实现Java虚拟机的本地库导出。

参数:

vm_args:指向 JavaVMInitArgs结构体的指针,默认参数填入该结构。

返回值:

如果所请求的版本得到支持,则返回JNI_OK;如果所请求的版本未得到支持,则返回JNI错误码(负数)。

JNI_GetCreatedJavaVMs

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

返回已创建的所有 Java 虚拟机。将指向虚拟机的指针按照其创建顺序写入vmBuf 缓冲区。最多写入 bufLen 个条目数。在 *nVMs 中返回所创建虚拟机的总数。

不支持在单个进程中创建多个虚拟机。

链接:

从实现Java虚拟机的本地库导出。

参数:

vmBuf:指向将放置虚拟机结构体的缓冲区的指针。

bufLen:缓冲区的长度 。

nVMs:指向整数的指针。

返回值:

成功时返回JNI_OK;失败则返回合适的JNI错误码(负数)。

JNI_CreateJavaVM

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

加载并初始化 Java 虚拟机。当前线程成为主线程。将 env 参数设置为主线程的 JNI 接口指针。

不支持在单个进程中创建多个虚拟机。

JNI_CreateJavaVM的第二个参数总是JNIEnv *指针,而第三个参数是指向JavaVMInitArgs结构的指针,该结构使用选项字符串来编码任意虚拟机启动选项:

c++
typedef struct JavaVMInitArgs {
    jint version;

    jint nOptions;
    JavaVMOption *options;
    jboolean ignoreUnrecognized;
} JavaVMInitArgs;

options域是下面类型的数组:

c++
typedef struct JavaVMOption {
    char *optionString;  /* 默认平台编码的选项字符串 */
    void *extraInfo;
} JavaVMOption;

这个数组的大小是JavaVMInitArgsnOptions域指定的。如果ignoreUnrecognized 设置为JNI_TRUEJNI_CreateJavaVM 将忽略所有以"-X"或者"_"开头的无法识别的选项字符串。如果ignoreUnrecognized 设置为JNI_FALSE,一旦遇到任何无法识别的选项字符串,JNI_CreateJavaVM就会返回JNI_ERR。所有Java虚拟机都必须识别以下一组标准选项:

选项字符串含义
-D<name>=<value>设置一个系统属性
-verbose[:class|gc|jni]启用详细输出。 选项后面可以跟一个逗号分隔的名称列表,表明VM将打印哪种类型的信息。 例如,“ -verbose:gc,class”指示虚拟机打印垃圾回收和类加载相关信息。 标准名称包括:gc, classjni。 所有非标准(特定于虚拟机)名称必须以“X”开头。
vfprintfextraInfo是指向vfprintf钩子的指针。
exitextraInfo是指向exit钩子的指针。
abortextraInfo是指向abort钩子的指针。

此外,每个虚拟机实现能支持它自己的非标准选项字符串集合。非标准选项名字必须以"-X"或下划线("_")开头。例如,JDK/JRE支持 -Xms-Xmx选项来允许程序员设置具体的初始和最大堆大小。以"-X"开始的选项能够在Java命令行访问。

以下是在JDK / JRE中创建Java VM的示例代码:

c++
JavaVMInitArgs vm_args;
JavaVMOption options[4];

options[0].optionString = "-Djava.compiler=NONE"; /* 关闭 JIT */
options[1].optionString = "-Djava.class.path=c:\myclasses"; /*用户类路径 */
options[2].optionString = "-Djava.library.path=c:\mylibs";  /* 设置本地库路径 */
options[3].optionString = "-verbose:jni";  /* 打印JNI相关信息 */

vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;

/* 注意在 JDK/JRE中, 不再需要调用
 * JNI_GetDefaultJavaVMInitArgs.
 */
res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...

链接:

从实现Java虚拟机的本地库导出。

参数:

p_vm:指向所得到的虚拟机结构位置的指针。

p_env:指向主线程的JNI接口指针所在位置的指针。

vm_args: Java 虚拟机初始化参数。

返回值:

成功时返回JNI_OK;失败则返回一个合适的JNI错误代码(负数)。

DestroyJavaVM

c++
jint DestroyJavaVM(JavaVM *vm);

卸载 Java 虚拟机并回收资源。

任何线程,无论是否连接到虚拟机,都能调用这个函数。如果当前线程连接到虚拟机,则虚拟机将等待,直到当前线程是唯一的非守护用户级Java线程。 如果当前线程未连接到虚拟机,则当前线程会连接到虚拟机,然后等待,直到当前线程是唯一的非守护用户级线程。

链接:

JavaVM接口函数表里索引3。

参数:

vm:将销毁的 Java 虚拟机。

返回值:

成功时返回JNI_OK;失败则返回一个合适的JNI错误代码(负数)。

不支持卸载虚拟机。

AttachCurrentThread

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

将当前线程连接到 Java 虚拟机。在 JNIEnv 参数中返回 JNI 接口指针。

试图连接已经连接的线程是空操作。

本地线程不能同时连接到两个 Java 虚拟机上。

当线程连接到虚拟机时,上下文类加载器是bootstrap加载器。

链接:

JavaVM接口函数表里索引4。

参数:

vm: 当前线程所要连接到的虚拟机 。

p_env:指向当前线程的JNI接口指针所在位置的指针。

thr_args:可以是NULL或指向JavaVMAttachArgs结构的指针以指定额外的信息。

AttachCurrentThread的第二个参数总是JNIEnv指针。AttachCurrentThread的第三个参数是保留的,应该置为NULL。可以传入下面结构体指针来指定其他额外信息:

c++
typedef struct JavaVMAttachArgs {
    jint version;
    char *name;    /* 线程的名字(Modified UTF-8字符串),或者 NULL */
    jobject group; /* 一个ThreadGroup对象的全局引用, 或者为 NULL */
} JavaVMAttachArgs

返回值:

成功时返回JNI_OK;失败则返回一个合适的JNI错误代码(负数)。

AttachCurrentThreadAsDaemon

c++
jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);

AttachCurrentThread一样的含义,但是新创建的java.lang.Thread 实例是守护线程。

如果线程已经通过AttachCurrentThreadAttachCurrentThreadAsDaemon连接,则此例程只需将penv指向的值设置为当前线程的JNIEnv。 在这种情况下,AttachCurrentThread和此例程都不会对线程的守护状态产生任何影响。

链接:

JavaVM接口函数表里索引7。

参数:

vm: 当前线程所要连接到的虚拟机 。

penv:指向当前线程的JNI接口指针所在位置的指针。

args:指向JavaVMAttachArgs结构的指针。

返回值:

成功时返回JNI_OK;失败则返回一个合适的JNI错误代码(负数)。

异常:

DetachCurrentThread

c++
jint DetachCurrentThread(JavaVM *vm);

断开当前线程与 Java 虚拟机之间的连接。释放该线程占用的所有 Java 监视器。通知所有等待该线程终止的 Java 线程。

主线程能断开与虚拟机之间的连接。

链接:

JavaVM 接口函数表里索引5。

参数:

vm:当前线程将断开连接的虚拟机。

返回值:

成功时返回JNI_OK;失败则返回一个合适的JNI错误代码(负数)。

GetEnv

c++
jint GetEnv(JavaVM *vm, void **env, jint version);

链接:

JavaVM 接口函数表里索引6。

参数:

vm: 获取接口的虚拟机实例 env:指向当前线程的JNI接口指针所在位置的指针。 ​version:请求的 JNI 版本.

返回值:

如果当前线程没有连接到虚拟机,会设置*envNULL并且返回JNI_EDETACHED。如果指定的版本是不支持的,会设置*envNULL并且返回JNI_EVERSION。否则,会设置*env为合适的接口并且返回JNI_OK