JNI接口函数和指针
Java虚拟机访问本机代码通过调用JNI的功能特性。JNI的功能都可以通过一个接口指针。一个接口指针是一个指向指针的指针。这个指针指向一个一个指针数组,这个数组中的每一个成员指向一个函数入口。每个接口的功能是在一个预定义的内部数组的偏移量。
图一、JNI接口函数和指针
JNI接口的组织像C++虚函数列表或COM接口一样。使用接口列表的好处是JNI名字空间与本地代码分离开来。
JNI接口指针只在当前线程有效。因此,一个本地方法不能从一个线程通过该接口指针指向另一个线程。
本地方法像参数一样接收JNI接口指针。当在同一个线程中有多个调用本地方法,VM保证相同的接口指针传递到这个本地方法。然而,本地方法可以被不同的Java线程调用,因此可能会收到不同的JNI接口指针。
加载和链接本机方法
本地方法装载的是System.loadLibrary方法。在下面的例子中,类的初始化方法加载一个特定平台的本地库中的本地方法f定义如下:
package
pkg;
class
Cls {
native
double f(int i, String s);
static
{
System.loadLibrary(“pkg_Cls”);
}
}
System.loadLibrary中的参数是一个库名,它是由程序员任意选择的。该系统遵循一个标准:把库名转换为本地库名的方法,但这是与平台相关的。例如,Solaris系统把pkg_Cls转换为libpkg_Cls.so,而Win32系统则转换为pkg_Cls.dll。
程序员可以使用一个单一的库来存储所有的任何类需要的本地方法,只要这些类能够被同一类加载器加载。虚拟机内部会为每个类加载器维护一个加载本地库的的列表。
虚拟机会检查与本地库匹配的方法名。虚拟机会首先查找短名称,即没有参数的名称签名。然后,再查找长名称,即带参数签名的名称。程序员只有当一个本地方法与另一本机方法重载时才需要使用长名称。
在下面的例子中,本地方法g不必须连接使用长名称,因为其另一个方法g不是一个本地方法,因此也没有在本地库。
class
Cls1 {
int
g(int i);
native
int g(double d);
}
本机方法的参数
JNI接口指针是本地方法的第一个参数。在JNI接口指针的类型是JNIEnv。第二个参数不同,这取决于本地方法是静态的还是非静态的。第二个参数为一个非静态本地方法时是对对象的引用。第二个参数为静态本地方法是对的Java类的引用。
其余的参数对应于常规的Java方法的参数。本机方法通过返回值调用传递它的结果返回到调用例程。
下面代码举例说明了使用C函数来实现f本地方法时,本地方法f声明如下:
package
pkg;
class
Cls {
native
double f(int i, String s);
...
}
Java对象引用
像整数,字符,等等这些原始类型,都在Java与本机代码之间复制。另外,任意Java对象,是通过引用传递。虚拟机必须跟踪所有已传递到本地代码的对象,使这些对象不会被垃圾收集器释放。相反,本地代码必须有一种机制来通知虚拟机什么时候不再需要使用这些对象了。此外,垃圾收集器必须能够搬运对象所指的本机代码。
全局和局部引用
JNI把本地代码使用的相关对象分为两在范畴:局部和全局引用。局部引用的有效期为一个本地方法调用的时间,并自动机方法返回后获释。全球引用仍然有效,直到它们被明确释放。
所有的Java的JNI函数返回的对象都是局部引用。JNI允许程序员通过局部引用来创建全局引用。
局部引用只有在线程中被建立时才有效,并只在该线程中有效。本地代码不能在线程之间调用局部引用。
访问字段和方法
JNI允许本地代码访问字段和调用Java对象的方法。JNI通过符号名称和类型签名来判定方法和字段。例如,要调用类cls中的方法f,本地代码首先获得一个方法的ID,如下:
jmethodID
mid = env->GetMethodID(cls, “f”, “(ILjava/lang/String;)D”);
本地代码便可以重复的使用method
ID而在方法查找时不需要时间,具体如下:
jdouble
result = env->CallDoubleMethod(obj, mid, 10, str);
报告程序错误
JNI不检查编程错误,如空指针或非法参数类型传递。非法使用的参数类型包括用父类对象引用子类对象。
Java异常
JNI允许本地方法任意提高Java异常。本机代码也可以处理严重的Java异常。Java的未处理的异常是传送回了VM。
因此,程序员可以快速检查上次的JNI调用的返回值,以确定是否发生了错误,并调用一个函数,ExceptionOccurred(),以获得异常对象,它包含一个错误的条件更详细的说明。
异常处理
在本地代码中有两种方法处理异常:
1、本机方法可以选择立即返回,在Java代码中抛出异常,启动了本地方法调用。
2、本地代码能过调用ExceptionClear()来清除异常,然后执行自己的异常处理代码。
在异常被提升之后,本地代码在其它JNI调用之前必须先清除异常。