从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计 的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。让我们看一些使用JNI的简单例子吧。
使用 java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。
开始:
如果你习惯了使用JNI,你就不会觉得它难了。既然本地方法是由其他语言实现的,它们在Java中没有函数体。但是,所有本地代码必须用本地关键词 声明,成为Java类的成员。清单A演示了一个简单的类,它申明了一个本地的(native),静态的(static)方法:sum。
写完了你的Java类,接下来就要写本地代码。本地方法符号提供一个满足约定的头文件,使用Java工具可以很容易地创建它而不用手动去创建。你对 Java的class文件使用javah命令,就会为你生成一个对应的C/C++头文件。清单B就是为清单A的Test1类创建的头文件。注意:它创建了 一个C/C++函数:Java_Test1_sum。
执行本地方法:
一旦你有了这个头文件,你就需要写头文件对应的本地方法,就像我在清单C做的那样。注意:所有的本地方法的第一个参数都是指向JNIEnv结构的。 这个结构是用来调用JNI函数的,(我会在另一个章节中讨论)。第二个参数jclass的意义,要看方法是不是静态的(static)或者实例 (Instance)的。前者,jclass代表一个类对象的引用,而后者是被调用的方法所属对象的引用。最后的两个jint参数表示了Java方法的 int参数。
返回值和参数类型根据等价约定映射到本地C/C++类型,如表A所示。有些类型,如清单B里面的两个jint参数,在本地代码中可直接使用,而其他类型只有通过JNI调用操作。
表A
Java类型 本地类型 描述 boolean jboolean C/C++8位整型 byte jbyte C/C++带符号的8位整型 char jchar C/C++无符号的16位整型 short jshort C/C++带符号的16位整型 int jint C/C++带符号的32位整型 long jlong C/C++带符号的64位整型e float jfloat C/C++32位浮点型 double jdouble C/C++64位浮点型 Object jobject 任何Java对象,或者没有对应java类型的对象 Class jclass Class对象 String jstring 字符串对象 Object[] jobjectArray 任何对象的数组 boolean[] jbooleanArray 布尔型数组 byte[] jbyteArray 比特型数组 char[] jcharArray 字符型数组 short[] jshortArray 短整型数组 int[] jintArray 整型数组 long[] jlongArray 长整型数组 float[] jfloatArray 浮点型数组 double[] jdoubleArray 双浮点型数组
※ JNI类型映射
最后一步是把本地代码编译成共享库(比如,UNIX的so文件,Windows的dll文件)。在Java中调用方法前,共享库须通过System.loadLibrary导入。最常用的方式是在类的静态(static)初始化器里做这这个工作。
在本地代码中访问JNI
我举的例子很简单,并不能满足演示怎样写JNI方法的目标。现在,让我们看一些高级的,通过JNIEnv结构使用非简单类型的例子。
JNI通过函数的形式提供了很多功能,供本地代码通过指向JNIEnv结构的指针调用;它作为第一个参数传递给每个本地方法。JNI函数的调用有下面几种格式(这里,假设env是指向JNIEnv的指针):
//C 格式
(*env)-><jni function>( env, <parameters> )
//C++ 格式
env-><jni function>( < parameters> )
这篇文章中接下来的例子我将会用C++格式。
使用数组:
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表B),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
表B
函数 Java数组类型 本地类型 GetBooleanArrayElements jbooleanArray jboolean GetByteArrayElements jbyteArray jbyte GetCharArrayElements jcharArray jchar GetShortArrayElements jshortArray jshort GetIntArrayElements jintArray jint GetLongArrayElements jlongArray jlong GetFloatArrayElements jfloatArray jfloat GetDoubleArrayElements jdoubleArray jdouble
JNI数组存取函数
当你对数组的存取完成后,要确保调用相应的ReleaseXXXArrayElements函数,参数是对应Java数组和 GetXXXArrayElements返回的指针。如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相 关的资源。
为了使用java对象的数组,你必须使用GetObjectArrayElement函数和SetObjectArrayElement函数,分别去get,set数组的元素。GetArrayLength函数会返回数组的长度。
清单D包含了一个简单的类,它演示了本地代码如何使用Java数组。这个本地实现循环遍历一个整型(int)数组,返回这些元素的总和。为简单起见,这个清单包含了java代码和本地实现。我已经省略了头文件,它可以很方便地通过javah得到。
在本地代码中访问JNI
使用对象
JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或 方法的ID是任何处理域和方法的函数的必须参数。
表C列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
表C
函数 描述 GetFieldID 得到一个实例的域的ID GetStaticFieldID 得到一个静态的域的ID GetMethodID 得到一个实例的方法的ID GetStaticMethodID 得到一个静态方法的ID
※域和方法的函数
如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。符号是从域的类型或者方法的参数,返回值得到字符串,如表D所示。
表D
Java 类型 符号 boolean Z byte B char C short S int I long L float F double D void V objects对象 Lfully-qualified-class-name;L类名 Arrays数组 [array-type [数组类型 methods方法 (argument-types)return-type(参数类型)返回类型
※确定域和方法的符号
一旦你有了类和方法或者域的ID,你就能把它保存下来以后使用,而没有必要重复去获取。
有几个分别访问域和方法的函数。实例的域可以使用对应域的GetXXXField的变体函数访问。GetStaticXXXField函数用于静态类型。设置域的值,用SetXXXField 和SetStaticXXXField函数。表E包含了所有访问域的函数列表。
表E
Java 类型 Method方法 boolean GetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField byte GetByteField, GetStaticByteField, SetByteField, SetStaticByteField char GetCharField, GetStaticCharField, SetCharField, SetStaticCharField short GetShortField, GetStaticShortField, SetShortField, SetStaticShortField int GetIntField, GetStaticIntField, SetIntField, SetStaticIntField long GetLongField, GetStaticLongField, SetLongField, SetStaticLongField float GetFloatField, GetStaticFloatField, SetFloatField, SetStaticFloatField double GetDoubleField, GetStaticDoubleField, SetDoubleField, SetStaticDoubleField object GetObjectField, GetStaticObjectField, SetObjectField, SetStaticObjectField
※访问域的函数
另外,方法的访问是由CallXXXMethod 函数和CallStaticXXXMethod函数完成的,XXX表明了方法的返回值类型。这些函数的变体允许传递数组参数 (CallXXXMethodA and CallStaticXXXMethodA)或者传递一个可变大小的列表(CallXXXMethodV and CallStaticXXXMethodV)。
一个完整的列表
表F:一个完整的列表
返回类型 函数 boolean CallBooleanMethod, CallBooleanMethodA, CallBooleanMethodV, CallStaticBooleanMethod, CallStaticBooleanMethodA, CallStaticBooleanMethodV byte CallByteMethod, CallByteMethodA, CallByteMethodV, CallStaticByteMethod, CallStaticByteMethodA, CallStaticByteMethodV char CallCharMethod, CallCharMethodA, CallCharMethodV, CallStaticCharMethod, CallStaticCharMethodA, CallStaticCharMethodV short CallShortMethod, CallShortMethodA, CallShortMethodV, CallStaticShortMethod, CallStaticShortMethodA, CallStaticShortMethodV int CallIntMethod, CallIntMethodA, CallIntMethodV, CallStaticIntMethod, CallStaticIntMethodA, CallStaticIntMethodV long CallLongMethod, CallLongMethodA, CallLongMethodV, CallStaticLongMethod, CallStaticLongMethodA, CallStaticLongMethodV float CallFloatMethod, CallFloatMethodA, CallFloatMethodV, CallStaticFloatMethod, CallStaticFloatMethodA, CallStaticFloatMethodV double CallDoubleMethod, CallDoubleMethodA, CallDoubleMethodV, CallStaticDoubleMethod, CallStaticDoubleMethodA, CallStaticDoubleMethodV void CallVoidMethod, CallVoidMethodA, CallVoidMethodV, CallStaticVoidMethod, CallStaticVoidMethodA, CallStaticVoidMethodV object CallObjectMethod, CallObjectMethodA, CallObjectMethodV, CallStaticObjectMethod, CallStaticObjectMethodA, CallStaticObjectMethodV
※方法访问函数
清单E演示了如何在本地代码中调用方法。本地方法printRandom得到了静态方法Math.random的ID,并且调用它几次,打印出结果。实例方法也一样处理。
当你关注java的扩展时,JNI是一个强大的工具,它不会严重降低可移植性。我这里只是接触它的表面,仅仅向你演示了JNI的能力和潜力。我鼓励你获取