android jni程式碼編寫規則--整理總結

yangxi_001發表於2016-04-14
轉自:http://blog.csdn.net/lizhiguo0532/article/details/7219357 
    JNI層的程式碼其實比較簡單,難點是要掌握c++和java資料型別的轉換,明白java程式是執行在虛擬機器中的,特別是函式並不是可以互相呼叫,jni中的記憶體概念並沒有暴露給java虛擬機器程式等。

 

一.   java引數型別和jni本地引數型別對照

 

基本型別

Java 型別         jni本地型別                    描述 

boolean             jboolean                    C/C++ unsigned 8 bits

byte                jbyte                       C/C++ signed 8 bits

char                jchar                       C/C++ unsigned 16 bits

short               jshort                      C/C++ signed 16 bits

int                 jint                        C/C++ signed 32 bits

long                jlong                       C/C++ signed 64 bits

float               jfloat                      C/C++  32位浮點型

double              jdouble                     C/C++  64位浮點型

void                void                        N/A

                    表一

 

物件型別

Object              jobject                     任何Java物件,或者沒有對應

java型別的物件

Class               jclass                      class物件

String              jstring                     字串物件

                    表二

 

陣列型別

boolean[]           jbooleanArray               布林型陣列 unsigned

byte[]              jbyteArray                  位元型陣列 signed

char[]              jcharArray                  字元型陣列 unsigned

short[]             jshortArray                 短整型陣列 signed

int[]               jintArray                   整型陣列 signed

long[]              jlongArray                  長整型陣列 signed

float[]             jfloatArray                 浮點型陣列

double[]            jdoubleArray                雙浮點型陣列

Object[]            jobjectArray                任何物件的陣列

                    表三

 

 

JNI引用型別與Java的對應關係如下樹層次圖:

1. java中的返回值void和JNI中的void是完全對應的。

 

2. java中的基本資料型別(boolean ,byte , char ,short ,int,long,float,double八種)在JNI中對應的資料型別只要在前面加上j就對應了(jboolean ,jbyte , jchar ,jshort ,jint,jlong,jfloat,jdouble)。

JNI中還有個Java中沒有的jsize,定義如下:

typedef jint jsize;

其實jsize整型是用來描述基本指標和大小。

 

3. java中的物件,包括類庫中定義的類、介面以及自定義的類介面,都對應於JNI中的jobject。

 

4. java中基本資料型別的陣列對應與JNI中的j<type>array型別。(type就是上面說的8種基本資料型別)

 

5. java中物件的陣列對應於JNI中的jobjectArray型別。(在java中一切物件、介面以及陣列都是物件)

http://blog.csdn.net/xyz_lmn/article/details/6956003

http://www.cnblogs.com/liangwind/archive/2009/08/18/1925515.html

   

    Java基本型別的精度

java 的基本資料型別是不存在有符號和無符號這種概念的. JAVA中的基本資料型別不存在無符號的,它們的取值範圍是固定的,不會隨著機器硬體環境或者作業系統的改變而改變。

簡單型別   位元組數   範圍/精度

float       4       32位IEEE754單精度

double      8       64位IEEE754雙精度

byte        1       -128到127

short       2       -32,768到32,767

int         4       -2,147,483,648到2,147,483,647

long        8       -9,223,372,036,854,775,808到9,223,372,036,854,775,807

char        2       整個Unicode字符集

boolean     1       True或者false

像byte 是範圍是 -128到127, 你想要變為 0到255 怎麼辦, 跟 0XFF 做與運算 就可以了.

如 byte bb , 如果你想賦值它值 255, 那是不行的, 就算賦值了, bb 的值也是 255 對 256 求模後的值 -1

      如果你只是想取他 0到255 的值, 還是很簡單的,

      bb & 0XFF  , 如 bb = -1,  那 bb & 0XFF 結果為 255,

      這個與運算後的結果會隱式轉換為int 型別的, 因為 byte 放不下了.

與運算 還是很快的, 比加減法還快的.

http://www.stuhack.com/biancheng/java/35169.html

   

   

