JNI 筆記 (總結一些基礎的,常用的)

看了一些博客的內容,總結常用的。

具體是哪些,懶得去弄了。做個筆記,方便自己。

(JNI筆記 01)
 先說說幾個常用的參數:
 JavaVM *g_jvm        : JavaVM 接口,g_jvm指針指向一個虛擬機進程,通過該指針,該進程下的所有線程可以獲取 JNIEnv 的指針。
                                      以下函數可以獲取g_jvm:
                                       jint JNI_OnLoad(JavaVM* jvm, void* reserved) ;   // java中加載動態連接庫時,會自動調用這個函數,如果有的話。
                                       jint JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
                                       傳入一個全局變量g_jvm,得到後就可以在任意上下文中使用,具體例子見(JNI筆記 04)

 JNIEnv *env            :JNIEnv 接口,env指針指向用來分配和存儲線程本地數據的區域(如下圖),不同線程的env是不同的。  
                                     可以通過以下函數獲取 env ,必須傳入g_jvm
                                     JNIEnv* JNU_GetEnv() 
                                    { 
                                        JNIEnv* env; 
                                        (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_4); 
                                        //(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);  這個也可以
                                        return env; 
                                     }
Jobject  thiz              :根據native方法是一個static方法還是一個實例方法而不同的。
                                       如果是一個實例方法,它就是調用該方法的對象的引用,和C++中的this指針類似(此時類型爲jobject)。
                                       如果是一個static方法,那它就對應着包含該方法的類(此時類型爲jclass

jfieldID  var_id                      :  成員變量的id類型 , 本質是個指針
jmethodID method_id         :  成員函數的id類型,本質是個指針
jclass  clazz                          :類的類型,在C中是void*  , C++ 中是 _jclass* 



  注意:  在C中, (*env)指向JNINativeInterface結構體的指針,可以用這個指針調用結構體的成員函數指針


===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-



(JNI筆記 02)

(1) java調用C動態庫中的方法。
我把 從java調用本地C/C++代碼的過程分爲三層: 
java 層 :   編寫一個.java文件,比如test.java,定義一個類,加載靜態庫: static { System.loadLibrary("pbochighapi"); }  
                                           聲明要用到的native方法:public native int iCreate (byte[] dataIn, int datalen,  String str); 
                   之後就可以正常使用這個類的所有方法(本地or非本地)

JNI 層:   編寫一個.c文件,屬於一個橋樑的作用,將 java文件中的native聲明的方法,轉換爲JNI調用的接口。
                 通俗易懂的說,就是轉換方法名,轉換參數列表(對應關係見下圖)
                 以便java中的函數調用可以調用對應的函數。我們要做的大部分工作在這個地方。注意要包C底層的 .h文件
                 調用過程 : java的native函數  -----> 此文件中轉換好的對應方法  -----> 每個對應方法中調用了.h文件中聲明的一個方法

C 底層:  純粹的C代碼,C函數。不用任何修改。只需向上提交 .h 文件



java 和 jni  參數轉換對應關係 


  其他引用映射:


Class                    ---------------   jclass
String                  ---------------   jstring
Object                 ---------------   jobject   任何對象
Object[]               ---------------  jobjectArray
boolean[]            ---------------  jbooleanArray
byte[]                   --------------   jbyteArray
char[]                   --------------   jcharArray
short[]                  --------------   jshortArray
int[]                      --------------   jintArray
long[]                  --------------    jlongArray
float[]                  --------------    jfloatArray
double[]             --------------    jdoubleArray
 

1)創建聲明瞭native 方法的類(HelloWorld.java)。 

2)使用javac編譯源文件,生成類文件(HelloWorld.class)。  javac  xxxx.java

3)使用javah -jni生成C頭文件(HelloWorld.h)。                   
javah -jni XXXXX.class

4)用C、C++實現native方法(HelloWorld.c、HelloWorld.cpp),調用c動態庫中的方法。(JNI層)

