1、背景
今天调试了一下Android jni关于Java中调用C代码的程序,发现我的数组参数传递方式不对,导致值传递不正确,我的方法是:
C代码,入口函数
#include#include jint Java_sony_MedicalRecordDemo_MainActivity_decryptionSuccess(JNIEnv* env, jobject thiz,jint Attr[]) { return Attr[0]; }
java代码,调用
package sony.MedicalRecordDemo; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { private static final String libSoName = "NDKtest"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initViews(); } private void initViews() { int Attr[] = {0,0,1,1,0}; int result = decryptionSuccess(Attr); System.out.println(result); } public native int decryptionSuccess(int[] Attr); static { System.loadLibrary(libSoName); } }
返回结果:1073819256,明显值没有传到C代码。
2、问题所在及解决方法
查看了一些关于android jni参数传递方面的资料,发现问题出在C代码中的
jint Java_sony_MedicalRecordDemo_MainActivity_decryptionSuccess(JNIEnv* env, jobject thiz,jint Attr[])
红色的地方。
解决方法是:
#include#include jint Java_sony_MedicalRecordDemo_MainActivity_decryptionSuccess(JNIEnv* env, jobject thiz,jintArray Attr) { jint* arr; jint length; arr = (*env)->GetIntArrayElements(env,Attr,NULL); length = (*env)->GetArrayLength(env,Attr); return arr[0]; }
3、注意GetIntArrayElements的用法
第一种:
env->GetIntArrayElements(array1,NULL);
我用这种方法的时候,用ndk命令编译时出错了。
第二种:
(*env)->GetIntArrayElements(env,nums, isCopy) , 返回 所有数据。If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy ismade; if no copy is made, it is set to JNI_FALSE.
貌似ndk命令只支持这个方式吧,我不太确定,还请高人指点。
4、反思与总结
JNI数组传递与异常处理
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表A),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
表A
函数 |
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函数会返回数组的长度。
下面通过一个实际的例子,演示一下在JAVA中传递基本类型的数组与对象类型的数组,然后在C++中进行相应的处理。
数组传递:
JAVA中的代码:
package com.cjz.ibm;
public class CopyArray {
static int totalsum = 0;
static int a[] = new int[] { 1, 2, 3, 4, 5 };
static String str[] = new String[] { "we", "are", "friends" };
static {
System.loadLibrary("CopyArray");
}
private native int sum(int[] num);
private native int sum(String[] str);
public static void main(String[] args) {
CopyArray cp = new CopyArray();
cp.sum(a);
cp.sum(str);
}
}
在这个简单的java程序中,我们定义了两种类型的数组,一种是整形数组,属于基本数据类型的数组,另一种是字符串类型 的数组,属于对象数组。然后把这两种类型数组分别作为参数传递到本地方法sum中去。其中sum函数具有相同的函数名和返回值类型,区别它们的是参数类 型,这样,就涉及到方法签名的问题,方法签名是参数的类型+方法的返回值类型。可知,它们的方法签名是不相同的,所以为两个不同的方法。
经过编译,生成C++的头文件,这个过程可以参考http://blog.csdn.net/chenjin_zhong/archive/2010/09/08/5870305.aspx
C++代码:
#include
#include
#include "com_cjz_ibm_CopyArray.h"
JNIEXPORT jint JNICALL Java_com_cjz_ibm_CopyArray_sum___3I
(JNIEnv* env, jobject obj, jintArray array1){
//传入的参数是整形数组
jint* arr;//定义一个整形指针
int sum=0;
//对于整形数组的处理,主要有GetIntArrayElements与GetIntArrayRegion
//第一种方法
arr=env->GetIntArrayElements(array1,NULL);//得到一个指向原始数据类型内容的指针
jint length=env->GetArrayLength(array1);//得到数组的长度
int i=0; cout<<"arr["< sum+=arr[i]; for(i=0;i cout<<"buf["< sum+=buf[i]; //可以先往一个数组中输入值,然后把这个数组copy到jintArray中 //打印新的数组值 cout<<"arr2["<
} return sum; JNIEXPORT jint JNICALL Java_com_cjz_ibm_CopyArray_sum___3Ljava_lang_String_2 //把jobjectArray数组中的值取出来 int size=env->GetArrayLength(array2);//得到数组的长度值 //复制数组到新的数组中 cout<<"复制之后的数组为:"< jclass objClass = env->FindClass("java/lang/String");//定义数组中元素类型 jobjectArray texts= env->NewObjectArray(size, objClass, 0);//创建一个数组类型为String类型 jstring jstr; for(i=0;i { env->SetObjectArrayElement(texts, i, jstr);//放入到texts数组中去,必须放入jstring } for(i=0;i jstring str1=(jstring)env->GetObjectArrayElement(texts,i);//打印出新复制的数组值 } 在C++代码中,int类型的数组对应JNI中的jintArray,而字符串类型的数组对应jobjectArray. 首先分析一下代码: GetIntArrayElements( GetIntArrayRegion( array:a reference to an array whose elements are to be copied. start:the starting index of the array elements to be copied. len: the number of elements to be copied. buf: the destination buffer. 功能:把jintArray中的元素复制到buffer中。 SetIntArrayRegion( array:a reference to a primitive array to which the elements to be copied. start:the starting index in the primitive array. len:the number of elements to be copied. buf:the source buffer. 功能:把buf中的元素copy到jintArray中去。 可见,SetIntArrayRegion与GetIntArrayRegion是设置jintArray数组与取出jintArray数组中的值。 NewIntArray(jsize length) length:the number of elements in the array to be created. 功能:构造一个数组对象 返回值:返回一个jintArray类型。 GetArrayLength( 返回值:返回jintArray的长度。 由于在C++中,jintArray不能用下标直接存取,所以用到JNI中提供的接口函数进行操作。 GetObjectArrayElement(jobjectArray array,jsize index) array: a reference to the java.lang.Object array from which the element will be accessed. index: the array index 功能:返回对应索引值的object.返回的是一个数组元素的值。 SetObjectArrayElement(jobjectArray array,jsize index,jobject value) array: a reference to an array whose element will be accessed. index: index of the array element to be accessed. value: the new value of the array element. 功能:用来设置对应索引元素的值。 因此,对于基本类型的数组,我们可以用Get 而Set 对于数组的操作就介绍到这了。 异常处理: 下面看一个简单的例子: JAVA代码: ackage com.ibm.cjz; public class Exception1 { private native void doit(); private void callback() { int a[] = new int[3]; } public static void main(String[] args) { try { ex.doit(); System.out.println(ex); } } static { System.loadLibrary("Exception"); } 在这个JAVA代码中,定义了一个整形数组,然后在callback()方法中调用这个数组,可以看到数组越界了,下面演示在C++中如何捕获这个异常。 C++代码: #include "com_ibm_cjz_Exception1.h" jclass cls=env->GetObjectClass(obj); jthrowable excp=0; jclass cls=env->GetObjectClass(excp); } jthrowable ExceptionOccurred(JNIEnv *env); 返回值:返回一个异常对象。 void ExceptionClear(JNIEnv *env); 功能:清除该异常对象,只在本线程中传递。 jint ThrowNew(JNIEnv *env, jclass clazz, 功能:抛出一个指定的异常对象和异常信息。 打印结果: In C:java.lang.ArrayIndexOutOfBoundsException: 5 第一行是C++中打印的信息,第二行是从C++中抛出的异常。 如果把 env->ExceptionClear();//把这个异常清除掉,使之不能传递到java jclass errclass; 都注释掉,也会向JAVA中抛出异常。这时就没有清除掉异常了。 总之,数组的传递与异常在处理在JNI中非常的重要,在这里只是简单的介绍一下,欢迎大家共同学习。
for(i=0;i
}
//第二种方法
jint buf[]={0,0,0,0,0};//定义一个jint类型的buffer把原始的数组copy到这个buf中去
env->GetIntArrayRegion(array1,0,length,buf);
}
//返回一个jint类型的数组
jintArray iarr =env->NewIntArray(length);//新建一个jintArray
env->SetIntArrayRegion(iarr, 0, length, buf);//将buf中的值复制到jintArray中去,数组copy
jint* arr2;
arr2=env->GetIntArrayElements(iarr,NULL);
for(i=0;i
}
(JNIEnv* env, jobject obj, jobjectArray array2){
//在java中,String[]类型是对象,所以对应C++中的数组为jobjectArray
int i=0;
cout<<"输出原始的数组值:"<
const char* chars=env->GetStringUTFChars(obja,NULL);//将jstring类型转换成char类型输出
cout<
}
jstr=(jstring)env->GetObjectArrayElement(array2,i);//把array2中的元素取出来
const char* chars1=env->GetStringUTFChars(str1,NULL);
cout<
}
return 0;
System.out.println(a[5]);// 数组越界,在C++中会发生异常,然后C++中把这个异常传递给java
Exception1 ex = new Exception1();
} catch (Exception ex) {
}
#include
#include
JNIEXPORT void JNICALL Java_com_ibm_cjz_Exception1_doit
(JNIEnv* env, jobject obj){
jmethodID mid=env->GetMethodID(cls,"callback","()V");
env->CallVoidMethod(obj,mid);
//调用callback方法,获取异常信息
const char* str;
excp=env->ExceptionOccurred();//得到异常对象
if(excp){
//env->ExceptionClear();
//env->ExceptionDescribe();
jmethodID mid=env->GetMethodID(cls,"toString","()Ljava/lang/String;");//调用String中的toString方法
jstring msg=(jstring)env->CallObjectMethod(excp,mid);
str=env->GetStringUTFChars(msg,NULL);
cout<<"In C:"<
}
jclass errclass;
errclass=env->FindClass("java/lang/ArrayIndexOutOfBoundsException");
env->ThrowNew(errclass, "thrown from C++ code");//向java中抛出异常
const char *message);
java.lang.ArrayIndexOutOfBoundsException: thrown from C++ code
errclass=env->FindClass("java/lang/ArrayIndexOutOfBoundsException");
env->ThrowNew(errclass, "thrown from C++ code");//向java中抛出异常