您的位置:首页 > 博客中心 > 数据库 >

Oracle官网JNI简介和接口函数分析

时间:2022-03-10 17:25


第一章 概述

 

本章主要介绍JNI(Java Native Interface),JNI是一种本地编程接口。它允许运行在JAVA虚拟机中的JAVA代码和用其他编程语言,诸如C语言、C++、汇编,写的应用和库之间的交互操作。

JNI的最大优势在于没有强加任何限制在JAVA虚拟机的下层实现上,因此,JAVA虚拟机供应商能够提供JNI的支持而不影响虚拟机的其他部分,程序员只需写出一个版本的本地应用和库,就可使之运行在一切支持JNI的JAVA虚拟机上。

 

本章包含了以下的要点:

 

? JNI概述

? 目标

? JNI方法

? JNI编程

? 变化

 

 

当你能够完全的用JAVA写出一个应用,也有一种情况,那就是只用JAVA无法满足应用的要求。这种无法完全用JAVA完成应用的情况出现的时候,程序员会用JNI写出JAVA本地方法来处理。

 

下面列举几个用JAVA本地方法的例子:

? 标准JAVA类库不支持应用所需的平台依赖特性

? 你已经有一个其他语言实现好的库,想让它能够通过JNI在JAVA代码中使用

? 你想在诸如汇编的低级语言中实现一部分时效性的代码

 

通过JNI的编程,你能够实现如下功能:

? 创建,检查,更新JAVA对象(包括数组和字符串)

? 调用JAVA方法

? 捕捉或者抛出异常

? 加载类和获取类的信息

? 执行运行时类型检查

 

你也能使用JNI通过调用API来让任意的本地应用嵌入到JAVA虚拟机中,这允许程序员轻松地让他们现有的程序在JAVA中可用,而不需要连接虚拟机的源代码。

 

历史背景

 

不同供应商的虚拟机提供不同的本地接口代码。不同的接口迫使程序员在一个给定的平台上产生,维持,分配多个版本的本地方法库。我们简略地查看一下这些本地方法接口,例如:

? JDK1.0本地方法接口

? 网景公司的JRI(JAVA运行时接口)

? 微软的RNI(原始本地接口)和JAVA/COM接口

 

 

JDK1.0有本地方法接口,但是有两个重大的使这个接口不能被其他JAVA虚拟机采用的问题.

首先,本地代码作为C结构体的成员来访问JAVA对象的域。然而,JAVA语言规格书没有定义怎么在内存中放置对象。如果一个JAVA虚拟机在内存中以不同的方式放置对象,那么程序员必须重新编译本地方法库。

其次,JDK1.0的本地方法接口依靠保守的垃圾回收器。例如,无限制的使用unhand宏,将使得适当地去扫描本地栈。

 

 

网景公司提出了JAVA运行时接口(JRI),这是一个通用的JAVA虚拟机提供服务的接口。JRI设计时运用了嵌入式的思想--它使得不需要考虑JAVA虚拟机下层实现细节的条件。JRI处理大范围的问题,包括了本地方法、调试、反射机制、嵌入(调用)等。

 

 

微软的JAVA虚拟机支持两种本地方法接口。低层次地,提供了有效的原始本地接口(RNI),RNI提供了对JDK本地方法接口的源码级后向兼容性的高度,尽管JDK有一个主要的不同点。本地代码必须使用RNI函数来明确地和垃圾回收器交互,而不是依靠垃圾回收器。

 

目标

 

我们相信一个统一的、深思熟虑的接口应该向每个人提供以下的益处:

? 每个虚拟机供应商能够支持大量的本地代码

? 工具制造者不必维护不同种类的本地方法接口

? 应用开发者能够只写一个版本的本地代码而能够在不同的虚拟机上运行

 

获取本地方法接口的最好途径是包含对JAVA虚拟机感兴趣的所有部分,因此,我们在设计一个统一的本地方法接口的JAVA授权商中组织了一系列的审议。毫无疑问地,这场关于标准化的本地接口审议必须满足下面的要求:

? 二进制兼容性 - 最基本目的是实现一个给定的平台上所有的JAVA虚拟机二进制兼容的本地方法库。开发者只需要就给定的平台维护一个版本的本地方法库。

? 效益 - 为了支持时效性的代码,本地方法接口必须只征收很少的费用。所有众所周知的满足虚拟机独立性的技术背负着很大的费用,所以总得在效益和虚拟机独立性上取得折中。

