本章我们重点说明以下JNI设计的问题,本章中提到的大多数设计问题都与native方法有关。至于调用相关的API的设计,我们会在后面进行介绍。
一、JNI接口函数和指针
native 代码通过调用JNI函数来访问Java VM功能。JNI函数可通过接口指针获得。接口指针是指向指针的指针。该指针指向一个指针数组,每个指针指向一个接口函数。每个接口函数都在数组内的预定义偏移处。下图说明了接口指针的组织。

接口指针
JNI接口的组织方式类似于C ++虚函数表或COM接口。使用接口表而不是硬连接函数条目的优点是JNI名称空间与native代码分离。VM可以轻松提供多个版本的JNI功能表。例如,VM可能支持两个JNI函数表:
- 一个用于平台执行彻底的非法参数检查,适合调试;
- 另一个执行JNI规范所需的最小量检查,因此更有效。
JNI接口指针仅在当前线程中有效。因此,native方法不能将接口指针从一个线程传递到另一个线程。实现JNI的VM可以在JNI接口指针指向的区域中分配和存储线程本地数据。
Native方法接收JNI接口指针作为参数。当VM从同一Java线程多次调用native方法时,保证将VM传递给native方法。但是,可以从不同的Java线程调用native方法,因此可以接收不同的JNI接口指针。
二、编译,加载和链接native方法
由于Java VM是多线程的,因此native库也应该与多线程感知的native编译器一起编译和链接。例如,该-mt标志应该用于使用Sun Studio编译器编译的C ++代码。对于符合GNU gcc编译器的代码,应使用标志-D_REENTRANT或-D_POSIX_C_SOURCE。有关更多信息,请参阅native编译器文档。
使用System.loadLibrary方法加载native方法。在以下示例中,类初始化方法加载特定于平台的native库,其中f定义了native方法:
package pkg; class Cls { native double f(int i, String s); static { System.loadLibrary(“pkg_Cls”); } }
参数System.loadLibrary是由程序员任意选择的库名。系统遵循标准但特定于平台的方法将库名称转换为native库名称。例如,Solaris系统将名称转换pkg_Cls为libpkg_Cls.so,而Win32系统将同名转换pkg_Cls为pkg_Cls.dll。
程序员可以使用单个库来存储任意数量的类所需的所有native方法,只要这些类要使用相同的类加载器加载即可。VM在内部维护每个类加载器的加载native库列表。供应商应选择本地库名称,以尽量减少名称冲突的可能性。
如果底层操作系统不支持动态链接,则必须将所有native方法与VM预先链接。在这种情况下,VM完成System.loadLibrary调用而不实际加载库。
程序员还可以调用JNI函数RegisterNatives()来注册与类关联的native方法。该RegisterNatives()功能对于静态链接功能特别有用。
三、解析native方法名称
动态链接器根据其名称解析条目。native方法名称由以下组件连接:
- 前缀
Java_
- 一个错位的完全限定的类名
- 下划线(“_”)分隔符
- 方法名称
- 对于重载的native方法,两个下划线(“__”)后跟参数签名
VM检查驻留在native库中的方法的方法名称匹配。VM首先查找短名称; 也就是说,没有参数签名的名称。然后它查找长名称,这是带有参数签名的名称。只有当native方法使用另一个native方法重载时,程序员才需要使用长名称。但是,如果native方法与非native方法具有相同的名称,则这不是问题。非native方法(Java方法)不驻留在native库中。
在以下示例中,g不必使用长名称链接方法g,因为另一种方法不是native方法,因此不在native库中。
class Cls1 { int g(int i); native int g(double d); }
我们采用了一种简单的名称修改方案,以确保所有Unicode字符都转换为有效的C函数名称。
我们使用下划线(“_”)字符代替完全限定类名中的斜杠(“/”)。由于名称或类型描述符从不以数字开头,因此我们可以使用_0...,_9表示转义序列。
native方法和接口API都遵循给定平台上的标准库调用约定。例如,UNIX系统使用C调用约定,而Win32系统使用__stdcall。
四、native方法参数
JNI接口指针是native方法的第一个参数。JNI接口指针的类型为JNIEnv。第二个参数根据native方法是静态方法还是非静态方法而有所不同。非静态native方法的第二个参数是对该对象的引用。静态native方法的第二个参数是对其Java类的引用。
其余参数对应于常规Java方法参数。native方法调用通过返回值将其结果传递回调用例程。下面的一篇文章,我们会介绍Java和C类型之间的映射。
下面的代码声明了C函数来实现native方法f。native方法f声明如下:
package pkg; class Cls { native double f(int

