最近在JNI相关项目中遇到一个问题:在Java层传入多个int类型的参数,在jni层修改参数值或地址之后重新返回到Java层。这应该算是基本知识了,尤其是基本类型的参数往往看似简单,所以在之前学习jni时就一笔带过了,结果现在突然遇到这个问题竟然需要再查找资料学习,因此这周在重新复习一遍基础知识之后将此记录一下。走的再远,也不要忘记脚底的路。
还是用Demo来解释下需求和对应解决方案吧
1 public class LibraryManager { 2 3 static{ 4 System.loadLibrary("libtest"); 5 } 6 7 public final static native int add1(int arg1, int arg2, int result); 8 public final static native int add2(int arg1, int arg2, int result); 9 12 }
在Java层写了两个方法分别模拟这个需求,在底层对arg1和arg2参数做操作,之后将结果存入result中,希望能在Java层使用result,至于方法的返回值,则是模拟表示方法执行成功与否的标志位。
下面分别在jni中用两种方式实现该需求,当然这两种都是典型错误的。
1 JNIEXPORT jint JNICALL Java_com_xxx_LibraryManager_add1( 2 JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2, jint jarg3){ 3 jarg3=jarg1+jarg2; 4 LOGI("add1 arg3=%d", jarg3); 5 return 1; 6 } 7 8 JNIEXPORT jint JNICALL Java_com_xxx_LibraryManager_add2( 9 JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2, jint *jarg3){ 10 int result=jarg1+jarg2; 11 jarg3=&result; 12 LOGI("add2 arg3=%d", *jarg3); 13 return 2; 14 }
如果还记得jni的运行原理的话,应该很容易理解这么写只是修改在c线程里边的参数值(add1()中)和参数地址(add2()中),至于Java层对应的参数没有变化,也就是说jni中的基本类型作为参数时只是形参传入的,对于上层没有任何影响。当然如果作为return值的话是绝对可以的,但是现在讨论的是作为参数值的方法。那么还是这个需求,应该怎么解决呢?
在LibraryManager.class中增加两个新的方法
1 public final static native int addConfirm1(int arg1, int arg2, int[] result); 2 public final static native int addConfirm2(int arg1, int arg2, Integer result);
看第三个参数就能知道解决方法了,将int类型转换为int[](addConfirm1()中)和int对应的整型类(addConfirm2()中),当然就是使用jni中的JNIEnv可以获取到的方法来修改参数,至于具体用法在下面详细列出。
1 JNIEXPORT jint JNICALL Java_com_bob_testlib_LibraryManager_addConfirm1( 2 JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2, jintArray jarg3){ 3 int result=jarg1+jarg2; 4 int *arg3 = jenv->GetIntArrayElements(jarg3, 0); 5 *arg3=result; 6 jenv->ReleaseIntArrayElements(jarg3, arg3, 0); 7 return 3; 8 } 9 JNIEXPORT jint JNICALL Java_com_bob_testlib_LibraryManager_addConfirm2( 10 JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2, jobject jarg3){ 11 int result=jarg1+jarg2; 12 jclass intClass = jenv->FindClass("java/lang/Integer"); 13 jfieldID intId = jenv->GetFieldID(intClass, "value", "I"); 14 jenv->SetIntField(jarg3, intId, result); 15 return 4; 16 }
在addConfirm1()中将int[]的参数传入,这样可以通过JNIEnv的GetIntArrayElements()获取到传入参数的地址并绑定到int*变量中,在修改变量之后,通过ReleaseIntArrayElements()通过第三个参数mode=0更新Java层jintArray的参数,并释放JNI层的int*变量。
在addConfirm2()中将jobject参数传入,通过JNIEnv的FindClass()找到Java层Integer类对应jni层的jclass,再根据jclass通过JNIEnv的GetFiledID()找到Java层Integer类的value对应jni层的jclass的jfieldID,最后通过JNIEnv的SetIntField()将要更新的int值存入到Java层的jobject中即可。这个流程就是把Java层的Integer看成自定义的类,之后就是更新自定义类中的变量。
目前能想到的有上面两种方式可以解决类似需求,其思想都是将Java中的基本类型转变成Java类,之后再用JNI中对类的操作方法进行修改。