Get/SetArrayRegion系列函数。这些函数会进行越界检查,在需要的时候会有可能抛出ArrayIndexOutOfBoundsException异常。
对于少量的、固定大小的数组,Get/Set
如果你没有一个预先分配的C缓冲区,并且原始数组长度未定,而本地代码又不想在获取数组元素的指针时阻塞的话,使用Get/ReleasePrimitiveArrayCritical函数对。就像Get/ReleaseStringCritical函数对一样,这对函数很小心地使用,以避免死锁。
Get/Release
3.3.5 访问对象数组
JNI提供了一个函数对来访问对象数组。GetObjectArrayElement返回数组中指定位置的元素,而SetObjectArrayElement修改数组中指定位置的元素。与基本类型的数组不同的是,你不能一次得到所有的对象元素或者一次复制多个对象元素。字符串和数组都是引用类型,你要使用Get/SetObjectArrayElement来访问字符串数组或者数组的数组。
下面的例子调用了一个本地方法来创建一个二维的int数组,然后打印这个数组的内容:
class ObjectArrayTest {
private static native int[][] initInt2DArray(int size); public static void main(String[] args) { int[][] i2arr = initInt2DArray(3); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) {
System.out.print(\ }
System.out.println(); } }
static {
System.loadLibrary(\ } }
静态本地方法initInt2DArray创建了一个给定大小的二维数组。执行分配和初始化数组任务的本地方法可以是下面这样子的:
JNIEXPORT jobjectArray JNICALL
Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size) {
jobjectArray result; int i;
jclass intArrCls = (*env)->FindClass(env, \ if (intArrCls == NULL) {
return NULL; /* exception thrown */ }
result = (*env)->NewObjectArray(env, size, intArrCls, NULL); if (result == NULL) {
return NULL; /* out of memory error thrown */ }
for (i = 0; i < size; i++) {
jint tmp[256]; /* make sure it is large enough! */ int j;
jintArray iarr = (*env)->NewIntArray(env, size); if (iarr == NULL) {
return NULL; /* out of memory error thrown */ }
for (j = 0; j < size; j++) { tmp[j] = i + j; }
(*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); }
return result; }
函数newInt2DArray首先调用JNI函数FindClass来获得一个int型二维数组类的引用,传递给FindClass的参数“[I”是JNI class descriptor(JNI类型描述符),它对应着JVM中的int[]类型。如果类加载失败的话,FindClass会返回NULL,然后抛出一个异常。
接下来,NewObjectArray会分配一个数组,这个数组里面的元素类型用intArrCls类引用来标识。函数NewObjectArray只能分配第一维,JVM没有与多维数组相对应的数据结构。一个二维数组实际上就是一个简单的数组的数组。 创建第二维数据的方式非常直接,NewInt-Array为每个数组元素分配空间,然后SetIntArrayRegion把tmp[]缓冲区中的内容复制到新分配的一维数组中去。 在循环最后调用DeleteLocalRef,确保JVM释放掉iarr这个JNI引用。
第四章 字段和方法
现在,你知道了如何通过JNI来访问JVM中的基本类型数据和字符串、数组这样的引用类型数据,下一步就是学习怎么样和JVM中任意对象的字段和方法进行交互。比如从本地代码中调用JAVA中的方法,也就是通常说的来自本地方法中的callbacks(回调)。 我们从进行字段访问和方法回调时需要的JNI函数开始讲解。本章的稍后部分我们会讨论怎么样通过一些cache(缓存)技术来优化这些操作。在最后,我们还会讨论从本地代码中访问字段和回调方法时的效率问题。
4.1 访问字段
JAVA支持两种field(字段),每一个对象的实例都有一个对象字段的复制;所有的对象共享一个类的静态字段。本地方法使用JNI提供的函数可以获取和修改这两种字段。先看一个从本地代码中访问对象字段的例子:
class InstanceFieldAccess { private String s;
private native void accessField();
public static void main(String args[]) {
InstanceFieldAccess c = new InstanceFieldAccess(); c.s = \ c.accessField();
System.out.println(\
System.out.println(\ }
static {
System.loadLibrary(\ } }
InstanceFieldAccess这个类定义了一个对象字段s。main方法创建了一个对象并设置s的值,然后调用本地方法InstanceFieldAccess.accessField在本地代码中打印s的值,并把它修改为一个新值。本地方法返回后,JAVA中把这个值再打印一次,可以看出来,字段s的值已经被改变了。下面是本地方法的实现: JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj) {
jfieldID fid; /* store the field ID */ jstring jstr; const char *str;
/* Get a reference to obj's class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf(\
/* Look for the instance field s in cls */ fid = (*env)->GetFieldID(env, cls, \
\ if (fid == NULL) {
return; /* failed to find the field */ }
/* Read the instance field s */
jstr = (*env)->GetObjectField(env, obj, fid); str = (*env)->GetStringUTFChars(env, jstr, NULL); if (str == NULL) {
return; /* out of memory */ }
printf(\
(*env)->ReleaseStringUTFChars(env, jstr, str);
/* Create a new string and overwrite the instance field */ jstr = (*env)->NewStringUTF(env, \ if (jstr == NULL) {
return; /* out of memory */ }
(*env)->SetObjectField(env, obj, fid, jstr); }
运行程序,得到输出为:
In C:
c.s = \ In Java:
c.s = \
4.1.1 访问一个对象字段的流程
为了访问一个对象的实例字段,本地方法需要做两步:
首先,通过在类引用上调用GetFieldID获取field ID(字段ID)、字段名字和字段描述符: Fid=(*env)->GetFieldID(env,cls,”s”,”Ljava/lang/String;”);
上例中的代码通过在对象引用obj上调用GetObjectClass获取到类引用。一旦获取到字段ID,你就可以把对象和字段ID作为参数来访问字段: Jstr=(*env)->GetObjectField(env,obj,fid);
因为字符串和数组是特殊的对象,所以我们使用GetObjectField来访问字符串类型的实例字段。除了Get/SetObjectField,JNI还支持其它如GetIntField、SetFloatField等用来访问基本类型字段的函数。
4.1.2 字段描述符
在上一节我们使用过一个特殊的C字符串“Ljava/lang/String”来代表一个JVM中的字段类型。这个字符串被称为JNI field descriptor(字段描述符)。
字符串的内容由字段被声明的类型决定。例如,使用“I”来表示一个int类型的字段,“F”来表示一个float类型的字段,“D”来表示一个double类型的字段,“Z”来表示一个boolean类型的字段等等。
像java.lang.String这样的引用类型的描述符都是以L开头,后面跟着一个JNI类描述符,以分号结尾。一个JAVA类的全名中的包名分隔符“.”被转化成“/”。因此,对于一个字段类
型的字段来说,它的描述符是“Ljava/lang/String”。
数组的描述符中包含“]”字符,后面会跟着数组类型的描述符,如“[I”是int[]类型的字段的描述符。12.3.3详细介绍了各种类型的字段描述以及他们代表的JAVA类型。 你可以使用javap工具来生成字段描述符。
4.1.3 访问静态字段
访问静态字段和访问实例字段相似,看下面这个InstanceFieldAccess例子的变形:
class StaticFielcdAccess { private static int si;
private native void accessField();
public static void main(String args[]) {
StaticFieldAccess c = new StaticFieldAccess(); StaticFieldAccess.si = 100; c.accessField();
System.out.println(\
System.out.println(\ }
static {
System.loadLibrary(\ } }
StaticFieldAccess这个类包含一个静态字段si,main方法创建了一个对象,初始化静态字段,然后调用本地方法StaticFieldAccess.accessField在本地代码中打印静态字段中的值,然后设置新的值,为了演示这个值确实被改变了,在本地方法返回后,JAVA中再次这个静态字段的值。 下面是本地方法StaticFieldAccess.accessField的实现: JNIEXPORT void JNICALL
Java_StaticFieldAccess_accessField(JNIEnv *env, jobject obj) {
jfieldID fid; /* store the field ID */ jint si;
/* Get a reference to obj's class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf(\
/* Look for the static field si in cls */
fid = (*env)->GetStaticFieldID(env, cls, \ if (fid == NULL) {
return; /* field not found */ }