二.jni層使用java的基本型別資料

對於上面八種基本的資料型別boolean ,byte , char ,short ,int,long,float,double,jni層的c++程式碼可以用強制直接轉換成對於長度的c/c++型別資料。

如:unsigned char tmp = (unsigned char) m_jboolean;

    unsigned short tmp = (unsigned short)m_jchar;

    或者同長度型別的資料就可以直接賦值的,int tmp = m_jint;

 

 

三.jni層對陣列的使用

JNI通過JNIEnv提供的操作Java陣列的功能。它提供了兩個函式:一個是操作java的簡單型陣列的,另一個是操作物件型別陣列的。

1.     操作java的簡單型陣列

因為速度的原因,簡單型別的陣列作為指向本地型別的指標暴露給原生程式碼。因此,它們能作為常規的陣列存取。這個指標是指向實際的Java陣列或者Java陣列的拷貝的指標。另外,陣列的佈置保證匹配本地型別。

為了存取Java簡單型別的陣列,你就要要使用GetXXXArrayElements函式(見表三),XXX代表了陣列的型別。這個函式把Java陣列看成引數,返回一個指向對應的本地型別的陣列的指標。

        完整的函式族見下表:

函式                         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

 

當你對陣列的存取完成後,要確保呼叫相應的ReleaseXXXArrayElements函式,引數是對應Java陣列和GetXXXArrayElements返回的指標。如果必要的話,這個釋放函式會複製你做的任何變化(這樣它們就反射到java陣列),然後釋放所有相 關的資源。

例如:

static jint com_ginwave_fs_com_HWRC_GetRecogRange(JNIEnv* env, jclass clazz, jintArray Handle)

{

unsigned long *pHandle = NULL;

int ret = 0;

jint *tmpHandle = env->GetIntArrayElements(Handle, 0);

pHandle = (unsigned long *)tmpHandle;

ret = (int)HWRC_GetRecogRange(pHandle);

env->ReleaseIntArrayElements(Handle, tmpHandle, 0);

return r

}

 

獲取陣列的長度:

jint theArrayLength = env->GetArrayLength(Frame);

       

        建立一個新的函式陣列簇如下:

NewBooleanArray

NewByteArray

NewCharArray

NewShortArray

NewIntArray

NewLongArray

NewFloatArray

NewDoubleArray

        引數為陣列長度,如:

        jbyte *list;

        jbyteArray byteArray = NULL;

        byteArray = env->NewByteArray(len);

        if (byteArray)

            env->SetByteArrayRegion(byteArray, 0, len, list);

       

        關於函式簇GetXXXArrayRegion和SetXXXArrayRegion,其中XXX為基本型別。

        例如:

        env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset);

        Setxxx的方向是從JNI層往java層傳遞;

        env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);

        而Getxxx的方向則是資料從java層向jni層傳遞。

       

        這裡是獲取簡單型陣列中的資料供jni或者下層使用,如果需要在jni層設定java

中對於的簡單型陣列的話,就需要使用到接下來講到的物件型別的一些操作。

    總結下,有以下幾簇函式:

    GetArrayLength

    NewXXXArray

    GetXXXArrayElements

    ReleaseXXXArrayElements

    GetXXXArrayRegion

    SetXXXArrayRegion

    對於資料,暫時遇到這些函式了。。。

   

 

 

2.     操作java物件型別資料

Java物件做為引用被傳遞到本地方法中,所有這些Java物件的引用都有一個共同的父型別jobject(相當於java中的Object類是所有類的父類一樣)。

 

1). string物件

從java程式中傳過去的String物件在本地方法中對應的是jstring型別,jstring型別和c中的char*不同,所以如果你直接當做char*使用的話,就會出錯。因此在使用之前需要將jstring轉換成為c/c++中的char*,這裡使用JNIEnv的方法轉換。

static jstring  com_prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)

{

char buf[128];

const char *str = (*env)->GetStringUTFChars(env, prompt, 0);

printf("%s", str);

env->ReleaseStringUTFChars(prompt, str);

...

        

}