以上在ecplise中還需要把動態庫 * .so 文件 或者  *.dll 文件放到 libs下
以上動作完成後,實現本地方法 helloworld.c文件的時候,在類似
      JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *env, jobject obj)的方法中,
      需要把jni的參數類型轉換爲C類型,或者把C類型的參數轉換爲 JNI 類型的,就可以給C動態庫的函數傳入參數或者傳出值
      具體 JNI <-->C 轉換參數的方法見(JNI筆記03)

(2) JNI 層調用java層中的對象方法、類方法,訪問對象中的屬性
      在 JNI調用中,不僅僅Java可以調用本地方法,本地代碼也可以調用Java中的方法和成員變量。

jclass FindClass("com/hxsmart/NativeTest") ;        // 獲取一個類的class
jfieldID GetFieldID(jclass clazz, const char *name,const char *sign)                         // 取得普通成員變量的id
jfieldID GetStaticFieldID(jclass clazz, const char*name, const char *sign)               // 取得靜態成員變量的id
jmethodID GetStaticMethodID(jclass clazz, const char*name, const char *sign)    // 取得靜態成員函數的id
jmethodID GetMethodID(jclass clazz, const char *name,constchar *sign)               // 取得普通成員函數的id

這四個方法的參數列表都是一模一樣的,每個參數的含義: 
第一個參數 jclass  clazz            :  代表我們操作的Class類,我們要從這個類裏面取的屬性和方法的ID。 
第二個參數 const  char *name :  這是一個常量字符數組,代表我們要取得的方法名或者變量名。 
第三個參數 const  char *sig      :  這也是一個常量字符數組,代表我們要取得的方法或變量的簽名

什麼是方法或者變量的簽名呢? 
我們來看下面的例子,如何來獲得屬性和方法ID: 
public class NativeTest { 
        public void show(int i){ 
            System.out.println(i); 
        } 
        public void show(double d){ 
            System.out.println(d); 
        } 
} 
本地代碼部分: 
//首先取得要調用的方法所在的類的Class對象,在C/C++中即jclass對象 
jclass clazz_NativeTest= (*env)->FindClass("com/hxsmart/NativeTest"); 
//取得jmethodID 
jmethodID id_show=(*env)->GetMethodID(clazz_NativeTest,“show”,"???"); 
上述代碼中的id_show取得的jmethodID到底是哪個show方法呢?
由於Java語言有方法重載的面向對象特性,所以只通過函數名不能明確的讓JNI找到Java裏對應的方法。
所以這就是第三個參數sig的作用,它用於指定要取得的屬性或方法的類型簽名。

(1)基本類型以特定的大寫字母表示

(2)Java對象以L開頭,然後以“/”分隔包的完整類型,例如String的簽名爲:Ljava/lang/String;   注意最後面的逗號也是要的。