? 功能性 - 接口必须暴露出足够多的JAVA虚拟机内部来让本地方法完成有用的任务。

 

JNI方法

 

我们希望采用一个现有的方法作为标准接口,因为这将给在不同的虚拟机中学过多种接口的程序员最小的负担。不幸的是,没有这样的可以完全满足我们要求的解决方法。

 

网景公司的JRI是最接近我们预想的可移植本地方法接口,被用作我们设计的出发点。熟悉JRI的读者会注意到在API命名约定,方法和域ID的使用,局部和全局引用的使用等的相似点。尽管尽了我们最大的努力,然而,JNI和JRI不是二进制兼容的,即使虚拟机能够支持JRI和JNI。

 

微软的RNI是JDK1.0的改进,因为它解决了本地方法工作在保守垃圾回收器上的问题。然而,RNI作为虚拟机独立性的本地方法接口是不合适的。和JDK一样,RNI作为C结构体的去访问JAVA对象,导致了两个问题:

? RNI向本地代码暴露了内部JAVA对象的布局。

? 作为C结构体直接访问JAVA对象,使得不能高效地合并先进垃圾回收算法需要的”写屏障”

 

作为二进制标准,COM确保了通过不同虚拟机的二进制兼容性。调用COM方法要求只有一个小开销的间接调用,COM对象是在解决版本问题上动态链接库的一个重大提高。

 

然而,使用COM作为标准JAVA本地方法接口在一些方面有限制:

? 首先,JAVA/COM接口缺乏某些希望的函数,诸如访问私有域和引起通用的异常。

? 其次,JAVA/COM接口自动为JAVA对象提供标准的 IUnknown 和 IDispatch COM 接口,因此本地代码能够访问公有的域和方法。然而,IDispatch 接口不能处理重载的JAVA方法并且在匹配方法名是大小写敏感度的。此外,所有的通过IDispatch 接口暴露的JAVA方法被封装来执行动态的类型检测和强制转换。这是因为IDispatch 接口是用弱类型语言(例如Basic)的思想设计的。

? 再次,COM设计为允许软件组件(包括完全的应用程序)一起工作,而不是处理单独底层函数。我们相信将所有的JAVA类或者底层的本地方法作为软件组件式不合适的。

? 最后,COM的直接采用为缺乏UNIX平台的支持而受限制。

 

尽管JAVA对象不作为COM对象暴露给本地代码,JNI接口它本身是和COM二进制兼容的,JNI使用和COM相同的跳转表结构和调用约定。这意味着,一旦跨平台支持的COM可用,JNI就能作为JAVA虚拟机的COM接口。

 

JNI不是唯一的被给定JAVA虚拟机支持的本地方法接口。一个标准的接口使得想加载本地代码库到JAVA虚拟机的程序员受益。在一些情况下,程序员可能不得不使用底层的,虚拟机特性的接口来获得最高的效率,在另一些情况下,程序员可能使用上层的接口来构建软件组件。确实,当JAVA环境和组件软件技术更加成熟,本地方法将逐渐地失去重要性。

 

 

本地方法程序员应该用JNI编程。用JNI编程把你从诸如终端用户可能运行的供应商虚拟机等未知中隔离出来,通过遵从JNI标准,你将会给本地库最好的机会来在给定的JAVA虚拟机中运行。

如果你实现了JAVA虚拟机,你应该实现JNI。JNI经过长期试验来确保不强加任何的开销和限制在虚拟机的实现上,包括对象表示,垃圾回收方案等。

 

 

 

第二章 设计综述

 

本章聚焦在JNI的主要设计议题,这一章节的大部分设计议题都和本地方法有关,调用API的设计将在第五章介绍

 

 

本地代码通过调用JNI函数来获取JAVA虚拟机的特性。JNI函数是通过接口指针来发挥作用的。接口指针是指向指针的指针。这种指针指向了一个指针数组,数组中的每一个指针指向一个接口函数。每个接口函数在数组中有一个既定的偏移量。图2-1 显示了接口指针的组织结构。

 gxlsystem.com,布布扣

  图2-1 接口 指针

 

JNI接口组织地就像一个C++虚函数表和COM接口,使用接口表而不是硬链接的函数实体的好处是,JNI的名字空间和本地代码分开了。虚拟机能够轻松地提供多种版本的JNI函数表,例如,虚拟机提供了两种JNI函数表:

 

