2014年2月5日 星期三

[轉貼]android jni代碼編寫規則--整理總結


 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中的jarray類型。(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, "", "()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的基本數據類型是無符號的

沒有留言:

張貼留言