C++基礎二 JNI基礎操作

C的預編譯

c語言執行的流程

預編譯:完成代碼文本的替代工作

編譯:形成目標代碼(.obj)

鏈接:將目標代碼與c函數庫連接合並,形成最終的可執行文件

執行:運行

void main() {
    #include "my.txt";
    getchar();
}

my.txt的內容

printf("%s\n", "I am a great man");

1.JNI定義

1.關鍵詞的解析JNIEXPORT.  JNICALL

2.JNIenv.  env 是Java運行環境,在c++是一級指針c是二級指針:需要傳this改變env的值,c需要傳env需要二層,c++有this指針所以用一層就行了。

3.jclass obj是所屬調用這個jni方法是對象。當是所屬調用方法是在靜態類中時是jclass。這個參數可以獲取當前調用對象或者類中的屬性和方法等。

4.JNIenv  可以通過這個方法創建類變量,通過放射獲取java對象等待。

5.Java傳遞的參數在JNIenv 和Jclass 後面帶着。基本類型有一個轉化標具體看轉化標。結構類型的話有String object 數組(基本類型數組,對象類型數組)等。

 

2.c++調Java,Java調c++

JNIenv->findClass 查找Java層的對象。

JNIenv->GetMethodID 獲取Java對象中的方法。

JNIenv-> GetFieldID (JNIenv env, jclass clazz, const char *name, const char *sig)獲取Java中的屬性.設置是否可以訪問

獲取Java對象中的方法執行.

調用父類的值方法執行等。

 

1.jni處理基本類型

基本數據傳進來也是在JNIenv 和jclass後面傳進來的。

基本類型對照表.

訪問靜態屬性

// 訪問靜態屬性
JNIEXPORT jstring JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    // jclass
    jclass cls = (*env)->GetObjectClass(env, jobj);
    // jfieldID 獲取基本類型的靜態 int count變量
    jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");
    // GetStatic<Type>Field
    jint count = (*env)->GetStaticIntField(env, cls, fid);
    count++;
    // 修改靜態java 變量的值
    (*env)->SetStaticIntField(env, cls, fid, count);
}

C訪問Java的方法

//訪問java方法
JNIEXPORT 返回值 JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    // jclass
    jclass cls = (*env)->GetObjectClass(env, jobj);
    // jmethodID GetMethodID 獲取非靜態方法
    // GetStaticMethodID 獲取靜態的方法
    jmethodID mid = (*env)->GetMethodID(env, cls, "方法名稱", "參數和返回值");
    
    // Call<Type>Method 獲取方法的值
    int random = (*env)->CallIntMethod(env, jobj, mid, 200); 
}

2.jni處理對象

1.構建任何想要的對象. 要求獲取Java類的構造方法

例子:獲取當前對象的構造方法

class cls = (*env)->FindClass(env, "java/util/Date");
// jmethodID
jmethod construct_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
// 實例化一個Date對象
jobject date_obj = (*env)->NewObect(env, cls, construct_mid);
// 從對象中獲取時間的方法
jmethod mid = (*env)->GetMehodID(env, cls, "getTime", "()J");
// 調用Java對象中的方法獲取時間數據
jlong time = (*env)->CallLongMethod(ENV, date_obj, mid);

獲取對象父類的方法

// 獲取傳進來的jobj對象的類
class cls = (*env)->GetObjectClass(env, jobj);
// 獲取對象中的屬性
jfieldID fid = (*env)->GetFieldID(env, cls, "屬性名", “對象簽名”);
// 獲取屬性對象的obj
jobject j_obj = (*env)->GetObjectField(env, jobj, fid);
// 獲取屬性對象的類. 如果是子類傳子類的包名和類名,如果是父類的包名傳父類的包名和類名
jclass j_cls = (*env)->FindClass(env, "對象包名和類名");
// 獲取屬性類中的方法id
jmethodID j_mid = (*env)->GetMethodID(env, j_cls, "方法名稱", "V()");
// 執行對象中的方法
(*env)->CallObjectMethod(env, j_obj, j_mid);
// 執行父類的方法
(*env)->CallNonvirtualObjectMethod(env, j_obj, j_cls, j_mid);

3.jni處理字符串

獲取當前調用的Java對象的類

// 獲取當前調用的Java對象的class
class cls = (*env)->GetObjectClass(env, jobj);

獲取屬性名稱,屬性簽名

 jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String");

通過屬性簽名,獲取jstring的屬性

// 獲取jclass 中的的字符串
jstring str = (*env)->GetObjectField(env, job, fid);

通過jstring獲取char* c字符串,JNI_TRUE表示是否是複製 key和str 指向同一個字符串地址,c_str這個指針是指向同一個還是需要重新劃分一個地址出來

char *c_str = (*env)->GetStringUTFChars(env, jstr, JNI_TRUE);

將C字符串轉化成jstring

jstring new_jstr = (*env)->NewStringUTF(env, text);

修改調用方法所在對象中的屬性的值

(*env)->SetObjectField(env, job, fid, new_jstr);

c中處理字符串的有拼接修改等

strcat拼接

jni中文亂碼處理

// 將Java傳進來的string轉化成char數組 
char *c_str = (*env)->GetStringUTFChars(env, inStr, JNI_FALSE);

char c_str[] = "C字符串"
//C中的中文字符串傳到Java處理。可以使用Java的string方法來處理
//獲取String對象,通過string對象中的方法處理
//1.執行String的構造函數
//2.獲取jmethodID
//3.byte數組
//4.字符編碼

