Android Studio NDK 入門教程(2)--Java與C++之間的簡單資料轉換與傳遞

Wastrel_xyz發表於2016-08-16

概述

本文將講解Java與原生程式碼之間的資料轉換,包括基礎型別之間的轉換,以及陣列的傳遞與轉換。

型別轉換表

JAVA基礎型別與C++之間的對應表

Java型別 C/C++型別 描述
boolean jboolean 無符號8位整數
byte jbyte 有符號8位整數
char jchar 有符號16位整數
short jshort 有符號16位整數
int jint 有符號32位整數
long jlong 有符號64位整數
float jfloat 32位單精度浮點數
double jdouble 64位雙精度浮點數

這一點可以從jni.h標頭檔案中定義看到:

typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */

由上面的JNI定義程式碼可以看到,基礎型別都是根據Java型別大小等價轉換的。因此在使用Java傳遞過來的基礎型別或者返回Java中的基礎型別都可以直接使用。比如:

JNIEXPORT jint JNICALL
Java_com_example_wastrel_test_Test_BaseTypeTest(JNIEnv *env, jclass type,jint i) {
    return i+5;
}

Java引用型別與C++之間的對應表

Java型別 C/C++型別 描述
Objec jobject 任何Java物件
Class jclass Class類物件
String jstring String類物件
Object[] jobjectArray 物件陣列
boolean[] jbooleanArray 布林陣列
byte[] jbyteArray 位元組陣列
char[] jcharArray 字元型陣列
short[] jshortArray 短整型陣列
int[] jintArray 整型陣列
long[] jlongArray 長整型陣列
float[] jfloatArray 浮點型陣列
double[] jdoubleArray 雙精度浮點型陣列
/*
 * Reference types, in C++
 */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};

typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;
typedef _jthrowable*    jthrowable;
typedef _jobject*       jweak;

通過jni裡的定義可以看到,任何引用型別(陣列在Java中也是引用傳遞的)傳遞到C++裡面只是一個指向Java物件的指標。因此引用型別不能直接使用,JNIEnv提供了大量的方法來完成了他們之間的轉換。

使用JNIEnv完成資料之間的轉換

這裡只說明引用物件之間的轉換,因為對於普通型別,Java與C++是互通的。

String的傳遞

這裡將String單獨拿出來講解,因為String在Java中有著超高的使用頻次,JNIEnv對String有相對應的轉換函式。JNI裡的函式定義如下:

/**轉換成unicode相關函式 */
jstring     NewString(const jchar*, jsize);
jsize       GetStringLength(jstring);
const jchar* GetStringChars(jstring, jboolean* isCopy);
void        ReleaseStringChars(jstring, const jchar*);
/** 轉換成UTF-8相關函式 */
jstring     NewStringUTF(const char*);
jsize       GetStringUTFLength(jstring);
const char* GetStringUTFChars(jstring, jboolean* isCopy);
void        ReleaseStringUTFChars(jstring, const char*);

當從 JNI 函式 GetStringChars中返回得到字串B時,如果B是原始字串java.lang.String 的拷貝,則isCopy被賦值為JNI_TRUE。如果B和原始字串指向的是JVM中的同一份資料,則 isCopy被賦值為 JNI_FALSE。

當isCopy值為JNI_FALSE時,原生程式碼決不能修改字串的內容,否則JVM中的原始字串也會被修改,這會打破 JAVA語言中字串不可變的規則。
通常,因為你不必關心JVM是否會返回原始字串的拷貝,你只需要為 isCopy傳遞NULL作為引數。

由以上程式碼,我們可以清晰的看到String處理函式分為了Unicode和UTF-8兩種編碼型別,但在Android中使用的UTF-8相關的函式,兩種編碼提供的功能都基本一致,從函式名就可以看出對應函式的功能。下面通過一個使用C++合併String的例子來演示如何使用:

//Java中定義Native函式
 public native static String strConcat(String str1,String str2);
//生成C++函式並用C++實現字串連線功能
#include "string.h"
JNIEXPORT jstring JNICALL Java_com_example_wastrel_test_Test_strConcat
        (JNIEnv *env, jclass clazz, jstring str1, jstring str2){
        //將jstring轉換成const char*指標,使用const修飾符表示其內容不可被修改
        const char* c1=env->GetStringUTFChars(str1, NULL);
        const char* c2=env->GetStringUTFChars(str2, NULL);
        //計算新字串的長度
        int size=strlen(c1)+strlen(c2);
        //建立一個新的字串,這裡長度+1是為了使字串有結尾標記'\0'
        char * n_char=new char[size+1];
        //利用C標準庫提供的字串操作方法對字串進行連線,這裡需要include"string.h"標頭檔案
        strcpy(n_char,c1);
        strcat(n_char,c2);
        //將生成的新字串轉換成UTF的jstring
        jstring rs=env->NewStringUTF(n_char);
        //刪除剛剛分配的記憶體 避免引起記憶體洩漏
        delete [] n_char;
        //通知JVM虛擬機器Native程式碼不在持有字串的引用,說明白點,就是告訴虛擬機器我不使用它了,你可以回收了。
        //因為在JVM中如果物件被引用,那麼物件將不會被回收。
        //這裡為什麼要傳遞jstring和生成的char*呢?是因為char*有可能是jstring的拷貝,如果是拷貝,那麼char*就應該被刪除。
        env->ReleaseStringUTFChars(str1,c1);
        env->ReleaseStringUTFChars(str2,c2);
        return rs;
}
//然後我們在Java中呼叫該函式
print(Test.strConcat("里約奧運",",中國加油"));