這裡使用GetStringUTFChars方法將傳進來的prompt(jstring型別)轉換成為UTF-8的格式,就能夠在本地方法中使用了。

注意:在使用完你所轉換之後的物件之後,需要顯示呼叫ReleaseStringUTFChars方法,讓JVM釋放轉換成UTF-8的string的物件的空間,如果不顯示的呼叫的話,JVM中會一直儲存該物件,不會被垃圾回收器回收,因此就會導致記憶體溢位。

 

下面是訪問String的一些方法:

GetStringUTFChars        將jstring轉換成為UTF-8格式的char*

GetStringChars           將jstring轉換成為Unicode格式的char*

ReleaseStringUTFChars    釋放指向UTF-8格式的char*的指標

ReleaseStringChars       釋放指向Unicode格式的char*的指標

NewStringUTF             建立一個UTF-8格式的String物件

NewString                建立一個Unicode格式的String物件

GetStringUTFLength       獲取UTF-8格式的char*的長度

GetStringLength          獲取Unicode格式的char*的長度

 

提供給兩個jstring和char *互相轉換的函式:

/* c/c++ string turn to java jstring */

static jstring strTojstring(JNIEnv* env, const unsigned char* pStr)

{

    int        strLen    = strlen((const char*)pStr);

    jclass     jstrObj   = env->FindClass("java/lang/String");

    jmethodID  methodId  = env->GetMethodID(jstrObj, "", "([BLjava/lang/String;)V");

    jbyteArray byteArray = env->NewByteArray(strLen);

    jstring    encode    = env->NewStringUTF("utf-8");

 

    env->SetByteArrayRegion(byteArray, 0, strLen, (jbyte*)pStr);

   

    return (jstring)env->NewObject(jstrObj, methodId, byteArray, encode);

}

//check ok!

 

 

/* java jstring turn to c/c++ string */

static char* jstringTostr(JNIEnv* env, jstring jstr)

{       

    char* pStr = NULL;

 

    jclass     jstrObj   = env->FindClass("java/lang/String");

    jstring    encode    = env->NewStringUTF("utf-8");

    jmethodID  methodId  = env->GetMethodID(jstrObj, "getBytes", "(Ljava/lang/String;)[B");

    jbyteArray byteArray = (jbyteArray)env->CallObjectMethod(jstr, methodId, encode);

    jsize      strLen    = env->GetArrayLength(byteArray);

    jbyte      *jBuf     = env->GetByteArrayElements(byteArray, JNI_FALSE);

 

    if (jBuf > 0)

    {

        pStr = (char*)malloc(strLen + 1);

 

        if (!pStr)

        {

            return NULL;

        }

 

        memcpy(pStr, jBuf, strLen);

 

        pStr[strLen] = 0;

    }

 

    env->ReleaseByteArrayElements(byteArray, jBuf, 0);

 

    return pStr;

}

// check ok!

       

        2) 訪問java物件

    JNI提供的另外一個功能是在原生程式碼中使用Java物件。通過使用合適的JNI函式,你可以建立Java物件,get、set 靜態(static)和 例項(instance)的域,呼叫靜態(static)和例項(instance)函式。JNI通過ID識別域和方法,一個域或方法的ID是任何處理域和方法的函式的必須引數。

下表列出了用以得到靜態(static)和例項(instance)的域與方法的JNI函式。每個函式接受(作為引數)域或方法的類,它們的名稱,符號和它們對應返回的jfieldID或jmethodID。

       

        函式                    描述

GetFieldID              得到一個例項的域的ID

GetStaticFieldID        得到一個靜態的域的ID

GetMethodID             得到一個例項的方法的ID

GetStaticMethodID       得到一個靜態方法的ID

       

        下面以一個例子來說明用法:上下層之間需要傳遞一個或者多個結構體值。

        c/c++結構體定義:

        typedef struct tagTHWFrame{

            short left;

            short top;

            short width;

            short height;

} THWFrame;

當然在java層也需要定義一個匹配的類出來:

public class THWFrame{