(3)在Java裏數組類型也是引用類型,數組以[ 開頭,後面跟數組元素類型的簽名,例如:int[]   簽名就是[I ,
         對於二維數組,如int[][] 簽名就是[[I,object 數組簽名就是 [Ljava/lang/Object;  



(4)   方法簽名 : 

            (參數1類型簽名參數2類型簽名參數3類型簽名.......)返回值類型簽名

注意:

1. 函數名,在簽名中沒有體現出來

2. 參數列表相挨着,中間沒有逗號,沒有空格

3. 返回值出現在()後面 

例如:

Java方法                                                                   對應簽名 

boolean isLedOn(void) ;                                            ()Z 
void setLedOn(int ledNo);                                         (I)V 
String substr(String str, int idx, int count);                 (Ljava/lang/String;II)Ljava/lang/String
char fun (int n, String s, int[] value);                          (ILjava/lang/String;[I)C 
boolean showMsg(View v, String msg);                     (Landroid/View;Ljava/lang/String;)Z


好了,下面我們就可以根據獲取的ID調用相關的方法 和 設置、訪問成員變量了。

Get方法:
第一個參數代表要獲取的屬性所屬對象或jclass對象
第二個參數即屬性ID

jXXX GetXXXField ( jobject thiz, jfieldID fieldID);            //普通
jXXX GetStaticXXXField ( jclass  thiz, jfieldID fieldID);  //靜態

Set方法:
第一個參數代表要設置的屬性所屬的對象或jclass對象
第二個參數即屬性ID
第三個參數代表要設置的值。

void SetXXXField(jobject thiz , jfieldID fieldID , jxxx val);            //普通
void SetStaticXXXField(jobject thiz , jfieldID fieldID , jxxx val);  //靜態


下面是方法的調用

第一個參數代表調用的這個方法所屬於的對象,或者這個靜態方法所屬的類。

第二個參數代表jmethodID。

第三個參數以及後面的參數,代表這個方法的參數列表

<TYPE>是返回值的類型,比如返回值是void,則 CallVoidMethod

調用普通方法:

Call<Type>Method(jobject thiz, jmethodID methodID,...);

Call<Type>MethodV(jobject thiz, jmethodID methodID,va_listargs);

Call<Type>tMethodA(jobject thiz, jmethodID methodID,const jvalue *args);

調用靜態方法:

CallStatic<Type>Method(jclass clazz, jmethodID methodID,...);

CallStatic<Type>MethodV(jclass clazz, jmethodID methodID,va_listargs);

CallStatic<Type>tMethodA(jclass clazz, jmethodID methodID,const jvalue *args);

例子:
(1)

// Java方法

public int show(int i,double d,char c){

}

 

// 本地調用Java方法

jint i=10L;

jdouble d=2.4;

jchar c=L'd';

(*env)->CallIntMethod(thiz , id_show , i, d, c);

(2) 用得少,略過。
(3) 略過

靜態方法也是一樣的。


(3) JNI 層 創建java對象
     其實就是去調用java某個類的構造函數,所以還是用到了 調用靜態方法的那一套:
     jobject NewObject(jclass clazz, jmethodID methodID,...);

     jobject NewObjectV(jclass clazz, jmethodID methodID,va_list args);

    jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args) ;

步驟:  (1) jclass clazz=(*env)->FindClass("java/util/Date");

             (2)jmethodID id_date=(*env)->GetMethodID(clazz,"Date","()V");  //取得某一個構造方法的jmethodID,後面的 ()V 根據實際的構造函數寫。有參數的話,括號內就寫上參數簽名

            (3)jobject date= (*env)->NewObject(clazz,id_date);     //調用NewObject方法創建java.util.Date對象

    

===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(JNI 筆記 03)
今天搞搞  JNI 的參數怎麼和 C語言的參數進行轉換,基本類型可以直接用,數組 和 引用類型需要轉換。

(1)   jstring  <--->  char *buf ( char buf[] )
            
            
  1.     JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject thiz, jstring string)  
  2.     {  
  3.         // 1. jstring ---> char *
  4.         const char *str;   
  5.         str = (*env)->GetStringUTFChars(env, string,0);  //把 string轉換 const char*  ,str 引用 它
  6.         if (str == NULL) {  
  7.             return NULL; /* OutOfMemoryError already thrown */  
  8.         }  
  9.         printf("%s", str);  //測試是否轉換成功  // str可以拿去用了,用完就釋放
  10.         //  const char * 轉換 char *
  11.        int len = strlen(str);
  12.        char buf[len+1] ;
  13.        strcpy(buf,str);
  14.        printf("%s", buf); //測試是否轉換成功
  15.       (*env)->ReleaseStringUTFChars(env, string , str);  // 釋放引用,後面的兩個參數: jstring ,const char *

  16.         // 從C底層傳出的 char * ,轉換爲 string
  17.         // 2. char * ----> jstring
  18.         return  (*env)->NewStringUTF(buf); 
  19.     }  
以上只是其中的兩個字符串函數,還有許多其他的,具體使用的時候可以依據下面的圖做選擇


(2)  基礎類型數組  <----> int[] ,float[], double[] byte[](char [ ] , char *).........  
       實際寫的函數,如果傳入的是 int[],還要傳入一個數組大小的參數。
 jintArray <----> int[]

JNIEXPORT jintArray JNICALL 
Java_IntArray_test(JNIEnv *env, jobject thiz, jintArray arr) 
{ 
        jint *carr; 
        jint i, sum = 0; 
        // 1. jintArray  ----> int[]
        // 獲取jintArray中的 jint 所有元素
        carr = (*env)->GetIntArrayElements(env, arr, NULL);    // carr 是一個指針,指向一個array的副本
        if (carr == NULL) { 
            return 0; /* exception occurred */ 
        
       
        //使用carr[i]訪問數組元素 
        int len = (*env)->GetArrayLength(env, arr); 
         int des[len];
        for (i=0; i<len; i++) { 
            des[i]= carr[i]; 
        
        // des[] 就是轉換好的 int數組
        // 釋放引用
        (*env)->ReleaseIntArrayElements(env, arr, carr, 0); 

         // 1. int[]  ----> jintArray
         int test[] = {1,2,3,4,5,6,7,8};  
         int len = sizeof(test)/sizeof(int) ;  // 模擬底層C函數帶出來的 int test[]數組,大小也是帶出來的。
         //定義數組對象 
         jintArray array  = (*env)-> NewIntArray(env, len);
        //從start開始複製長度爲len 的數據 從 buffer到 array 中
        (*env)->SetIntArrayRegion(env,array,0,len,test)    
        return array;


(3) 對象數組(Strings和數組都是引用類型。你可以使用上述兩個函數來訪問字符串的數組和數組的數組) 

和基礎類型數組不一樣的是,你不能一次獲取所有的對象元素,或者一次複製多個對象元素。
主要函數:

1.        (*env)->GetIntArrayElements(env,jarray, isCopy) , 返回jarray中的所有數據的指針。

           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.

2.         (*env)->GetIntArrayRegion(env,array,start,len,buffer), 從start開始複製長度爲len 的數據到buffer中 

3.        (*env)->SetIntArrayRegion(env,array,start,len,buffer) , 從start開始複製長度爲len 的數據 從 buffer到 array 中

4.        jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, index);           //得到在某一個索引的值
5.        void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, index, jobject);   //設置某個索引上的值


例子:
JNIEXPORT jobjectArray JNICALL 
Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size)   // 這是一個 靜態方法,返回值是對象數組
{ 
        jobjectArray result; 
        int i; 
        jclass intArrCls = (*env)->FindClass(env, "[I"); // int[] 類
        if (intArrCls == NULL) { 
            return NULL;   /* exception thrown */ 
        } 
        result = (*env)->NewObjectArray(env, size, intArrCls, NULL); //生成數組類型的數組,大小爲size,即二維數組
        if (result == NULL) { 
            return NULL; /* out of memory error thrown */ 
        
        
        // int[size][size]
        for (i = 0; i < size; i++)
       { 
            int tmp[256];   /* make sure it is large enough! */
            int j; 
            jintArray iarr = (*env)->NewIntArray(env, size);  //1. 生成一個int數組,在二維數組中只是一個元素
            if (iarr == NULL) { 
                return NULL; /* out of memory error thrown */ 
            
            // 2. int數組賦值,封裝好一個元素
            for (j = 0; j < size; j++)
            
                tmp[j] = i + j; 
            
            //3. 將int數組作爲一個元素放到 Object數組中  
            (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);   
                // env->SetIntArrayRegion(intArray, 0, size, (const int*)tmp);    如果tmp是malloc出來的,使用這種方式,並記得最後free掉。

            (*env)->SetObjectArrayElement(env, result, i, iarr); 
            (*env)->DeleteLocalRef(env, iarr); //4.  刪除int數組的引用
        
        // free(tmp);
        return result; 
}

如果是string數組,與上面步驟相同,在 最後的地方改爲 :
        jstring jstr; 
        char* sa[] = { "Hello,", "world!", "JNI", "很", "好玩" }; //這裏是模擬字符串數組,應用中要根據實際得來的數據
        int i=0; 
        for(;i<ARRAY_LENGTH;i++) //一般數組的長度也是底層C帶出來,或者傳入的jarray根據jsize len =(*env)->GetArrayLength(env, jarray);
        
            jstr = (*env)->NewStringUTF( env, sa[i] ); // 差別在這裏,數組的元素不同,所以生成元素的函數不同而已。

            (*env)->SetObjectArrayElement(env, texts, i, jstr);//必須放入jstring 
            (*env)->DeleteLocalRef(env, jstr); //4.  刪除int數組的引用
        



===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(JNI 筆記 04)

先說下注冊函數,然後開始講 本地代碼如何訪問java 中的方法,屬性 ,對象等

JNI註冊函數 ,註冊以後的jni函數的命名可以不需要符合類似javah命令生成的函數的規則
請看這篇博客:

java中調用System.loadLibrary("somelib")的時候,系統會自動調用jni的函數JNI_OnLoad 
在程序退出的時候,系統卸載“somelib”,會自動調用jni的函數JNI_OnUnload
所以感覺有點像C++ 中的構造函數 和析構函數,我們一般重寫這兩個方法,來做一些初始化的動作。

jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) 
{ 
                JNIEnv *env = NULL; 
                if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_4))
                
                    return JNI_ERR; 
                
                g_jvm = jvm ;  // 在static變量保存 jvm指針
                sg_env= env;  // 在static 變量保存env指針

//                jclass cls = (*env)->FindClass(env, JNIT_CLASS); 
//                if (cls == NULL)  
//               { 
//                    return JNI_ERR; 
//                } 
//                jint nRes = (*env)->RegisterNatives(env, cls, gMethods, sizeof(gMethods)/sizeof(gMethods[0])); 
//                if (nRes < 0) 
//                { 
//                         return JNI_ERR; 
//                
                // 其他初始化代碼:比如
                 iHxEmvInit(iIMateTestCard,iIMateResetCard,iIMateExchangeApdu,iIMateCloseCard); 
                 sendReceiveCallBack = (*sg_env)->GetMethodID(sg_env, sg_class, "sendReceiveCallBack", "([BI[BI)I"); 
                 return JNI_VERSION_1_4; 
}

OnUnLoad 就不寫了!




=========================================================================
=========================================================================
在native中向LogCat輸出調試信息
另外,在JNI層,可以包含 #include <android/log.h>
 __android_log_print(ANDROID_LOG_INFO, "JNIMsg"" num = %d",num); 打印JNI層的信息。
記得 加入了頭文件後還必須給鏈接器指定__android_log_print函數所在的庫文件liblog.so,
在Android.mk文件中加上一行 LOCAL_LDLIBS := -llog



關於jclass
jclass代表JAVA中的java.lang.Class。
我們看jclass的定義:
1. #ifdef __cplusplus 
2.        /*Reference types, in C++*/ 
3.       class _jobject {}; 
4.       class _jclass : public _jobject {};          /*_jclass繼承_jobject*/ 
5.       typedef _jclass* jclass;
6. #else 
7.      /*Reference types, in C.*/ 
8.       typedef  void*     jobject; 
9.       typedef  jobject  jclass; 
10. #endif 

在C中jclass代表類型void*,在C++中代表類型_jclass*。
當多個native函數都需要使用同一個JAVA類的jclass變量時,不能夠這樣做:
定義一個 jclass 類型全局變量,並只對其賦初值一次,然後在多次對native函數調用中使用這個jclass變量。不能企圖以此方式來節約獲得jclass變量的開銷。
jclass  StudentClazz = (*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");    $表示一個類的內部類
每次JAVA調用native都必須重新獲得jclass,上次調用native所得到的jclass在下次調用native時再使用是無效的。









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章