· 第一种完成了非法参数检测,更适合调试

· 另一种完成最少的JNI规格要求的检测,因此更加高效

 

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

 

JN接口指针作为参数被本地方法接收,虚拟机保证从相同的线程多次调用本地方法时,会传递相同的接口指针给本地方法。但是,一个本地方法可以从不同的线程中调用,因此会接收到不同的JNI接口指针。

 

 

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

 

由于JAVA虚拟机是多线程的,所以本地库应该使用多线程相关的编译器来编译和链接。例如,用标志-mt指示用SUN Studio 编译器来编译C++代码,用GNU GCC编译器编译代码,将使用 -D_REENTRANT 或者-D_POSIX_C_SOURCE 来指示。更多信息参考本地编译器文档。

 

本地方法使用System.loadLibrary方法装载,在下面的例子中,类初始化中装载了一个平台特性的本地库,其中定义了一个test方法。

 

package pkg; 

 

class Cls {

    native double f(int i, String s);

    static {

        System.loadLibrary(“pkg_Cls”);

    }

}

 

System.loadLibrary的参数是一个由程序员任意命名的库名,系统根据一个统一的,平台性的方法来库名到本地库名,例如,Solaris系统转换pkg_Cls到libpkg_Cls.so,而WIN32的系统转换pkg_Cls到pkg_Cls.dll。

 

程序员可以用一个单独的库来存储任意数量的类所需的本地方法,只要这些类用相同的类装载器装载。虚拟机内部提供了一系列已经装载好的本地库,供应商应该选择合适的库名来减小命名上的冲突。

 

本地库必须与虚拟机静态的连接,库与虚拟机镜像结合的方式是与实现相关的。

 

System.loadLibrary和等价的方法必须确保库被成功地加载。

 

一个和虚拟机结合的库L,被定为只有通过导出了JNI_OnLoad_L函数实现静态连接。

 

一个静态连接库L导出了 JNI_OnLoad_L 方法和JNI_OnLoad 方法,那么JNI_OnLoad方法将被忽略。

 

如果一个库L被静态地连接,那么第一次调用System.loadLibrary("L")或者等价的方法,JNI_OnLoad_L 方法将会按照JNI_OnLoad的规定引用相同地参数和返回预期值。

 

静态加载的库L将会禁止动态加载相同名字的库。

 

当包含静态链接库的类加载器被垃圾回收时,虚拟机将会调用 JNI_OnUnload_L函数,如果有这样的函数导出了。

 

如果静态链接导出了JNI_OnUnload_L函数和JNI_OnUnload函数,JNI_OnUnload函数将会被忽略。

 

程序员能够调用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接口指针是给本地方法的第一个参数。JNI接口指针是JNIENV类型的。第二个参数由于方法动态或静态而不同。非静态的方法的第二个参数是对象的引用,静态方法的第二个参数是它JAVA类的引用。

 

剩下的参数是和普通的JAVA方法的参数一致。本地方法调用通过返回值传递它的结果给调用的函数。

 

下面的代码例子说明一个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,        /* interface pointer */

     jobject obj,        /* "this" pointer */

     jint i,             /* argument #1 */

     jstring s)          /* argument #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" /* specify the C calling convention */ 

 

jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

 

     JNIEnv *env,        /* interface pointer */

     jobject obj,        /* "this" pointer */

     jint i,             /* argument #1 */

     jstring s)          /* argument #2 */

 

{

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

 

     // ...

 

     env->ReleaseStringUTFChars(s, str);

 

     // return ...

}

 

使用C++ ,额外的间接层和接口指针参数从源代码中消失了,然而,基本的机理还是和C一样的。在C++,JNI函数被定义成扩展成他们C的副本的内联成员函数。

 

 

诸如整型、字符型的原始类型在JAVA和本地代码之间复制。任意的JAVA对象传递引用。虚拟机必须追踪所有的传到本地代码中的对象,因此这些对象不会被垃圾回收器释放掉。相反地,本地代码必须有一种方式去告诉虚拟机它不再需要这些对象,另外,垃圾回收器必须能够移动被本地代码指向的对象。

 

全局和局部引用

 

JNI根据本地代码将对象应用分为两种:局部引用和全局引用,局部应用在本地方法调用期间有效,在本地方法返回之后自动释放。全局引用一直保持有效,直到他们被明确释放。

 