    public short left;

    public short top;

    public short width;

    public short height;   

}

注意貌似這裡只能定義成public的。

下面是jni層相關的程式碼,主要思想是對java對應類物件的屬性域獲得ID值後一個一個訪問。

/* int HWRC_SetInputBox( unsigned long *pHandle, const THWFrame *pFrame ); */

static void ObjectTOTHWFrameStruct(JNIEnv* env, jobjectArray Frame, THWFrame *pFrame, int index)

{

    jobject obj = env->GetObjectArrayElement(Frame, index);

    jclass cls = env->GetObjectClass(obj);

    jfieldID left = env->GetFieldID(cls, "left", "S");

    pFrame[index].left = (short)env->GetShortField(obj, left);

       

    jfieldID top = env->GetFieldID(cls, "top", "S");

    pFrame[index].top = (short)env->GetShortField(obj, top);

       

    jfieldID width = env->GetFieldID(cls, "width", "S");

    pFrame[index].width = (short)env->GetShortField(obj, width);

       

    jfieldID height = env->GetFieldID(cls, "height", "S");

    pFrame[index].height = (short)env->GetShortField(obj, height); 

   

}

static jint com_ginwave_fs_com_HWRC_SetInputBox(JNIEnv* env, jclass clazz,

            jintArray Handle, jobjectArray Frame)

{

    unsigned long *pHandle = NULL;

    THWFrame *pFrame = NULL;

    int frame_len = 0;

    int ret = 0;

   

    jint *tmpHandle = env->GetIntArrayElements(Handle, 0);

    pHandle = (unsigned long *)tmpHandle;

   

    jint theArrayLength = env->GetArrayLength(Frame);

    frame_len = theArrayLength;

    pFrame = (THWFrame *)malloc( sizeof(THWFrame) * frame_len );

   

    for( int i = 0; i < frame_len; i++ ){

        ObjectTOTHWFrameStruct(env, Frame, pFrame, i);

    }

   

    ret = HWRC_SetInputBox(pHandle, (const THWFrame *)pFrame);

   

    env->ReleaseIntArrayElements(Handle, tmpHandle, 0);

    free(pFrame);

    frame_len = NULL;

   

    return ret;

}

// {"HWRC_SetInputBox", "([I[Ljava/com/ginwave/fs/com/THWFrame)I" , (void *)com_ginwave_fs_com_HWRC_SetInputBox },

// check ok!

 

 

/* int HWRC_GetInputBox( unsigned long *pHandle, THWFrame *pFrame ); */

static void THWFrameStructTOObject(JNIEnv* env, jobjectArray Frame, THWFrame *pFrame, int index)

{

    jobject obj = env->GetObjectArrayElement(Frame, index);

    jclass cls = env->GetObjectClass(obj);

    jfieldID left = env->GetFieldID(cls, "left", "S");

    env->SetShortField(obj, left, (short)pFrame[index].left);

       

    jfieldID top = env->GetFieldID(cls, "top", "S");

    env->SetShortField(obj, top, (short)pFrame[index].top);

       

    jfieldID width = env->GetFieldID(cls, "width", "S");

    env->SetShortField(obj, width, (short)pFrame[index].width);

       

    jfieldID height = env->GetFieldID(cls, "height", "S");

    env->SetShortField(obj, height, (short)pFrame[index].height);

}

static jint com_ginwave_fs_com_HWRC_GetInputBox(JNIEnv* env, jclass clazz,

            jintArray Handle, jobjectArray Frame)

{

    unsigned long *pHandle = NULL;

    THWFrame *pFrame = NULL;

    int frame_len = 0;

    int ret = 0;

   

    jint *tmpHandle = env->GetIntArrayElements(Handle, 0);

    pHandle = (unsigned long *)tmpHandle;

   

    jint theArrayLength = env->GetArrayLength(Frame);

    frame_len = theArrayLength;

    pFrame = (THWFrame *)malloc( sizeof(THWFrame) * frame_len );

   

    ret = HWRC_GetInputBox(pHandle, pFrame);

   

    for( int i = 0; i < frame_len; i++ ){

        THWFrameStructTOObject(env, Frame, pFrame, i);

    }

   

    env->ReleaseIntArrayElements(Handle, tmpHandle, 0);

    free(pFrame);

    frame_len = NULL;

   

    return ret;

}