// 獲取String的class
class str_cls = (*env)->FindClass(env, "java/lang/String");
// 獲取字符轉化的方法
jmethodID constructor_mid = (*env)->GetMethodID(env, j_str, "<init>", "([BLjava/lang/String;])V");

//jbyte -> char
//jbyteArray->char[]
jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
//byte 數組賦值
//0->strlen(c_str) 從0開始賦值到結束.c_str這個字符數組複製到bytes這個字符數組中
(*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);

//字符編碼jstring
jstring charsetName = (*env)->NewStringUTF(env, "GB2312");

//調用構造函數,返回編碼之後的字符串
jstring jstr = (*env)->NewObject(env, str_cls, constructor_mid, bytes, charsetName);

 

4.jni處理數組

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


JNIEXPORT 返回值 JNICALL 方法名稱(JNIEnv *env, jobject jobj, jintArray arr){
    // jintArray->jint指針-> C int 數組.最後一個參數爲ture表示複製一個新的數組出來修改,NULL表示同步 false也表示同步
    jint *elems = (*env)->GetIntArrayElementd(env, arr, NULL);
    // 排序
    qsort(elms, len, sizeof(jint), compare);
    // 同步
    // 第四個參數mode的意思
    // 0:Java數組進行更新,並且釋放C/c++數組
    // JNI_ABORT:對Java數組不更新,並且釋放C/c++數組
    // JNI_COMMIT:對Java數組更新,不釋放c/c++數組(函數執行完後) 
    (*env)->ReleaseIntArrayElements(env, arr, elems, 0);
}

返回數組:

//返回數組
JNIEXPORT 返回數據 JNICALL 方法名稱(JNIenv *env, jobject obj, jint len){
    // 創建一個指定大小的數組
    jintArray jint_arr = (*env)->NewIntArray(env, len);
    jint *elems = (*env)->GetIntArrayElements(env, int_arr, NULL);

    int i = 0;
    for (; i<len; i++) {
        elems[i] = i;
    }

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

    
    
    return jint_arr;
}

五 JNI引用變量 作用告訴虛擬機何時回收一個jni變量,提高效率減少定義jni變量

1.局部引用 通過DeleteLocalref手動釋放對象

訪問了一個很大的java對象,在使用之後還進行了複雜的耗時操作

創建了大量的局部引用,佔用了太多的內存,而且這些對象需要釋放的

//局部引用
JINEXPORT jintArray JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    int i = 0;
    for (; i < 5; i++) {
        // 創建Date對象
        jclass cls = (*env)->FindClass(env, "java/util/Date");
        // 獲取結構體的方法id
        jmethodID construct_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
        // 創建對象
        jobject jobj = (*env)->NewObject(ENV, cls,construct_mid);
        //使用這個對象

        //回收
        (*env)->DeleteLocalRef(env, obj);
    }
}

 

2.全局引用

數據共享 提高效率 手動控制對象的釋放

//全局引用 在方法外面
jstring global_str;

//創建的方法 給全局變量賦值
JNIEXPORT void JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    jstring obj = (*env)->NewStringUTF(env, "jni very nice");
    global_str = (*env)->NewGlobalRef(env, obj);
}

//使用的方法 獲取全局變量的值
JNIEXPORT void JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    return global_str;
}

//釋放全局變量佔用的內存
JNIEXPORT void JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    (*env)->DeleteGlobalRef(env, global_str);
}

3.弱全局引用

//節約內存,在內存不足時自動釋放所引用的對象

//可以引用一個不常用的對象,如果爲null,臨時創建

//創建弱全局引用:NewWeakGlobalRef

 

六.JNI的異常處理

JNI自己拋出的異常,在Java層無法被捕獲,只能在c層處理用戶自己通過Java的exception拋出

// 異常處理
JNIEXPORT void JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    jcalss cls = (*env)->GetObjectClass(env, jobj);
    jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;");

    //檢測是否發生了Java異常
    jthrowable exception = (*env)->ExceptionOccurred(env);
    
    if (exception != NULL){
        //讓Java代碼可以繼續執行
        //清空異常信息
        (*env)->ExceptionClear(env);

        // 補救措施
        fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
    }
    
    // 獲取屬性的值
    jstring jstr = (*env)->GetObjectField(env, jobj, fid);

    char *str = (*env)->GetStringUTFChars(env, jstr, NULL);

    // 對比屬性值是否合法
    if (stricmp(str, "changsha") != 0) {
        // 認爲拋出異常, 給java層處理
        jcalss newExcCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
        (*env)->ThrowNew(env, newExcCls, "key's blue is invalid!");
    }
}

七. JNI緩存策略

 

// 緩存機制
JNIEXPORT void JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    jcalss cls = (*env)->GetObjectClass(env, cls, "key", "Ljava/lang/String");
    // 獲取jfieldID只獲取一次
    // 局部靜態變量
     static jfieldID key_id = NULL;
    if (key_id == NULL) {
        key_id = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String");
    }
}

//全局的變量 在動態庫加載完成之後,進行全局變量初始化
jfieldID key_fid;
JNIEXPORT void JNICALL 方法名稱(JNIEnv *env, jobject jobj){
    jcalss cls = (*env)->GetObjectClass(env, cls, "key", "Ljava/lang/String");
    // 獲取jfieldID只獲取一次
    if (key_fid == NULL) {
        key_fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String");
    }
}

 

 

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