1、背景
今天調試了一下Android jni關於Java中調用C代碼的程序,發現我的數組參數傳遞方式不對,導致值傳遞不正確,我的方法是:
C代碼,入口函數
#include <stdio.h>
#include <jni.h>
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 <stdio.h>
#include <jni.h>
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 <iostream.h>
#include <string.h>
#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;
for(i=0;i<length;i++){
cout<<"arr["<<i<<"]="<<arr[i]<<endl;
sum+=arr[i];
}
//第二種方法
jint buf[]={0,0,0,0,0};//定義一個jint類型的buffer把原始的數組copy到這個buf中去
env->GetIntArrayRegion(array1,0,length,buf);
for(i=0;i<length;i++){
cout<<"buf["<<i<<"]="<<buf[i]<<endl;
sum+=buf[i];
}
//返回一個jint類型的數組
//可以先往一個數組中輸入值,然后把這個數組copy到jintArray中
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<env->GetArrayLength(iarr);i++){
cout<<"arr2["<<i<<"]="<<arr2[i]<<endl;
}
return sum;
}
JNIEXPORT jint JNICALL Java_com_cjz_ibm_CopyArray_sum___3Ljava_lang_String_2
(JNIEnv* env, jobject obj, jobjectArray array2){
//在java中,String[]類型是對象,所以對應C++中的數組為jobjectArray
//把jobjectArray數組中的值取出來
int size=env->GetArrayLength(array2);//得到數組的長度值
int i=0;
cout<<"輸出原始的數組值:"<<endl;
for(i=0;i<size;i++){
jstring obja=(jstring)env->GetObjectArrayElement(array2,i);
const char* chars=env->GetStringUTFChars(obja,NULL);//將jstring類型轉換成char類型輸出
cout<<chars<<endl;
}
//復制數組到新的數組中
cout<<"復制之后的數組為:"<<endl;
jclass objClass = env->FindClass("java/lang/String");//定義數組中元素類型
jobjectArray texts= env->NewObjectArray(size, objClass, 0);//創建一個數組類型為String類型
jstring jstr;
for(i=0;i<size;i++)
{
jstr=(jstring)env->GetObjectArrayElement(array2,i);//把array2中的元素取出來
env->SetObjectArrayElement(texts, i, jstr);//放入到texts數組中去,必須放入jstring
}
for(i=0;i<size;i++){
jstring str1=(jstring)env->GetObjectArrayElement(texts,i);//打印出新復制的數組值
const char* chars1=env->GetStringUTFChars(str1,NULL);
cout<<chars1<<endl;
}
return 0;
}
在C++代碼中,int類型的數組對應JNI中的jintArray,而字符串類型的數組對應jobjectArray.
首先分析一下代碼:
GetIntArrayElements(<ArrayType>array,jboolean *isCopy):第一個參數傳遞的是數組名,第二個參數傳遞的是否返回一個復制的數組。返回值:返回一個指向數組的指針。
GetIntArrayRegion(<ArrayType>array,jsize start,jsize len,<NativeType> *buf)
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(<ArrayType>array,jsize start,jsize len,<NativeType>*buf)
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(<ArrayType>array)
返回值:返回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<Type>ArrayElements和Get<Type>ArrayRegion來操作。
而Set<Type>ArrayRegion傳遞一個預先設置好的buf來返回一個j<Type>Array.而對於jobjectArray,可以用SetObjectArrayElement與GetObjectArrayElement來設置元素的值與返回元素的值。
對於數組的操作就介紹到這了。
異常處理:
下面看一個簡單的例子:
JAVA代碼:
ackage com.ibm.cjz;
public class Exception1 {
private native void doit();
private void callback() {
int a[] = new int[3];
System.out.println(a[5]);// 數組越界,在C++中會發生異常,然后C++中把這個異常傳遞給java
}
public static void main(String[] args) {
try {
Exception1 ex = new Exception1();
ex.doit();
} catch (Exception ex) {
System.out.println(ex);
}
}
static {
System.loadLibrary("Exception");
}
}
在這個JAVA代碼中,定義了一個整形數組,然后在callback()方法中調用這個數組,可以看到數組越界了,下面演示在C++中如何捕獲這個異常。
C++代碼:
#include "com_ibm_cjz_Exception1.h"
#include <iostream.h>
#include <string.h>
JNIEXPORT void JNICALL Java_com_ibm_cjz_Exception1_doit
(JNIEnv* env, jobject obj){
jclass cls=env->GetObjectClass(obj);
jmethodID mid=env->GetMethodID(cls,"callback","()V");
env->CallVoidMethod(obj,mid);
//調用callback方法,獲取異常信息
jthrowable excp=0;
const char* str;
excp=env->ExceptionOccurred();//得到異常對象
if(excp){
jclass cls=env->GetObjectClass(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:"<<str<<endl;
env->ExceptionClear();//把這個異常清除掉,使之不能傳遞到java
}
jclass errclass;
errclass=env->FindClass("java/lang/ArrayIndexOutOfBoundsException");
env->ThrowNew(errclass, "thrown from C++ code");//向java中拋出異常
}
jthrowable ExceptionOccurred(JNIEnv *env);
返回值:返回一個異常對象。
void ExceptionClear(JNIEnv *env);
功能:清除該異常對象,只在本線程中傳遞。
jint ThrowNew(JNIEnv *env, jclass clazz,
const char *message);
功能:拋出一個指定的異常對象和異常信息。
打印結果:
In C:java.lang.ArrayIndexOutOfBoundsException: 5
java.lang.ArrayIndexOutOfBoundsException: thrown from C++ code
第一行是C++中打印的信息,第二行是從C++中拋出的異常。
如果把 env->ExceptionClear();//把這個異常清除掉,使之不能傳遞到java
jclass errclass;
errclass=env->FindClass("java/lang/ArrayIndexOutOfBoundsException");
env->ThrowNew(errclass, "thrown from C++ code");//向java中拋出異常
都注釋掉,也會向JAVA中拋出異常。這時就沒有清除掉異常了。
總之,數組的傳遞與異常在處理在JNI中非常的重要,在這里只是簡單的介紹一下,歡迎大家共同學習。