1. 程式人生 > >Android NDK 開發(二)JNI 傳遞引數和返回值

Android NDK 開發(二)JNI 傳遞引數和返回值

前言

我們在使用 JNI 時最常問到的是 JAVA 和 C/C++之間如何傳遞資料,以及資料型別之間如何 互相對映。我們從整數等基本型別和陣列、字串等普通的物件型別開始講述。至於如何傳遞任意物件,將在後面會更新。

正文

JNI簡介及呼叫流程這篇文章,我們再來實現一個非靜態的native方法。

Java端:

public class JniTest {

    //靜態的
    public native static String getStringFromC();

    //非靜態的
    public native String getString2FromC(int
i); public static void main(String[] args) { String text = getStringFromC(); System.out.println(text); JniTest t = new JniTest(); text = t.getString2FromC(6); System.out.println(text); } //載入動態庫 static{ System.loadLibrary("jni_study"
); } }

在native層實現 getString2FromC 非靜態方法;

JNIEXPORT jstring JNICALL Java_com_study_jni_JniTest_getString2FromC
(JNIEnv *env, jobject jobj, jint num){
    return (*env)->NewStringUTF(env,"C String2");
}

由此看出,每個native函式,都至少有兩個引數(JNIEnv*,jclass或者jobject)

  • 當native方法為靜態方法時:
    jclass 代表native方法所屬類的class物件(JniTest.class)
  • 當native方法為非靜態方法時:
    jobject 代表native方法所屬的物件

1.Java基本資料型別傳遞

用過Java的人都知道,Java中的基本型別包括boolean,byte,char,short,int,long,float,double這樣幾種,如果你用這幾種型別做native方法的引數,當你通過javah -jni生成.h檔案的時候,只要看一下生成的.h檔案,就會一清二楚,這些型別分別對應的型別是jboolean,jbyte,jchar,jshort,jint,jlong,jfloat,jdouble 。這幾種型別幾乎都可以當成對應的C++型別來用。

Java基本資料型別與JNI資料型別的對映關係如下:

JNI的基本資料型別

對應的java引用資料型別:

struct _jobject;

typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

關係圖:

這裡寫圖片描述

最終都是jobject 的結構體指標型別。

2.String引數的傳遞

Java的String和C++的string是不能對等起來的,所以處理起來比較麻煩。先看一個例子

class Prompt {

// native method that prints a prompt and reads a line
private native String getLine(String prompt);

public static void main(String args[]) {
Prompt p = new Prompt();
String input = p.getLine("Type a line: ");

System.out.println("User typed: " + input);
}

static {
System.loadLibrary("Prompt");
}
}

在這個例子中,我們要實現一個native方法,String getLine(String prompt);讀入一個String引數,返回一個String值。通過執行javah -jni得到的標頭檔案是這樣的。

#include <jni.h>
#ifndef _Included_Prompt
#define _Included_Prompt
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject this, jstring prompt);
#ifdef __cplusplus
}
#endif
#endif

jstring是JNI中對應於String的型別,但是和基本型別不同的是,jstring不能直接當作C++的string用。如果你用

cout << prompt << endl;

編譯器肯定會扔給你一個錯誤資訊的。
其實要處理jstring有很多種方式,這裡只講一種我認為最簡單的方式,看下面這個例子:

#include "Prompt.h"
#include <iostream>

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
const char* str;
str = env->GetStringUTFChars(prompt, false);
if(str == NULL) {
return NULL; 
}

std::cout << str << std::endl;

//釋放資源
env->ReleaseStringUTFChars(prompt, str);

// 返回一個字串
char* tmpstr = "return string succeeded";
jstring rtstr = env->NewStringUTF(tmpstr);
return rtstr;
}

在上面的列子程式碼中,作為引數的prompt不能直接被C++程式使用,先做了如下轉換

str = env->GetStringUTFChars(prompt, false);

將jstring型別變成一個char*型別。
返回的時候,要生成一個jstring型別的物件,也必須通過如下方式,

jstring rtstr = env->NewStringUTF(tmpstr);

這裡用到的GetStringUTFCharsNewStringUTF都是JNI提供的處理String型別的函式。

3.陣列型別的傳遞

和String一樣,JNI為Java基本型別的陣列提供了j*Array型別,比如int[]對應的就是jintArray。來看一個傳遞int陣列的例子。

java端主要程式碼:

    public native void giveArray(int[] array);

    int[] array = {9,100,10,37,5,10};
        //排序
    t.giveArray(array);

    for (int i : array) {
        System.out.println(i);
    }

C實現主要程式碼:

int compare(int *a,int *b){
    return (*a) - (*b);
}

//傳入
JNIEXPORT void JNICALL Java_com_study_jni_JniTest_giveArray
(JNIEnv *env, jobject jobj, jintArray arr){
    //jintArray -> jint指標 -> c int 陣列
    jint *elems = (*env)->GetIntArrayElements(env, arr, NULL);
    //printf("%#x,%#x\n", &elems, &arr);

    //陣列的長度
    int len = (*env)->GetArrayLength(env, arr);
    //排序
    qsort(elems, len, sizeof(jint), compare);   

    //同步
    //mode
    //0, Java陣列進行更新,並且釋放C/C++陣列
    //JNI_ABORT, Java陣列不進行更新,但是釋放C/C++陣列
    //JNI_COMMIT,Java陣列進行更新,不釋放C/C++陣列(函式執行完,陣列還是會釋放)

    (*env)->ReleaseIntArrayElements(env, arr, elems, JNI_COMMIT);
}

這個程式碼中的GetIntArrayElementsReleaseIntArrayElements函式就是JNI提供用於處理int陣列的函式。

如果用arr[i]的方式去訪問jintArray型別,不用問肯定會出錯。
JNI還提供了另一對函式GetIntArrayRegionReleaseIntArrayRegion訪問int陣列,不在這裡做介紹,至於其他的型別陣列,方法類似。

4.返回陣列

在JNI中,二維陣列和String陣列都被視為object陣列,因為陣列和String被視為object。最後一個示例說明如何在原生代碼中建立一個字串陣列並將它返回給 Java 呼叫者。

java端的程式碼:

    int[] array2 = t.getArray(10);
    System.out.println("------------");
    for (int i : array2) {
        System.out.println(i);
    }

函式實現:

//返回陣列
JNIEXPORT jintArray JNICALL Java_com_study_jni_JniTest_getArray(JNIEnv *env, jobject jobj, jint len){
    //建立一個指定大小的陣列
    jintArray jint_arr = (*env)->NewIntArray(env, len);
    jint *elems = (*env)->GetIntArrayElements(env, jint_arr, NULL); 
    int i = 0;
    for (; i < len; i++){
        elems[i] = i;
    }

    //同步
    (*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0);   

    return jint_arr;
}

jintArray jint_arr ;因為要返回值,所以需要建立一個指定大小jintArray物件。根據jintArray物件拿到jint 指標。然後,賦值,同步,這樣通過引數返回就可以了。
下一篇將會介紹C 訪問Java 屬性和方法。