// {"HWRC_GetInputBox", "([I[Ljava/com/ginwave/fs/com/THWFrame)I" , (void *)com_ginwave_fs_com_HWRC_GetInputBox },

// check ok!

 

其中,比較難理解的應該是函式的簽名了,下面是他們的一些規則:

這個陣列的型別是JNINativeMethod,定義如下:

typedef struct {

const char* name;

const char* signature;

void* fnPtr;

} JNINativeMethod;

             

第一個變數name是Java中函式的名字。

第二個變數signature,用字串是描述了函式的引數和返回值

第三個變數fnPtr是函式指標,指向C函式。

 

其中比較難以理解的是第二個引數,例如

"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

             

實際上這些字元是與函式的引數型別一一對應的。

"()" 中的字元表示引數,後面的則代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具體的每一個字元的對應關係如下

             

字元             Java型別      C型別

V              void              void

Z              jboolean     boolean

I              jint         int

J                     jlong             long

D              jdouble           double

F               jfloat              float

B              jbyte             byte

C              jchar                     char

S               jshort             short

             

陣列則以"["開始,用兩個字元表示

[I       jintArray          int[]

[F       jfloatArray         float[]

[B       jbyteArray          byte[]

[C        jcharArray          char[]

[S        jshortArray          short[]

[D       jdoubleArray         double[]

[J        jlongArray         long[]

[Z        jbooleanArray      boolean[]

 

objects物件          Lfully-qualified-class-name;         L類名

Arrays陣列          [array-type                                 [陣列型別

 

方法引數或者返回值為java中的物件時,必須以“L”加上其路徑,不過此路徑必須以“/”分開,自定義的物件也使用本規則,不在包中時直接“L”加上類名稱。比如說 java.lang.String為“java/lang/String”,com.nedu.jni.helloword.Student為"com /nedu/jni/helloword/Student"

 

方法引數或者返回值為陣列時型別前加上[,例如[I表示 int[],[[[D表示 double[][][],即幾維陣列就加幾個[。

 

JNI函式中始終包含兩個必要的引數:JNIEnv* env, jclass clazz

JNIEnv *――它是一個介面指標,用於定位函式表中的函式!

在JNI規範中一般稱  為   “Interface Pointer”。看到這兒好像和過程呼叫很類似了!是的,JNI中的操作過程,就是程式導向的!後面的jobject是  一個指向該類的指標,類似與C語言中的this。這個第二個引數是變化的,當該方法為類的例項方法時該引數為jobject;當該方法為類方法 (即靜態方法)時該引數為jclass,指向該類的class。

通過ndk程式設計來得到jni層標頭檔案的時候,這第二個引數對於staic方法,生成出來的就是jclass,而對於非staic方法,生成出來的就是jobject。

             

從上圖可知,jobject包含了其實概括了所有的java型別,也就是說,像上圖中的非jobject型別的資料,在傳遞引數的時候都可以以jobject型別傳遞下去。比如說,如果要java中要傳遞一個二(多)維int陣列下去,就可以包裝成jobjectArray傳下去,只不過對應的簽名要弄成[[I了。

 

對於訪問java物件的方法

在本地方法中呼叫Java物件的方法的步驟:

①.獲取你需要訪問的Java物件的類:

jclass cls = (*env)->GetObjectClass(env, obj);   // FindClass(“android/util/log”)

使用GetObjectClass方法獲取obj對應的jclass。 // 直接搜尋類名,需要是static修飾的類。

②.獲取MethodID:

jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");

// GetStaticMethodID(…)  , 獲取靜態方法的ID

使用GetMethdoID方法獲取你要使用的方法的MethdoID。其引數的意義:

env-->JNIEnv

cls-->第一步獲取的jclass

"callback"-->要呼叫的方法名

"(I)V"-->方法的Signature, 簽名同前面的JNI規則。

③.呼叫方法:

(*env)->CallVoidMethod(env, obj, mid, depth);

// CallStaticIntMethod(….) , 呼叫靜態方法

使用CallVoidMethod方法呼叫方法。引數的意義:

env-->JNIEnv

obj-->通過本地方法穿過來的jobject

mid-->要呼叫的MethodID(即第二步獲得的MethodID)

depth-->方法需要的引數(對應方法的需求,新增相應的引數)

注:這裡使用的是CallVoidMethod方法呼叫,因為沒有返回值,如果有返回值的話使用對應的方法,在後面會提到。

CallVoidMethod               CallStaticVoidMethod

CallIntMethod                     CallStaticVoidMethod

CallBooleanMethod              CallStaticVoidMethod

CallByteMethod                   CallStaticVoidMethod

其實jni中還有很多很多的介面函式這裡沒有列舉,可以直接參考原始碼:

$ find  frameworks/base  type d  -name  jni

./voip/jni

./rfid/jni

./freestylus/jni

./native/graphics/jni

./drm/jni

./tests/BrowserTestPlugin/jni

./services/jni

./packages/TtsService/jni

./media/jni

./media/libdrm/mobile1/include/jni

./media/libdrm/mobile1/src/jni

./graphics/jni

./core/jni

./opengl/tests/gl_jni/jni

./opengl/tests/gl2_jni/jni

./opengl/tests/gldual/jni

這麼多jni目錄都可以參考,其中主要是core/jni目錄了。

四、關於異常

異常介面有:

jniThrowException(env, "java/lang/RuntimeException", "Method called after release()");

env->ThrowNew(env->FindClass("java/io/IOException"),"CWJLog Error, IOException");

doThrow(env, "java/lang/IllegalStateException", msg);

使用Throw,自己構造(沒用過)

jclass clazz = env->FindClass("java/io/IOException");

jmethodID methodId = env->GetMethodID(clazz, "<init>", "()V");

jthrowable throwable = env->NewObject(clazz, methodId);

env->Throwthrowable);

 

 

參考網址:

http://blog.csdn.net/xyz_lmn/article/details/6959545

 Android JNI入門第三篇——jni標頭檔案分析 

http://blog.csdn.net/xyz_lmn/article/details/6966259

 Android JNI入門第四篇——Android.mk檔案分析

http://blog.csdn.net/xyz_lmn/article/details/7017420

 Android JNI開發提高篇 

http://blog.csdn.net/xyz_lmn/article/details/6956003

 Android JNI入門第二篇——Java引數型別與本地引數型別對照

 

http://wenku.baidu.com/view/e9e28ca1b0717fd5360cdc18.html

JNI入門

http://www.ibm.com/developerworks/cn/java/j-jni/

使用 Java Native Interface 的最佳實踐

http://helloxuweifu.iteye.com/blog/1168647

http://blog.csdn.net/kangyaping/article/details/6584027

JNI函式呼叫大全

 

http://newfaction.net/2010/11/30/java-jni-getfieldid-and-getmethodid-and-parameter-description.html

java jni GetFieldID 和 GetMethodID 以及引數的說明

http://hi.baidu.com/spmno/blog/item/7d4d764ea78a6809b3de0588.html

jni中使用陣列的幾個方法

http://xxw8393.blog.163.com/blog/static/3725683420107109411366/

JNI 返回結構體引數 

http://www.cnblogs.com/nicholas_f/archive/2010/11/30/1892124.html

JNI中java型別與C/C++型別對應關係

http://blog.csdn.net/sunny09290/article/details/6884994

JNI資料型別

http://www.cnblogs.com/liangwind/archive/2009/08/18/1925515.html

jni --c/c++ 資料型別、陣列、物件

http://www.cnblogs.com/diyunpeng/archive/2009/09/24/1573296.html

Java有符號數與無符號數

http://www.stuhack.com/biancheng/java/35169.html

Java的基本資料型別是無符號的

相關文章