对象作为局部引用传递到本地方法。JNI函数返回的所有JAVA对象都是局部引用。JNI允许程序员从局部引用创建全局引用。需要JAVA对象的JNI函数接收局部和全局引用。本地方法可能返回局部的和全局的引用给虚拟机。

 

大多数情况下,程序员需要依靠虚拟机在本地方法返回之后来释放所有的局部引用,然而,有时程序员需要明确地释放局部引用,例如,考虑到下面的情形:

· 本地方法访问一个大的JAVA对象,因此创建了JAVA对象的局部引用。本地方法在返回调用者之前执行着额外的运算,大JAVA对象的局部引用将会阻止垃圾回收器回收该对象,即使这个对象不在剩下的运算中使用。

· 本地方法创建了大量的局部引用,即使他们不在同一时间使用,由于虚拟机需要相应的空间来记录局部应用,创建太多的局部应用可能引起系统内存溢出,例如,本地嗲吗循环通过大批的对象,作为局部引用取回元素,在每次迭代操作一个元素。在每次迭代之后,程序员不再需要数组元素的局部引用。

 

JNI允许程序员手动地在本地方法中的删除任意处的局部引用,为了确保程序员能够手动地释放局部引用,JNI函数不允许创建额外的局部引用,除了作为返回值的引用。

 

局部应用只在创建他们的线程中有效,本地代码不能传递他们从一个线程到另一个线程。

 

实现局部引用

 

为了实现局部引用,JAVA虚拟机创建了一个从JAVA到本地方法的转变控制的注册机。注册机映射了JAVA对象的局部引用,并阻止对象被回收,所有的传递到本地方法的JAVA对象(包括作为JNI函数调用返回值的)自动添加到注册机。在本地方法返回之后,注册机将被删除,同时其中的条目将被垃圾回收。

 

有不同的实现注册机的方法,例如用表,链表,哈希表等。

 

注意局部引用不能通过适当地扫描本地栈来很好的实现,本地代码可能将局部引用存放在全局数据区或堆数据结构中。

 

 

JNI提供了丰富的访问局部引用和全局引用的函数集。这意味着不论虚拟机内部怎么表示JAVA对象,只需要相同的本地方法实现。这就是JNI能够被大量的虚拟机实现的重要原因。

 

使用不透明引用的访问函数的费用高于直接访问C数据结构,我们相信,大多数情况下,JAVA程序员会使用本地方法来执行重要的任务而弱化接口的费用。

 

访问原始数组

 

对于大的包含很多像整型数组和字符串的原始数据类型的对象来说,费用是不能被接受的。(考虑到为了执行向量和矩阵计算的本地方法)迭代JAVA数组和通过函数调用取回每个元素师非常低效的。

 

一种解决方法介绍了概念“定位”,它是为了让本地方法使虚拟机限制数组的内容。本地方法接收一个直接指向元素的指针,然而,这种方法有两个含意:

· 垃圾回收器必须支持定位

· 虚拟机必须放置原始数组在连续的内存中。尽管这是大多数原始数组的自然实现,布尔类型的数组可以以压缩的和未压缩的形式实现,因此,依靠精确布局的布尔数组的本地代码不能被移植。 

 

我们采用了一个折中的方式来克服上面的问题

 

首先,提供了一系列的函数在原始数组的部分和本地内存缓冲区之间进行复制,只在需要访问大数组的少量元素时使用。

 

其次,程序员能够使用另外系列的函数来取回固定版本的数组元素。记住这些函数可能要求JAVA虚拟机执行存储器的分配和拷贝。这些函数是否真正地复制了数组取决于虚拟机的实现,就像下面的例子:

· 如果垃圾回收器支持定位,数组的布局和本地方法需要的一样,那么就不需要复制。

· 否则,数组被复制到固定的存储块(例如,C中的堆区域),执行必要的格式转换,返回指向复制后的数组的指针。

 

最后,接口提供了通知虚拟机本地代码不再需要访问数组元素的函数,当你调用这些函数的时候,系统要么除去这个数组,要么调和原始数组和它的固定复制品并释放复制品。

 

JNI实现必须确保不同线程中的本地方法能够同时地访问相同的数组,注意,JNI不需要为了独占通过本地方法锁住原始数组,在不同的线程中同时去更新数组会导致不确定的结果。

 

访问域和方法

