离上一段实习结束也有段时间了,在这段实习中最大的收获就是学会了JNI。在这里翻译官网的java8版本的JNI手册,记录一下~
🍒官网先行
简介
本章介绍 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
接口
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
函数来与垃圾收集器进行显式的交互,而不是依赖于保守的垃圾收集。
在高层级上,Microsoft
的 Java/COM
接口为 Java
虚拟机提供了与语言无关的标准二进制接口。Java
代码可以像使用 Java
对象一样来使用 COM
对象。Java
类也可以作为 COM
类显示给系统的其余部分。
目标
我们认为统一的,经过细致考虑的标准接口能够向每个用户提供以下好处:
每个虚拟机厂商都可以支持更多的平台相关代码。
工具制造商不必维护不同的本地方法接口。
应用程序设计人员可以只编写一种版本的平台相关代码就能够在不同的虚拟机上运行。
获得标准本地方法接口的最佳途径是联合所有对Java
虚拟机有兴趣的当事方。因此,我们在Java
获得许可方之间组织了一系列研讨会,对设计统一的本地方法接口进行了讨论。从研讨会可以明确地看出标准本地方法接口必须满足以下要求:
二进制兼容性 - 主要的目标是在给定平台上的所有
Java
虚拟机实现之间实现本地方法库的二进制兼容性。对于给定平台,程序员只需要维护一种版本的本地方法库。效率 - 若要支持时限代码,本地方法接口必须增加一点系统开销。所有已知的用于确保虚拟机无关性(因而具有二进制兼容性)的技术都会占用一定的系统开销。我们必须以某种方式在效率和
VM
独立性之间达成妥协。功能 - 接口必须显示足够的
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 虚拟机上运行的最好保证。
如果您正在实现 Java
虚拟机,那么您应该实现 JNI
。JNI
已经经过时间的考验,并确保不对您的VM
实施施加任何开销或限制,包括对象表示,垃圾收集方案等。 如果您遇到我们可能忽略的任何问题,请将您的反馈发送给我们。
变化
从Java SE 6.0
开始,删除了过时的结构体JDK1_1InitArgs
和JDK1_1AttachArgs
,而是使用JavaVMInitArgs
和JavaVMAttachArgs
。
设计概述
本章着重讨论 JNI
中的主要设计问题,其中的大部分问题都与本地方法有关。调用 API
的设计将在章节[调用 API](#调用 API)中讨论。
本章包含以下的话题:
JNI接口函数和指针
平台相关代码是通过调用 JNI
函数来访问 Java 虚拟机功能的。JNI
函数可通过接口指针来获得。接口指针是指针的指针,它指向一个指针数组,而指针数组中的每个元素又指向一个接口函数。每个接口函数都处在数组的一个预定偏移量中。下图说明了接口指针的组织结构。
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
的定义:
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
不是本地方法,因而它并不在本地库中。
class Cls1 {
int g(int i);
native int g(double d);
}
我们采取简单的名字修饰方案,以保证所有的 Unicode
字符都能被转换为有效的 C
函数名。我们用下划线("_
") 字符来代替全限定的类名中的斜杠("/
")。由于名称或类型描述符从来不会以数字打头,我们用 _0
,...
,_9
来代替转义字符序列,如下表所示:
Unicode 字符转换
转义字符序列 | 表示 |
---|---|
_0XXXX | Unicode 字符 XXXX . 请注意,小写用于表示非ASCII Unicode 字符,例如_0abcd 而不是 _0ABCD . |
_1 | 字符"_ " |
_2 | 签名中的字符"; " |
_3 | 签名中的字符"[ " |
本地方法和接口 API
都要遵守给定平台上的库调用标准约定。例如,UNIX
系统使用 C
调用约定,而Win32
系统使用 __stdcall
。
本地方法的参数
JNI
接口指针是本地方法的第一个参数。其类型是 JNIEnv
。第二个参数随本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其 Java
类的引用。
其余的参数对应于通常 Java
方法的参数。本地方法调用利用返回值将结果传回调用程序中。JNI 的类型和数据结构将描述 Java
类型和 C
类型之间的映射。
下面代码示例说明了如何用 C
函数来实现本地方法 f
。对本地方法 f
的声明如下:
package pkg;
class Cls {
native double f(int i, String s);
// ...
}
具有长修饰名称 Java_pkg_Cls_f_ILjava_lang_String_2
的 C
函数实现本地方法 f:
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++
将此代码写得稍微简洁一些,如下代码所示:
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
,如下所示:
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
数组的函数并不返回错误代码,但可能会抛出ArrayIndexOutOfBoundsException
或ArrayStoreException
。
在所有其它情况下,返回值如果不是错误代码值就可确保没有抛出异常。
异步异常
在多个线程的情况下,当前线程以外的其它线程可能会抛出异步异常。异步异常并不立即影响当前线程中本地代码的执行,直到出现下列情况:
本地代码调用某个有可能抛出同步异常的
JNI
函数,或者本地代码用
ExceptionOccurred()
显式检查同步异常和异步异常。
注意,只有那些有可能抛出同步异常的 JNI
函数才检查异步异常。
本地方法应在必要的地方(例如,在一个没有其它异常检查的紧密循环中)插入
ExceptionOccurred()
检查以确保当前线程可在适当时间内对异步异常作出响应。
异常的处理
可用两种方法来处理本地代码中的异常:
本地方法可选择立即返回,使异常在启动该本地方法调用的
Java
代码中抛出。本地代码可通过调用
ExceptionClear()
来清除异常,然后执行自己的异常处理代码。
抛出了某个异常之后,本地代码必须先清除异常,然后才能进行其它的 JNI
调用。当有待定异常时,只有以下这些 JNI
函数可被安全地调用:
ExceptionOccurred()
ExceptionDescribe()
ExceptionClear()
ExceptionCheck()
ReleaseStringChars()
ReleaseStringUTFChars()
ReleaseStringCritical()
Release<Type>ArrayElements()
ReleasePrimitiveArrayCritical()
DeleteLocalRef()
DeleteGlobalRef()
DeleteWeakGlobalRef()
MonitorExit()
PushLocalFrame()
PopLocalFrame()
JNI 的类型和数据结构
本章讨论 JNI
如何将 Java
类型映射到本地 C
类型。
本章包含以下话题:
基本类型
下表描述 Java
基本类型及其与计算机相关的本地等效类型。
基本类型和本地等效类型
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 | not applicable |
为了使用方便,提供以下定义:
#define JNI_FALSE 0
#define JNI_TRUE 1
jsize 整数类型用于描述基本的索引和大小:
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
一样。例如:
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
虚拟机的类型签名表示。下表列出了这些类型签名。
Java 虚拟机类型签名
类型签名 | Java类型 |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
L fully-qualified-class ; | fully-qualified-class |
[ type | type[] |
( arg-types ) ret-type | method 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
字符的字符序列可以表示每个字符仅使用一个字节,但可以表示所有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
指针检查。
本章的部分资料改编自 Netscape
的 JRI
文档。
该参考资料按用法对函数进行组织。参考部分按下列函数区域进行组织:
接口函数表
每个函数均可通过 JNIEnv
参数以固定偏移量进行访问。JNIEnv
的类型是一个指针,指向存储全部 JNI
函数指针的结构体。其定义如下:
typedef const struct JNINativeInterface *JNIEnv;
VM
初始化函数表,如以下代码示例所示。注意:前三项留作将来与 COM
兼容。此外,我们在函数表开头部分也留出来多个 NULL
项,从而可将将来与类有关的 JNI
操作添加到 FindClass
后面,而非函数表的末尾。
注意,函数表可在所有 JNI
接口指针间共享。
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
jint GetVersion(JNIEnv *env);
返回本地方法接口的版本。
链接:
JNIEnv
接口函数表索引4。
参数:
env
:JNI
接口指针。
返回值:
高 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
:
#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
:
#define JNI_VERSION_1_4 0x00010004
自从JDK/JRE 1.6
:
#define JNI_VERSION_1_6 0x00010006
类操作
DefineClass
jclass DefineClass(JNIEnv *env, const char *name, jobject loader,
const jbyte *buf, jsize bufLen);
从原始类数据的缓冲区中加载类。在DefineClass
调用返回后,VM不会引用包含原始类数据的缓冲区,如果需要,可以将其丢弃。
链接:
JNIEnv
接口函数表索引5。
参数:
env
:JNI
接口指针。
name
:被定义的类名或接口名。这个字符串是修正的UTF-8
编码。
loader
:分派给所定义的类的类加载器。
buf
:包含.class
文件数据的缓存区
bufLen
:缓冲区长度。
返回值:
返回 Java
类对象。如果出错则返回 NULL
。
抛出:
ClassFormatError
:如果类数据指定的类无效。
ClassCircularityError
:如果类或接口是自身的超类或超接口。
OutOfMemoryError
:如果系统内存不足。
SecurityException
:如果调用者试图在“java
”包树中定义一个类。
FindClass
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
的全限定名称是:
"java/lang/String"
java.lang.Object[]
数组类型签名是:
"[Ljava/lang/Object;"
链接:
JNIEnv
接口函数表索引6。
参数:
env
:JNI
接口指针。
name
:类全名(即包名后跟类名,之间由"/
"分隔)。如果该名称以"[
"(数组签名字符)打头,则返回一个数组类。字符串是modified UTF-8
编码
返回值:
返回类对象全名。如果找不到该类,则返回 NULL
。
抛出:
ClassFormatError
:如果类数据指定的类无效。
ClassCircularityError
:如果类或接口是自身的超类或超接口
NoClassDefFoundError
:如果找不到所请求的类或接口的定义
OutOfMemoryError
:如果系统内存不足。
GetSuperclass
jclass GetSuperclass(JNIEnv *env, jclass clazz);
如果 clazz
代表类而非类 object
,则该函数返回由 clazz
所指定的类的超类。
如果 clazz
指定类 object
或代表某个接口,则该函数返回 NULL
。
链接:
JNIEnv
接口函数表索引10。
参数:
env
:JNI
接口指针。
clazz
:Java
类对象。
返回值:
由 clazz
所代表的类的超类或 NULL
。
IsAssignableFrom
jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);
确定 clazz1
的对象是否可安全地强制转换为 clazz2
。
链接:
JNIEnv
接口函数表索引11。
参数:
env
:JNI
接口指针。
clazz1
:第一个类参数。
clazz2
:第二个类参数。
返回值:
下列某个情况为真时返回 JNI_TRUE
:
第一及第二个类参数引用同一个
Java
类。第一个类是第二个类的子类。
第二个类是第一个类的某个接口。
异常
Throw
jint Throw(JNIEnv *env, jthrowable obj);
抛出 java.lang.Throwable
对象。
链接:
JNIEnv
接口函数表索引13。
参数:
env
:JNI
接口指针。
obj
:java.lang.Throwable
对象。
返回值:
成功时返回 0,失败时返回负数。
抛出:
java.lang.Throwable
对象 obj
。
ThrowNew
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
利用指定类的消息(由 message
指定)构造异常对象并抛出该异常。
链接:
JNIEnv
接口函数表索引14。
参数:
env
:JNI
接口指针。
clazz
:java.lang.Throwable
的 子 类 。
message
:用于构造 java.lang.Throwable
对象的消息。
返回值:
成功时返回 0,失败时返回负数。
抛出:
新构造的 java.lang.Throwable
对象。
ExceptionOccurred
jthrowable ExceptionOccurred(JNIEnv *env);
确定是否某个异常正被抛出。在本地代码调用 ExceptionClear()
或 Java
代码处理该异常前,异常将始终保持抛出状态。
链接:
JNIEnv
接口函数表索引15。
参数:
env
:JNI
接口指针。
返回值:
返回正被抛出的异常对象,如果当前无异常被抛出,则返回 NULL
。
ExceptionDescribe
void ExceptionDescribe(JNIEnv *env);
将异常及堆栈的回溯输出到系统错误报告信道(例如 stderr
)。该例程可便利调试操作。
链接:
JNIEnv
接口函数表索引16。
参数:
env
:JNI
接口指针。
ExceptionClear
void ExceptionClear(JNIEnv *env);
清除当前抛出的任何异常。如果当前无异常,则此例程不产生任何效果。
链接:
JNIEnv
接口函数表索引17。
参数:
env
:JNI
接口指针。
FatalError
void FatalError(JNIEnv *env, const char *msg);
抛出致命错误并且不希望虚拟机进行修复。该函数无返回值。
链接:
JNIEnv
接口函数表索引18。
参数:
env
:JNI
接口指针。
msg
:错误消息。modified UTF-8
编码的字符串。
ExceptionCheck
我们引入了便利函数,以检查未决异常,而无需创建对异常对象的本地引用。
jboolean ExceptionCheck(JNIEnv *env);
当存在未决的异常时返回JNI_TRUE
,否则返回JNI_FALSE
。
链接:
JNIEnv
接口函数表索引228。
自从:
JDK/JRE 1.2
全局及局部引用
全局引用
NewGlobalRef
jobject NewGlobalRef(JNIEnv *env, jobject obj);
创建 obj
参数所引用对象的新全局引用。obj
参数既可以是全局引用,也可以是局部引用。全局引用通过调用 DeleteGlobalRef()
来显式释放。
链接:
JNIEnv
接口函数表索引21。
参数:
env
:JNI
接口指针。
obj
:全局或局部引用。
返回值:
返回给定obj
的全局引用。
可能在以下情况返回NULL
:
obj
指向null
。- 系统内存不足。
obj
是一个弱全局引用,已经被垃圾回收了。
DeleteGlobalRef
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
删除 globalRef
所指向的全局引用。
链接:
JNIEnv
接口函数表索引22。
参数:
env
:JNI
接口指针。
globalRef
:全局引用。
局部引用
局部引用在本机方法调用期间有效。它们在本机方法返回后自动释放。每个本地引用都会消耗一定数量的Java虚拟机资源。程序员需要确保本地方法不会过度分配本地引用。尽管本地方法返回Java
后会自动释放本地引用,但过多分配本地引用可能会导致虚拟机在执行本地方法期间内存耗尽。
DeleteLocalRef
void DeleteLocalRef(JNIEnv *env, jobject localRef);
删除 localRef
所指向的局部引用。
链接:
JNIEnv
接口函数表索引23。
参数:
env
:JNI
接口指针。
localRef
:局部引用。
注意:JDK/JRE 1.1
提供上述的DeleteLocalRef
使得程序员能够手动删除局部引用。例如,如果本地代码遍历一个可能很大的对象数组并在每次迭代中使用一个元素,在下一次迭代中创建新的局部引用之前,删除对不再使用的数组元素的局部引用是一个很好的实践。
从JDK/JRE 1.2
开始,提供了一组额外的功能,用于局部引用生命周期管理。它们是下面列出的四个函数。
EnsureLocalCapactiy
jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
确保至少给定数量的本地引用可以在当前线程中被创建。成功返回0;否则返回负数或者抛出OutOfMemoryError
。
在进入本地方法前,虚拟机自动确保至少可以创建16个本地引用。
为了向后兼容,虚拟机分配的本地引用超过了保证的容量。(作为调试支持,虚拟机可能会警告用户正在创建过多的本地引用。在JDK
中,程序员可以提供-verbose:jni
命令行选项来打开这些信息。)如果不能在确保的容量之外创建更多的本地引用,虚拟机将调用FatalError
。
链接:
JNIEnv
接口函数表索引26。
自从:
JDK/JRE 1.2
PushLocalFrame
jint PushLocalFrame(JNIEnv *env, jint capacity);
创建一个新的局部引用帧,其中至少可以创建给定数量的局部引用。成功返回0,失败返回负数或者潜在的OutOfMemoryError
。
注意在以前的局部帧中创建的局部引用在当前的局部帧中仍然有效。
链接:
JNIEnv
接口函数表索引19。
自从:
JDK/JRE 1.2
PopLocalFrame
jobject PopLocalFrame(JNIEnv *env, jobject result);
弹出当前局部引用帧,释放所有局部引用,并返回给定result
对象前一个局部引用帧的一个局部引用。
如果不需要返回前一帧的引用,则将NULL
传递给result
。
链接:
JNIEnv
接口函数表索引20。
自从:
JDK/JRE 1.2
NewLocalRef
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
函数NewLocalRef
和NewGlobalRef
来获取底层对象的局部或全局引用。如果对象已经释放,这些函数将返回NULL
。否则,新的引用将阻止底层对象的释放。新的引用,如果不为NULL
,能够用于访问底层的对象,并且当不在需要访问时删除。
NewWeakGlobalRef
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
创建一个新的弱全局引用。如果obj
指向null
或者虚拟机内存不足,返回NULL
。如果虚拟机内存耗尽将抛出OutOfMemoryError
。
链接:
JNIEnv
接口函数表索引226。
自从:
JDK/JRE 1.2
DeleteWeakGlobalRef
void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
删除给定弱全局引用所需的虚拟机资源。
链接:
JNIEnv
接口函数表索引227。
自从:
JDK/JRE 1.2
对象操作
AllocObject
jobject AllocObject(JNIEnv *env, jclass clazz);
分配新 Java
对象而不调用该对象的任何构造函数。返回该对象的引用。
clazz
参数务必不要引用数组类。
链接:
JNIEnv
接口函数表索引27。
参数:
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, 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。
参数:
env
:JNI
接口指针。
clazz
:Java
类对象。
methodID
:构造函数的方法 ID
。
NewObject
的其它参数:
传给构造函数的参数。
NewObjectA
的其它参数:
args
:传给构造函数的参数数组。
NewObjectV
的其它参数 :
args
:传给构造函数的参数 va_list
。
返回值:
返回 Java
对象,如果无法构造该对象,则返回 NULL
。
抛出 :
InstantiationException
:如果该类为接口或抽象类。
OutOfMemoryError
:如果系统内存不足。
构造函数抛出的任何异常。
GetObjectClass
jclass GetObjectClass(JNIEnv *env, jobject obj);
返回对象的类。
链接:
JNIEnv
接口函数表索引31。
参数:
env
:JNI
接口指针。
obj
:Java
对象(不能为 NULL
)。
返回值:
返回 Java
类对象。
GetObjectRefType
jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);
返回obj
参数引用对象的类型。参数obj
可以是局部引用、全局引用或弱全局引用。
链接:
JNIEnv
接口函数表索引232。
参数:
env
: JNI
接口指针。
obj
: 一个局部引用、全局引用或弱全局引用。
返回值:
GetObjectRefType
函数返回jobjectRefType
定义的枚举值之一:
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
jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
检测对象是否为某个类的实例。
链接:
JNIEnv
接口函数表索引32。
参数:
env
:JNI
接口指针。
obj
:Java
对象。
clazz
:Java
类对象。
返回值:
如果可将 obj
强制转换为 clazz
,则返回 JNI_TRUE
。否则返回 JNI_FALSE
。NULL
对象可强制转换为任何类。
IsSameObject
jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);
检测两个引用是否引用同一 Java
对象。
链接:
JNIEnv
接口函数表索引24。
参数:
env
:JNI
接口指针。
ref1
:Java
对象。
ref2
:Java
对象。
返回值:
如果 ref1
和 ref2
引用同一 Java
对象或均为 NULL
,则返回 JNI_TRUE
。否则返回 JNI_FALSE
。
访问对象的域
GetFieldID
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
返回类的实例(非静态)域的域 ID
。该域由其名称及签名指定。访问器函数的Get<type>Field
及 Set<type>Field
系列使用域 ID
检索对象域。
GetFieldID()
将导致未初始化的类初始化。
GetFieldID()
不能用于获取数组的长度域。应使用 GetArrayLength()
。
链接:
JNIEnv
接口函数表索引94。
参数:
env
:JNI
接口指针。
clazz
:Java
类对象。
name
: 域名(0终结的 modifiedUTF-8
字符串)。
sig
:域签名(0终结的 modifiedUTF-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 替换为该例程对应的本地类型。
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 |
参数:
env
:JNI
接口指针。
obj
:Java
对象(不能为 NULL
)。
fieldID
:有效的域 ID
。
返回值:
域的内容。
Set<type>Field例程
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 |
参数:
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 终结的 modifiedUTF-8
字符串)。
sig
:方法签名(0 终结的 modifiedUTF-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, 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例程
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>Method
和 Call<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 |
参数:
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>Field
和SetStatic<type>Field
访问器函数系列使用域 ID
检索静态域。
GetStaticFieldID()
将未初始化的类初始化。
链接:
JNIEnv
接口函数表索引144。
参数:
env
:JNI
接口指针。
clazz
:Java
类对象。
name
: 静态域名(0终结的 modifiedUTF-8
字符串)。
sig
:域签名(0终结的 modifiedUTF-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
替换为该例程对应的本地类型。
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 |
参数:
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
替换为该例程对应的本地类型。
链接:
JNIEnv
接口函数表索引如下。
SetStatic<type>Field例程 | 索引 |
---|---|
SetStaticObjectField() | 154 |
SetStaticBooleanField() | 155 |
SetStaticByteField() | 156 |
SetStaticCharField() | 157 |
SetStaticShortField() | 158 |
SetStaticIntField() | 159 |
SetStaticLongField() | 160 |
SetStaticFloatField() | 161 |
SetStaticDoubleField() | 162 |
参数:
env
:JNI
接口指针。
clazz
:Java
类对象。
fieldID
:静态域 ID
。
value
:域的新值。
调用静态方法
GetStaticMethodID
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
返回类的静态方法的方法 ID。方法由其名称和签名指定。
GetStaticMethodID()
将导致未初始化的类初始化。
链接:
JNIEnv
接口函数表索引113。
参数:
env
:JNI
接口指针。
clazz
:Java
类对象。
name
:静态方法名(0 终结的 modifiedUTF-8
字符串)。
sig
:方法签名(0 终结的 modifiedUTF-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
接受这些参数并将其传给编程人员所要调用的 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 |
参数:
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
对象。
链接:
JNIEnv
接口函数表索引163。
参数:
env
:JNI
接口指针。
unicodeChars
:指向 Unicode
字符串的指针。
len
:Unicode
字符串的长度。
返回值:
Java
字符串对象。如果无法构造该字符串,则为 NULL
。
异常:
如果系统内存不足抛出OutOfMemoryError
。
GetStringLength
jsize GetStringLength(JNIEnv *env, jstring string);
返回 Java
字符串的长度(Unicode
字符数)。
链接:
JNIEnv
接口函数表索引164。
参数:
env
:JNI
接口指针。
string
:Java
字符串对象。
返回值:
Java
字符串的长度。
GetStringChars
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
返回指向字符串的 Unicode
字符数组的指针。该指针在调用ReleaseStringchars()
前一直有效。
如果 isCopy
非空,则在复制完成后将*isCopy
设为JNI_TRUE
。如果没有复制, 则设为JNI_FALSE
。
链接:
JNIEnv
接口函数表索引165。
参数:
env
:JNI
接口指针。
string:Java
字符串对象。
isCopy
:指向布尔值的指针。
返回值:
指向 Unicode
字符串的指针,如果操作失败,则返回 NULL
。
ReleaseStringChars
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
通知虚拟机本地代码无需再访问 chars
。参数 chars
是一个指针,可通过GetStringChars()
从 string
获得。
链接:
JNIEnv
接口函数表索引166。
参数:
env
:JNI
接口指针。
string
:Java
字符串对象。
chars
:指向 Unicode
字符串的指针。
NewStringUTF
jstring NewStringUTF(JNIEnv *env, const char *bytes);
利用 UTF-8
字符数组构造新 java.lang.String
对象。
链接:
JNIEnv
接口函数表索引167。
参数:
env
:JNI
接口指针。
bytes
:指向 UTF-8
字符串的指针。
返回值:
Java
字符串对象。如果无法构造该字符串,则为 NULL
。
异常:
如果系统内存不足抛出OutOfMemoryError
。
GetStringUTFLength
jsize GetStringUTFLength(JNIEnv *env, jstring string);
以字节为单位返回字符串的 UTF-8
长度。
链接:
JNIEnv
接口函数表索引168。
参数:
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
。
链接:
JNIEnv
接口函数表索引169。
参数:
env:JNI
接口指针。
string
:Java
字符串对象。
isCopy
:指向布尔值的指针。
返回值:
指向 UTF-8
字符串的指针。如果操作失败,则为 NULL
。
ReleaseStringUTFChars
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
通知虚拟机本地代码无需再访问 utf
。utf
参数是一个指针,可利用GetStringUTFChars()
从 string
获 得 。
链接:
JNIEnv
接口函数表索引170。
参数:
env
:JNI
接口指针。
string
:Java
字符串对象。
utf
:指向 UTF-8
字符串的指针。
注意:在JDK/JRE 1.1
中,程序员可以在用户提供的缓冲区中获得原始数组元素。从JDK/JRE 1.2
开始,提供了额外的函数,允许本地代在用户提供的缓冲区中获取Unicode(UTF-16)
或modified UTF-8
编码的字符。见以下函数。
GetStringRegion
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
从偏移量start
开始拷贝len
数的Unicode
字符到给定的buf
缓冲区。
如果下标越界抛出 StringIndexOutOfBoundsException
。
链接:
JNIEnv
接口函数表索引220。
自从:
JDK/JRE 1.2
GetStringUTFRegion
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
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
jsize GetArrayLength(JNIEnv *env, jarray array);
返回数组中的元素数。
链接:
JNIEnv
接口函数表索引171。
参数:
env
:JNI
接口指针。
array
:Java
数组对象。
返回值:
数组的长度。
NewObjectArray
jarray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
构造新的保存 elementClass
类型对象的数组。所有元素初始值均设为initialElement
。
链接:
JNIEnv
接口函数表索引172。
参数:
env
:JNI
接口指针。
length
:数组大小。
elementClass
:数组元素类。
initialElement
: 初始值 。
返回值:
Java
数组对象。如果无法构造数组,则为 NULL
。
异常:
如果系统内存不足抛出OutOfMemoryError
。
GetObjectArrayElement
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
返回 Object
数组的元素。
链接:
JNIEnv
接口函数表索引173。
参数:
env
:JNI
接口指针。
array
:Java
数组。
index
:数组下标。
返回值:
Java
对象。
异常:
如果 index
不是数组中的有效下标抛出ArrayIndexOutOfBoundsException
。
SetObjectArrayElement
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
设置 Object
数组的元素。
链接:
JNIEnv
接口函数表索引174。
参数:
env
:JNI
接口指针。
array
:Java 数组。
index
:数组下标。
value
:新值。
异常:
如果index
不是数组中的有效下标抛出ArrayIndexOutOfBoundsException
。如果 value
的类不是数组元素类的子类抛出ArrayStoreException
。
New<PrimitiveType>Array例程
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 |
参数:
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
类型的指针,其中每一字节代表一个元素(未包装的表示)。所有其它类型的数组在内存中确保是连续的。
Get<PrimitiveType>ArrayElements例程名 | ArrayType | NativeType |
---|---|---|
GetBooleanArrayElements() | jbooleanArray | jboolean |
GetByteArrayElements() | jbyteArray | jbyte |
GetCharArrayElements() | jcharArray | jchar |
GetShortArrayElements() | jshortArray | jshort |
GetIntArrayElements() | jintArray | jint |
GetLongArrayElements() | jlongArray | jlong |
GetFloatArrayElements() | jfloatArray | jfloat |
GetDoubleArrayElements() | jdoubleArray | jdouble |
链接:
JNIEnv
接口函数表索引如下。
Get<PrimitiveType>ArrayElements例程名 | 索引 |
---|---|
GetBooleanArrayElements() | 183 |
GetByteArrayElements() | 184 |
GetCharArrayElements() | 185 |
GetShortArrayElements() | 186 |
GetIntArrayElements() | 187 |
GetLongArrayElements() | 188 |
GetFloatArrayElements() | 189 |
GetDoubleArrayElements() | 190 |
参数:
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
将具有下表所述的功能:
mode | actions |
---|---|
0 | 复制回内容并释放 elems 缓冲区 |
JNI_COMMIT | 复制回内容但不释放 elems 缓冲区 |
JNI_ABORT | 释放缓冲区但不复制回可能的更改 |
多数情况下,编程人员将把"0"传给 mode
参数以确保固定的数组和复制的数组保持一致。其它选项可以使编程人员进一步控制内存管理,但使用时务必慎重。
下表说明了构成基本类型数组处理程序系列的具体例程。应进行如下替换;
将
Release<PrimitiveType>ArrayElements
替换为下表中的某个实际基本类型数组撤处理程序例程名。将
ArrayType
替换为对应的数组类型。将
NativeType
替换为该例程对应的本地类型。
Release<PrimitiveType>ArrayElements例程名 | ArrayType | NativeType |
---|---|---|
ReleaseBooleanArrayElements() | jbooleanArray | jboolean |
ReleaseByteArrayElements() | jbyteArray | jbyte |
ReleaseCharArrayElements() | jcharArray | jchar |
ReleaseShortArrayElements() | jshortArray | jshort |
ReleaseIntArrayElements() | jintArray | jint |
ReleaseLongArrayElements() | jlongArray | jlong |
ReleaseFloatArrayElements() | jfloatArray | jfloat |
ReleaseDoubleArrayElements() | jdoubleArray | jdouble |
链接:
JNIEnv
接口函数表索引如下。
Release<PrimitiveType>ArrayElements例程名 | 索引 |
---|---|
ReleaseBooleanArrayElements() | 191 |
ReleaseByteArrayElements() | 192 |
ReleaseCharArrayElements() | 193 |
ReleaseShortArrayElements() | 194 |
ReleaseIntArrayElements() | 195 |
ReleaseLongArrayElements() | 196 |
ReleaseFloatArrayElements() | 197 |
ReleaseDoubleArrayElements() | 198 |
参数:
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
替换为下表中的某个实际基本类型元素访问器例程名。 - 将
ArrayType
替换为对应的数组类型。 - 将
NativeType
替换为该例程对应的本地类型。
Get<PrimitiveType>ArrayRegion例程名 | ArrayType | NativeType |
---|---|---|
GetBooleanArrayRegion() | jbooleanArray | jboolean |
GetByteArrayRegion() | jbyteArray | jbyte |
GetCharArrayRegion() | jcharArray | jchar |
GetShortArrayRegion() | jshortArray | jshort |
GetIntArrayRegion() | jintArray | jint |
GetLongArrayRegion() | jlongArray | jlong |
GetFloatArrayRegion() | jfloatArray | jloat |
GetDoubleArrayRegion() | jdoubleArray | jdouble |
链接:
JNIEnv
接口函数表索引如下。
Get<PrimitiveType>ArrayRegion例程名 | 索引 |
---|---|
GetBooleanArrayRegion() | 199 |
GetByteArrayRegion() | 200 |
GetCharArrayRegion() | 201 |
GetShortArrayRegion() | 202 |
GetIntArrayRegion() | 203 |
GetLongArrayRegion() | 204 |
GetFloatArrayRegion() | 205 |
GetDoubleArrayRegion() | 206 |
参数:
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
替换为该例程对应的本地类型。
Set<PrimitiveType>ArrayRegion例程名 | ArrayType | NativeType |
---|---|---|
SetBooleanArrayRegion() | jbooleanArray | jboolean |
SetByteArrayRegion() | jbyteArray | jbyte |
SetCharArrayRegion() | jcharArray | jchar |
SetShortArrayRegion() | jshortArray | jshort |
SetIntArrayRegion() | jintArray | jint |
SetLongArrayRegion() | jlongArray | jlong |
SetFloatArrayRegion() | jfloatArray | jfloat |
SetDoubleArrayRegion() | jdoubleArray | jdouble |
链接:
JNIEnv
接口函数表索引如下。
Set<PrimitiveType>ArrayRegion例程名 | 索引 |
---|---|
SetBooleanArrayRegion() | 207 |
SetByteArrayRegion() | 208 |
SetCharArrayRegion() | 209 |
SetShortArrayRegion() | 210 |
SetIntArrayRegion() | 211 |
SetLongArrayRegion() | 212 |
SetFloatArrayRegion() | 213 |
SetDoubleArrayRegion() | 214 |
参数:
env
:JNI
接口指针。
array
: Java
数组。
start
:起始下标。
len
:要复制的元素数。
buf
:源缓冲区。
异常:
如果区域中的某个下标无效抛出ArrayIndexOutOfBoundsException
。
注意:使用
JDK/JRE 1.1
的程序员可以使用Get/Release<primitivetype>ArrayElements
函数获取基本数组元素的指针。如果虚拟机支持固定,则返回指向原始数据的指针;否则,返回是的副本。JDK/JRE 1.3
中引入的新函数允许本地代码获取一个直接指向数组元素的指针,即使虚拟机不支持固定。
GetPrimitiveArrayCritical,ReleasePrimitiveArrayCritical
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
获取数组的指针时,虚拟机可能暂时禁用垃圾回收。
可能存在多个GetPrimtiveArrayCritical
和ReleasePrimitiveArrayCritical
嵌套。例如:
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
jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
向 clazz
参数指定的类注册本地方法。methods
参数将指定 JNINativeMethod
结构的数组,其中包含本地方法的名称、签名和函数指针。JNINativeMethod
结构的name
和signature
域指向modified UTF-8
字符串。nMethods
参数将指定数组中的本地方法数。JNINativeMethod
结构定义如下所示:
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
函数指针通常必须有下列签名:
ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);
链接:
JNIEnv
接口函数表索引215。
参数:
env
:JNI
接口指针。
clazz
:Java
类对象。
methods
:类中的本地方法。
nMethods
:类中的本地方法数。
返回值:
成功时返回 "0";失败时返回负数。
异常:
如果找不到指定的方法或方法不是本地方法抛出NoSuchMethodError
。
UnregisterNatives
jint UnregisterNatives(JNIEnv *env, jclass clazz);
注销类的本地方法。类将回到链接或注册了本地方法函数前的状态。
该函数不应在普通本地代码中使用。相反,它可以为特殊程序提供一种重新加载和重新链接本地库的途径。
链接:
JNIEnv
接口函数表索引216。
参数:
env
:JNI
接口指针。
clazz
:Java
类对象。
返回值:
成功时返回"0";失败时返回负数。
监视程序操作
MonitorEnter
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。
参数:
env
:JNI
接口指针。
obj
:常规 Java
对象或类对象。
返回值:
成功时返回"0";失败时返回负数。
MonitorExit
jint MonitorExit(JNIEnv *env, jobject obj);
当前线程必须是与 obj
所引用的基本 Java
对象相关联的监视器的所有者。线程将递减指示进入监视程序次数的计数器。如果计数器的值变为 0,当前线程释放监视程序。
本地代码不能使用MonitorExit
来退出通过synchronized
方法或者Java
虚拟机指令monitorenter
进入的监视器。
链接:
JNIEnv
接口函数表索引218。
参数:
env
:JNI
接口指针。
obj
:普通 Java
对象或类对象。
返回值:
成功时返回"0";失败时返回负数。
异常:
如果当前线程不拥有该监视器,抛出IllegalMonitorStateException
。
NIO支持
NIO
相关的入口点允许本地代码访问java.nio
直接缓冲区。直接缓冲区的内容能够驻留在普通的垃圾回收的堆外的本地内存中。关于直接缓冲区的信息可以查看 New I/O APIs 和java.nio.ByteBuffer
类的规范。
在JDK/JRE 1.4
引入的三个新函数允许JNI
代码创建,检查和操作直接缓冲区:
每种Java
虚拟机实现都应该支持这三个函数,但是并不是每种实现都要求支持JNI
访问直接缓冲区。如果JVM
不支持这种访问,NewDirectByteBuffer
和GetDirectBufferAddress
函数必须总是返回NULL
,并且GetDirectBufferCapacity
函数必须总是返回-1。如果JVM
确实支持这种访问,那必须实现这三个函数,并且返回一个合适的值。
NewDirectByteBuffer
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
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
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
、方法ID
和Java
核心反射API
中的域和方法对象之间转换函数。
FromReflectedMethod
jmethodID FromReflectedMethod(JNIEnv *env, jobject method);
把一个java.lang.reflect.Method
或者java.lang.reflect.Constructor
对象转换成一个方法ID
。
链接:
JNIEnv
接口函数表里索引7。
自从:
JDK/JRE 1.2
FromReflectedField
jfieldID FromReflectedField(JNIEnv *env, jobject field);
把一个java.lang.reflect.Field
对象转成成一个域ID
。
链接:
JNIEnv
接口函数表里索引8。
自从:
JDK/JRE 1.2
ToReflectedMethod
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
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
jint GetJavaVM(JNIEnv *env, JavaVM **vm);
返回与当前线程相关联的 Java
虚拟机接口(用于调用 API 中)。结果将放在第二个参数 vm
所指向的位置。
链接:
JNIEnv
接口函数表里索引219。
参数:
env
:JNI
接 口 指 针 。
vm
:指向放置结果的位置的指针。
返回值:
成功时返回"0";失败时返回负数。
调用API
调用 API
允许软件厂商将 Java
虚拟机加载到任意的本地程序中。厂商可以交付支持 Java
的应用程序,而不必链接 Java
虚拟机源代码。
本章首先概述了调用 API
,接下来是所有调用 API
函数的参考。包括以下话题:
概述
以下代码示例说明了如何使用调用 API
中的函数。在本例中,C++
代码创建Java 虚拟机并且调用名为 Main.test
的静态方法。为清楚起见,我们略去了错误检查。
#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
接口指针来访问虚拟机特性。其设计类似于 Netscape
的 JRI
嵌入式接口。
创建虚拟机
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
jint JNI_OnLoad(JavaVM *vm, void *reserved);
当本地库被加载时(例如通过System.loadLibrary
),虚拟机调用JNI_OnLoad
函数。JNI_OnLoad
必须返回本地库需要的JNI
版本。
为了使用任何新的JNI
函数,本地库必须导出返回JNI_VERSION_1_2
的JNI_OnLoad
函数。如果本地库没有导出JNI_OnLoad
函数,虚机机会假定本地库仅要求的JNI
版本是JNI_VERSION_1_1
。如果虚拟机没有识别JNI_OnLoad
函数返回的版本号,虚拟机将卸载库并像从未加载过一样。
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
void JNI_OnUnload(JavaVM *vm, void *reserved);
当包含本地库的类加载器被垃圾回收时,虚拟机会调用JNI_OnUnload
。这个函数用来执行清理操作。由于这个函数在未知的上下文(例如一个终结函数)中被调用,程序员应该对Java
虚拟机服务保持保守的态度,避免任意的Java
回调。
值得注意的是JNI_OnLoad
和 JNI_OnUnload
是JNI
库提供的两个可选择的函数,并不是虚拟机暴露导出的。
JNI_OnUnload_L(JavaVM *vm, void *reserved);
当包含一个静态链接的本地库L
的类加载器被垃圾回收时,如果库导出了JNI_OnUnload_L
函数,虚拟机就会调用该函数。这个函数用来执行清理操作。由于这个函数在未知的上下文(例如一个终结函数)中被调用,程序员应该对Java
虚拟机服务保持保守的态度,避免任意的Java
回调。
信息说明:
加载本地库的行为是使库及其本地入口点被感知并注册到Java
虚拟机和运行时的完整过程。注意简单执行操作系统层面的加载本地库的操作,例如UNIX(R)
系统中的dlopen
,并不能完全达到这个目标。通常Java
类加载器调用本地函数,以执行对主机操作系统的调用,该调用将加载库到内存中并返回一个本地库的句柄。该句柄将被存储,并在后续搜索本地库入口点时使用。一旦句柄成功的返回,表示注册库成功,Java
本地类加载器将完成装入过程。
链接:
从包含本地方法实现的本地库导出。
调用API函数
JavaVM
类型是指向调用 API
函数表的指针。以下代码示例显示了这种函数表。
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
jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
返回 Java 虚拟机的默认配置。在调用该函数之前,本地代码必须将vm_args->version
域设置为它所期望虚拟机支持的 JNI
版本。该函数返回后,将把 vm_args->version
设置为虚拟机支持的实际 JNI
版本。
链接:
从实现Java
虚拟机的本地库导出。
参数:
vm_args
:指向 JavaVMInitArgs
结构体的指针,默认参数填入该结构。
返回值:
如果所请求的版本得到支持,则返回JNI_OK
;如果所请求的版本未得到支持,则返回JNI
错误码(负数)。
JNI_GetCreatedJavaVMs
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
返回已创建的所有 Java
虚拟机。将指向虚拟机的指针按照其创建顺序写入vmBuf
缓冲区。最多写入 bufLen
个条目数。在 *nVMs
中返回所创建虚拟机的总数。
不支持在单个进程中创建多个虚拟机。
链接:
从实现Java虚拟机的本地库导出。
参数:
vmBuf
:指向将放置虚拟机结构体的缓冲区的指针。
bufLen
:缓冲区的长度 。
nVMs
:指向整数的指针。
返回值:
成功时返回JNI_OK
;失败则返回合适的JNI
错误码(负数)。
JNI_CreateJavaVM
jint JNI_CreateJavaVM(JavaVM **p_vm, JNIEnv **p_env, void *vm_args);
加载并初始化 Java
虚拟机。当前线程成为主线程。将 env
参数设置为主线程的 JNI
接口指针。
不支持在单个进程中创建多个虚拟机。
JNI_CreateJavaVM
的第二个参数总是JNIEnv *
指针,而第三个参数是指向JavaVMInitArgs
结构的指针,该结构使用选项字符串来编码任意虚拟机启动选项:
typedef struct JavaVMInitArgs {
jint version;
jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;
options
域是下面类型的数组:
typedef struct JavaVMOption {
char *optionString; /* 默认平台编码的选项字符串 */
void *extraInfo;
} JavaVMOption;
这个数组的大小是JavaVMInitArgs
中nOptions
域指定的。如果ignoreUnrecognized
设置为JNI_TRUE
,JNI_CreateJavaVM
将忽略所有以"-X
"或者"_
"开头的无法识别的选项字符串。如果ignoreUnrecognized
设置为JNI_FALSE
,一旦遇到任何无法识别的选项字符串,JNI_CreateJavaVM
就会返回JNI_ERR
。所有Java
虚拟机都必须识别以下一组标准选项:
选项字符串 | 含义 |
---|---|
-D<name>=<value> | 设置一个系统属性 |
-verbose[:class|gc|jni] | 启用详细输出。 选项后面可以跟一个逗号分隔的名称列表,表明VM 将打印哪种类型的信息。 例如,“ -verbose:gc,class ”指示虚拟机打印垃圾回收和类加载相关信息。 标准名称包括:gc , class 和jni 。 所有非标准(特定于虚拟机)名称必须以“X ”开头。 |
vfprintf | extraInfo 是指向vfprintf 钩子的指针。 |
exit | extraInfo 是指向exit 钩子的指针。 |
abort | extraInfo 是指向abort 钩子的指针。 |
此外,每个虚拟机实现能支持它自己的非标准选项字符串集合。非标准选项名字必须以"-X
"或下划线("_
")开头。例如,JDK/JRE
支持 -Xms
和 -Xmx
选项来允许程序员设置具体的初始和最大堆大小。以"-X
"开始的选项能够在Java
命令行访问。
以下是在JDK / JRE
中创建Java VM
的示例代码:
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
jint DestroyJavaVM(JavaVM *vm);
卸载 Java
虚拟机并回收资源。
任何线程,无论是否连接到虚拟机,都能调用这个函数。如果当前线程连接到虚拟机,则虚拟机将等待,直到当前线程是唯一的非守护用户级Java
线程。 如果当前线程未连接到虚拟机,则当前线程会连接到虚拟机,然后等待,直到当前线程是唯一的非守护用户级线程。
链接:
JavaVM
接口函数表里索引3。
参数:
vm
:将销毁的 Java
虚拟机。
返回值:
成功时返回JNI_OK
;失败则返回一个合适的JNI
错误代码(负数)。
不支持卸载虚拟机。
AttachCurrentThread
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
。可以传入下面结构体指针来指定其他额外信息:
typedef struct JavaVMAttachArgs {
jint version;
char *name; /* 线程的名字(Modified UTF-8字符串),或者 NULL */
jobject group; /* 一个ThreadGroup对象的全局引用, 或者为 NULL */
} JavaVMAttachArgs
返回值:
成功时返回JNI_OK
;失败则返回一个合适的JNI
错误代码(负数)。
AttachCurrentThreadAsDaemon
jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);
和AttachCurrentThread
一样的含义,但是新创建的java.lang.Thread
实例是守护线程。
如果线程已经通过AttachCurrentThread
或AttachCurrentThreadAsDaemon
连接,则此例程只需将penv
指向的值设置为当前线程的JNIEnv
。 在这种情况下,AttachCurrentThread
和此例程都不会对线程的守护状态产生任何影响。
链接:
JavaVM
接口函数表里索引7。
参数:
vm
: 当前线程所要连接到的虚拟机 。
penv
:指向当前线程的JNI
接口指针所在位置的指针。
args
:指向JavaVMAttachArgs
结构的指针。
返回值:
成功时返回JNI_OK
;失败则返回一个合适的JNI
错误代码(负数)。
异常:
无
DetachCurrentThread
jint DetachCurrentThread(JavaVM *vm);
断开当前线程与 Java
虚拟机之间的连接。释放该线程占用的所有 Java 监视器。通知所有等待该线程终止的 Java 线程。
主线程能断开与虚拟机之间的连接。
链接:
JavaVM
接口函数表里索引5。
参数:
vm
:当前线程将断开连接的虚拟机。
返回值:
成功时返回JNI_OK
;失败则返回一个合适的JNI
错误代码(负数)。
GetEnv
jint GetEnv(JavaVM *vm, void **env, jint version);
链接:
JavaVM
接口函数表里索引6。
参数:
vm
: 获取接口的虚拟机实例 env
:指向当前线程的JNI
接口指针所在位置的指针。 version
:请求的 JNI
版本.
返回值:
如果当前线程没有连接到虚拟机,会设置*env
为NULL
并且返回JNI_EDETACHED
。如果指定的版本是不支持的,会设置*env
为NULL
并且返回JNI_EVERSION
。否则,会设置*env
为合适的接口并且返回JNI_OK
。