注:本處以及往後程式碼中使用的print函式僅僅是把結果追加顯示在介面上的TextView上程式碼如下:

private void print(String str)
{
    tv.append(str+"\n");
}

生成C函式過程請參照上一篇文章:http://blog.csdn.net/venusic/article/details/52121254/

執行結果:
執行結果

基礎資料的陣列傳遞

陣列傳遞跟字串傳遞一樣,Native收到的都是引用的形式,因此JNIEnv也提供了一些列的方法來完成資料的轉換。因為不同資料型別之間的呼叫方式基本一致,此處使用int型陣列作為講解,int[]傳遞到Native後收到的是jintArray物件。

//獲得陣列的長度,該方法適用於所有jarray物件
jsize GetArrayLength(jarray array)

//在本地建立一個jint陣列,這個陣列只能通過SetIntArrayRegion賦值。
jintArray NewIntArray(jsize length);
//將jintArray轉換成jint指標
jint* GetIntArrayElements(jintArray array, jboolean* isCopy);
//取出陣列中的部分元素放在buf裡
void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint* buf);
//給jintArray按區間賦值
void SetIntArrayRegion(jintArray array, jsize start, jsize len,const jint* buf);
//釋放jntArray,第一個參數列示傳過來的jintArray,第二參數列示獲取到的本地陣列指標
//第三個引數需要重點說明,該引數有三個取值:0、JNI_COMMIT、JNI_ABORT
//取值 零(0) 時,更新陣列並釋放所有元素;
//取值 JNI_COMMIT 時,更新但不釋放所有元素;
//取值 JNI_ABORT 時,不作更新但釋放所有元素;
//一般實際應用中取0較多
void ReleaseIntArrayElements(jintArray array, jint* elems,jint mode);

例子1:來自JNI的int陣列

//Java中定義Native函式,size表示返回陣列的大小
public native static int[] getIntArray(int size);
//生成Native函式並實現方法
#include "stdlib.h"
#include "time.h"
//定義隨機數產生巨集 表示產生0~x之間的隨機數
#define random(x) (rand()%x)
JNIEXPORT jintArray JNICALL Java_com_example_wastrel_test_Test_getIntArray
        (JNIEnv *env, jclass clazz, jint size){

        //用時間變數初始化隨機數產生器
        srand((int)time(0));
        jint* rs=new jint[size];
        for (int i=0;i<size;i++)
        {
            //呼叫巨集產生0~100的隨機數
            rs[i]=random(100);
        }
        //通過JNIEnv的NewIntArray方法new一個jintArray物件
        jintArray array=env->NewIntArray(size);
        //把產生的隨機數值賦值給jintArray
        env->SetIntArrayRegion(array,0,size,rs);
        return array;
}
//Java中呼叫函式
int []rs=Test.getIntArray(10);
print("來自於JNI的Int陣列");
print(IntArrayToString(rs));
/**將int[]轉換成逗號分隔便於顯示的輔助函式*/
private String IntArrayToString(int[] ints)
{
    StringBuilder str=new StringBuilder();
    str.append('[');
    for (int i:ints)
    {
        str.append(i);
        str.append(',');
    }
    str.deleteCharAt(str.length()-1);
    str.append(']');
    return str.toString();
}

執行結果:
這裡寫圖片描述

例子2:使用JNI對例1返回的陣列進行排序

//宣告Java Native函式,引數為int[]
 public native static void sortIntArray(int []ints);
//實現C++函式
JNIEXPORT void JNICALL Java_com_example_wastrel_test_Test_sortIntArray
        (JNIEnv *env, jclass clazz, jintArray array){
        //獲得傳遞過來的陣列長度
        jsize size=env->GetArrayLength(array);
        //將陣列轉換成Java指標
        jint* jints=env->GetIntArrayElements(array,NULL);
        //簡單的氣泡排序
        for (int i = 0; i <size-1 ; ++i) {
                for (int j = 0; j <size-1-i ; ++j) {
                        if(jints[j]<jints[j+1])
                        {
                                int t=jints[j];
                                jints[j]=jints[j+1];
                                jints[j+1]=t;
                        }
                }
        }
        //將排序結果更新到Java陣列中,第三個引數等於0表明更新到原陣列並釋放所有元素
        env->ReleaseIntArrayElements(array,jints,0);
        return;
}
//在Java中呼叫
print("通過JNI對int陣列排序:");
Test.sortIntArray(rs);
print(IntArrayToString(rs));

這裡可以看到,我們並沒有返回陣列,而是通過ReleaseIntArrayElements函式將結果更新到Java陣列中。隨之我們在Java中的陣列值已經變更。

執行結果:
這裡寫圖片描述

其他基礎型別陣列的傳遞

其他基礎型別中的陣列傳遞與int[]幾乎一致,函式名更換成各自的型別即可。這裡不在過多敘述,我想有了上面的例子,很容易明白別的基礎資料型別傳遞使用。

示例程式下載地址:http://download.csdn.net/detail/venusic/9604128

相關文章