JNI 允许本地代码访问域和调用JAVA对象的方法。JNI通过符号名和类型标记来识别方法,查找域或者方法有两步。例如,调用cls类中f 方法,本地代码首先获取方法ID,就像下面:

 

jmethodID mid = env->GetMethodID(cls, “f”, “(ILjava/lang/String;)D”);

 

本地代码能够反复地使用这个方法ID而不需要再次去查方法,像下面:

 

jdouble result = env->CallDoubleMethod(obj, mid, 10, str);

 

域和方法的ID不能阻止虚拟机卸载已经生成方法ID类。当类被卸载后,方法ID和域ID将不可用,因此,如果长时间地使用一个方法ID或者域ID,本地代码必须保证:

· 保持一个基本类的存在的引用,或者

· 重新计算方法和域ID

 

JNI在内部实现不对域ID和方法ID强加任何限制。

 

记录编程错误

 

JNI不检查编程错误,像传递空指针,非法参数类型等。非法参数错误包括了用一个正常的JAVA对象代替一个JAVA类对象。JNI不检查编程错误的原因如下:

· 强制让JNI函数检查可能存在的错误条件会降低正常(没有错误)的本地方法的表现。

·在许多情况下,没有足够的运行时类型信息来进行这些检查。

 

大多数的C库函数防护编程错误。例如,printf()函数经常在接收到无效的地址引起一个运行时错误,而不是返回一个错误代码。强制C库函数检查所有的可能存在的错误可能导致这样的检查发生多次--一次在用户代码,再次在库中。 

 

程序员不能传递非法的指针或者错误类型的参数给JNI函数。这样做将会导致任意的结果发生,包括系统的毁坏和虚拟机的崩溃。

 

 

JNI允许本地方法提出任意的JAVA异常。本地代码可能处理先显著地JAVA异常,未经处理掉的异常将会被传回虚拟机中。

 

异常和错误代码

 

