JNI 数据类型 从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 ※ 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 JNI数组存取函数 当你对数组的存取完成后,要确保调用相应的Relea**XXArrayElements函数,参数是对应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 ※域和方法的函数 如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。符号是从域的类型或者方法的参数,返回值得到字符串,如表D所示。 表D | | | | | | | | | | | | | | | | | | | | | Lfully-qualified-class-name;L类名 | | | | (argument-types)return-type(参数类型)返回类型 |
※确定域和方法的符号 一旦你有了类和方法或者域的ID,你就能把它保存下来以后使用,而没有必要重复去获取。 有几个分别访问域和方法的函数。实例的域可以使用对应域的GetXXXField的变体函数访问。GetStaticXXXField函数用于静态类型。设置域的值,用SetXXXField 和SetStaticXXXField函数。表E包含了所有访问域的函数列表。 表E | | | GetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField | | GetByteField, GetStaticByteField, SetByteField, SetStaticByteField | | GetCharField, GetStaticCharField, SetCharField, SetStaticCharField | | GetShortField, GetStaticShortField, SetShortField, SetStaticShortField | | GetIntField, GetStaticIntField, SetIntField, SetStaticIntField | | GetLongField, GetStaticLongField, SetLongField, SetStaticLongField | | GetFloatField, GetStaticFloatField, SetFloatField, SetStaticFloatField | | GetDoubleField, GetStaticDoubleField, SetDoubleField, SetStaticDoubleField | | GetObjectField, GetStaticObjectField, SetObjectField, SetStaticObjectField |
※访问域的函数 另外,方法的访问是由CallXXXMethod 函数和CallStaticXXXMethod函数完成的,XXX表明了方法的返回值类型。这些函数的变体允许传递数组参数 (CallXXXMethodA and CallStaticXXXMethodA)或者传递一个可变大小的列表(CallXXXMethodV and CallStaticXXXMethodV)。 一个完整的列表 表F:一个完整的列表 | | | CallBooleanMethod, CallBooleanMethodA, CallBooleanMethodV, CallStaticBooleanMethod, CallStaticBooleanMethodA, CallStaticBooleanMethodV | | CallByteMethod, CallByteMethodA, CallByteMethodV, CallStaticByteMethod, CallStaticByteMethodA, CallStaticByteMethodV | | CallCharMethod, CallCharMethodA, CallCharMethodV, CallStaticCharMethod, CallStaticCharMethodA, CallStaticCharMethodV | | CallShortMethod, CallShortMethodA, CallShortMethodV, CallStaticShortMethod, CallStaticShortMethodA, CallStaticShortMethodV | | CallIntMethod, CallIntMethodA, CallIntMethodV, CallStaticIntMethod, CallStaticIntMethodA, CallStaticIntMethodV | | CallLongMethod, CallLongMethodA, CallLongMethodV, CallStaticLongMethod, CallStaticLongMethodA, CallStaticLongMethodV | | CallFloatMethod, CallFloatMethodA, CallFloatMethodV, CallStaticFloatMethod, CallStaticFloatMethodA, CallStaticFloatMethodV | | CallDoubleMethod, CallDoubleMethodA, CallDoubleMethodV, CallStaticDoubleMethod, CallStaticDoubleMethodA, CallStaticDoubleMethodV | | CallVoidMethod, CallVoidMethodA, CallVoidMethodV, CallStaticVoidMethod, CallStaticVoidMethodA, CallStaticVoidMethodV | | CallObjectMethod, CallObjectMethodA, CallObjectMethodV, CallStaticObjectMethod, CallStaticObjectMethodA, CallStaticObjectMethodV |
※方法访问函数 清单E演示了如何在本地代码中调用方法。本地方法printRandom得到了静态方法Math.random的ID,并且调用它几次,打印出结果。实例方法也一样处理。
|