某些JNI函数用JAVA异常处理机制来记录错误情况,在大多数情况下,JNI函数用过返回错误代码和抛出异常来记录错误情况。错误代码通常是超出正常返回值范围的特殊返回值(例如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怎么映射JAVA类型到C类型

 

本章涵盖了如下的主要内容:

· 原始类型

· 引用类型

· 域和方法ID

· 值类型

· 类型标示

· 改进的UTF-8字符串

 

原始类型

 

下面的表格描述了JAVA原始类型和与他们的机器相关的本地等价类型

原始类型和本地等价类型

Java Type

Native Type

Description

boolean

jboolean

unsigned 8 bits

byte

jbyte

signed 8 bits

char

jchar

unsigned 16 bits

short

jshort

signed 16 bits

int

jint

signed 32 bits

long

jlong

signed 64 bits

float

jfloat

32 bits

double

jdouble

64 bits

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是规则的C指针类型:

 

struct _jfieldID;              /* opaque structure */

typedef struct _jfieldID *jfieldID;   /* field IDs */

 

struct _jmethodID;              /* opaque structure */

typedef struct _jmethodID *jmethodID; /* method IDs */

 

值类型

 

联合类型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

Lpkg/Cls ;

pkg.Cls (完整类名)

[ type

type[] (数组)

( 参数类型 )返回值类型

方法类型

 

 

举个例子,JAVA方法:

 

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

 

有如下的类型标示:

 

(ILjava/lang/String;[I)J

 

 

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‘ 的字符用3个字节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

 

六个字节所代表的字符值为 ((x & 0xf) << 12) + ((y & 0x3f) << 6) + (z & 0x3f) 。

 

多字符的字节数以大端序存放在class文件中。

 

这种格式和标准UTF-8格式有两种不同。首先,空字符0是用两个字节而不是一个字节编码的,这意味着改进的UTF-8没有嵌入的空值。其次,只有一字节、两字节、三字节的标准UTF-8被使用了。JAVA虚拟机不能识别四字节格式的标准UTF-8;它用两次三字节的格式代替。

 

 

 

这章的作用是JNI函数的参考部分。提供了全部的JNI函数。也显示了JNI函数表准确的设计。

 

注意用了条目“必须”来描述对JNI编程者的限制。例如,当你看见某个JNI函数必须接收非空的对象,那么保证空值不能传给JNI函数将是你的责任。因此,JNI实现不需要在JNI函数中进行空指针的检查。

 

本章的这部分是由网景公司的JRI文档改编过来的。

 

这些参考资料是通过用处分组的,参考部分是由下面的功能区域组织起来的。

 

接口函数表 (Interface Function Table)

版本信息(Version Information)

GetVersion

Constants

类操作(Class Operations)

DefineClass

FindClass

GetSuperclass

IsAssignableFrom

异常(Exceptions)

Throw

ThrowNew

ExceptionOccurred

ExceptionDescribe

ExceptionClear

FatalError

ExceptionCheck

全局和局部引用(Global and Local References)

Global References

NewGlobalRef

DeleteGlobalRef

Local References

DeleteLocalRef

EnsureLocalCapacity

PushLocalFrame

PopLocalFrame

NewLocalRef

弱全局引用(Weak Global References)

NewWeakGlobalRef

DeleteWeakGlobalRef

对象操作(Object Operations)

AllocObject

NewObject, NewObjectA, NewObjectV

GetObjectClass

GetObjectRefType

IsInstanceOf

IsSameObject

获取域和对象(Accessing Fields of Objects)

GetFieldID

Get<type>Field Routines

Set<type>Field Routines

调用实例方法(Calling Instance Methods)

GetMethodID

Call<type>Method Routines, Call<type>MethodA Routines, Call<type>MethodV Routines

CallNonvirtual<type>Method Routines, CallNonvirtual<type>MethodA Routines, CallNonvirtual<type>MethodV Routines

获取静态域(Accessing Static Fields)

GetStaticFieldID

GetStatic<type>Field Routines

SetStatic<type>Field Routines

调用静态方法(Calling Static Methods)

GetStaticMethodID

CallStatic<type>Method Routines, CallStatic<type>MethodA Routines, CallStatic<type>MethodV Routines

字符串操作(String Operations)

NewString

GetStringLength

GetStringChars

ReleaseStringChars

NewStringUTF

GetStringUTFLength

GetStringUTFChars

ReleaseStringUTFChars

GetStringRegion

GetStringUTFRegion

GetStringCritical, ReleaseStringCritical

数组操作(Array Operations)

GetArrayLength

NewObjectArray

GetObjectArrayElement

SetObjectArrayElement

New<PrimitiveType>Array Routines

Get<PrimitiveType>ArrayElements Routines

Release<PrimitiveType>ArrayElements Routines

Get<PrimitiveType>ArrayRegion Routines

Set<PrimitiveType>ArrayRegion Routines

GetPrimitiveArrayCritical, ReleasePrimitiveArrayCritical

注册本地方法(Registering Native Methods)

RegisterNatives

UnregisterNatives

监控操作(Monitor Operations)

MonitorEnter

MonitorExit

网络接口支持(NIO Support)

NewDirectByteBuffer

GetDirectBufferAddress

GetDirectBufferCapacity

反射支持(Reflection Support)

FromReflectedMethod

FromReflectedField

ToReflectedMethod

ToReflectedField

JAVA虚拟机接口(Java VM Interface)

GetJavaVM

 

 

接口函数表

 

每个函数通过参数JNIEnv的固定偏移变得可用。JNIEnv类型是一个指向存储了所有JNI函数指针的结构体的指针,定义如下:

 

typedef const struct JNINativeInterface *JNIEnv;

 

虚拟机像下面显示的代码例子一样初始化函数表。注意最先的三个条目是为未来的接口兼容性而保留的。另外,我们保留了一些额外的空条目靠近函数表的开始,为此,举个例子,将来的一个和类相关的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);

 

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

 

LINKAGE:

JNIEnv的接口函数表索引 4 。

PARAMETERS:

env: JNI接口指针.

RETURNS:

返回主版本号在高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.

 

 

常量 Constants

从 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

 

类操作Class Operations

 

DefineClass

 

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

 

从原始类数据缓冲区装载一个类. DefineClass调用返回之后, 包含原始类数据的缓冲区不被虚拟机引用,如果需要的话可能会被丢弃.

LINKAGE:

JNIEnv的接口函数表索引 5。

PARAMETERS:

env: JNI接口指针.

name: 被定义的类或者接口的名称. 字符串用改进的UTF-8编码.

loader:分配给确定类的类装载器.

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

bufLen: 缓冲区长度.

RETURNS:

返回JAVA对象,或者发生错误返回NULL。

THROWS:

ClassFormatError: 如果类数据没有指定一个有效的类.

ClassCircularityError: 如果一个类或者接口是它自己的超类或者超接口.

OutOfMemoryError: 如果系统内存溢出.

SecurityException: 如果调用者尝试定义一个类在“java”包结构树下.

 

 

FindClass

 

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

 

在 JDK 1.1发行版, 这个函数装在一个本地定义的类. 它在由CLASSPATH 环境变量指定的目录和zip文件中查找指定名称的类。

从 Java 2 SDK 1.2发行版, JAVA安全模型允许装载非系统类和调用本地方法. FindClass 查找和当前本地方法相关联的类加载器; 也就是, 声明本地方法的类的类加载器. 如果本地方法属于系统类,将没有相关的类加载器.否则,适当的类加载器将会装载和连接指定的类.

从 Java 2 SDK 1.2发行版,当通过调接口调用了FindClass , 将没有当前的本地方法和相关的类加载器. 这种情况下, ClassLoader.getSystemClassLoader的结果将被使用. 这是虚拟机为应用程序创建的类加载器, 它能在列出在 java.class.path 属性中查找类。

 

参数name 是一个完全限定的类名或者数组类型标示. 例如java.lang.String类的完全限定类名是:

                  "java/lang/String"

 

类数组java.lang.Object[] 数组类型标示是:

                  "[Ljava/lang/Object;"

 

LINKAGE:

JNIEnv的接口函数表索引 5。

PARAMETERS:

env: JNI接口指针.

name: 完全限定的类名 (也就是, 包名,用“/”分隔开,然后是类名). 如果名字以 “[“ (数组标示字符)开头,返回数组类.字符串用改进型UTF-8 编码.

RETURNS:

从完全限定类名返回类对象,如果没有找到返回NULL。

THROWS:

ClassFormatError: 如果类数据没有指定一个有效的类.

ClassCircularityError: 如果一个类或者接口是它自己的超类或者超接口.

NoClassDefFoundError: 如果要求的类和接口定义没有找到.

OutOfMemoryError:  如果系统内存溢出.

 

GetSuperclass

 

jclass GetSuperclass(JNIEnv *env, jclass clazz);

 

如果 clazz表示除了Object的任何类,那么这个函数返回代表这个指定类clazz的超类。

如果 clazz 表示 Object类,或者clazz 表示一个接口, 函数返回NULL.

LINKAGE:

JNIEnv的接口函数表索引 10。

PARAMETERS:

env: JNI接口指针.

clazz: JAVA类对象.

RETURNS:

返回指定类clazz的超类, 或者 NULL.

 

IsAssignableFrom

 

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

 

确定对象clazz1 能否安全转化为 clazz2.

LINKAGE:

JNIEnv的接口函数表索引 11。

PARAMETERS:

env:JNI接口指针.

clazz1: 第一个类参数.

clazz2: 第二个类参数.

RETURNS:

如果下面任意一种情况成立,返回JNI_TRUE :

第一个和第二个类参数是同一个JAVA类.

第一个类参数是第二个类参数的超类.

第一个类有第二个类作为接口.

 

 Exceptions

Throw

 

jint Throw(JNIEnv *env, jthrowable obj);

 

引起java.lang.Throwable对象的抛出.

LINKAGE:

JNIEnv的接口函数表索引 13。

PARAMETERS:

env: JNI接口指针.

obj: 一个java.lang.Throwable 对象.

RETURNS:

成功返回0,失败返回负数.

THROWS:

java.lang.Throwable 对象.

 

ThrowNew

 

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

 

从指定类创建一个附带消息 message的异常类,并且引起该异常抛出.

LINKAGE:

JNIEnv的接口函数表索引 14。

PARAMETERS:

env: JNI接口指针.

obj: 一个java.lang.Throwable 对象.

message: 用来创建java.lang.Throwable对象的消息 ,字符串用改进型UTF-8 编码.

RETURNS:

成功返回0,失败返回负数.

THROWS:

java.lang.Throwable 对象.

 

ExceptionOccurred

 

jthrowable ExceptionOccurred(JNIEnv *env);

 

确定是否有异常抛出。 该异常将会保持抛出直到调用本地方法ExceptionClear(),或者JAVA代码处理了异常.

LINKAGE:

JNIEnv的接口函数表索引 15。

PARAMETERS:

env: JNI接口指针.

RETURNS:

返回当前过程中抛出的异常,如果当前

热门排行

今日推荐

热门手游