JNI基礎之JNIEnv,jclass和jobject

在上一篇文章中,簡單的介紹了eclipse下生成jni頭文件以及java調用C語言的流程,其中,在生成的頭文件方法聲明中,須要傳入一個JNIEnv類型的變量,這裏咱們就來看一下JNIEnv這個變量類型java

JNIEXPORT jstring JNICALL Java_com_will_jni_JNITest_getStringFromC
  (JNIEnv *, jclass);
JNIEnv 類型
咱們右擊JNIEnv,點擊轉到聲明,跳轉到jni.h頭文件中JNIEnv的聲明處。數組

#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
這裏經過預編譯指令分別針對C++和C語言環境JNIEnv做了不一樣的聲明,上面的代碼中能夠看到,在C++環境中, JNIEnv是 JNIEnv_結構體的別名,而在C語言環境中,它是JNINativeInterface_結構體的指針別名,注意這裏C++和C中聲明的區別,一個是結構體的別名,一個是結構體指針的別名。爲何會有這種區別呢,咱們在後面再說。eclipse

c語言中的JNIEnv函數

接着跳到C語言中JNINativeInterface_結構體的定義中this

struct JNINativeInterface_ {
    void *reserved0;
    void *reserved1;
    void *reserved2;
 
    void *reserved3;
    jint (JNICALL *GetVersion)(JNIEnv *env);
 
    jclass (JNICALL *DefineClass)
      (JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
       jsize len);
    jclass (JNICALL *FindClass)
      (JNIEnv *env, const char *name);
 
    ···
}
能夠看到,在這個結構體中,它定義了一大堆的函數,如上面羅列的GerVersion,DefinedClass,FindClass等,仔細看完這個結構體,能夠發現這個結構體中基本聲明瞭jni.h中的全部函數。JNIEnv類型實際上表明瞭Java環境,經過這個JNIEnv* 指針,就能夠對Java端的代碼進行操做。例如,建立Java類中的對象,調用Java對象的方法,獲取Java對象中的屬性等等。JNIEnv的指針會被JNI傳入到本地方法的實現函數中來對Java端的代碼進行操做。 指針

NewObject:建立Java類中的對象對象

NewString:建立Java類中的String對象ip

New<Type>Array:建立類型爲Type的數組對象get

Get<Type>Field:獲取類型爲Type的字段string

Set<Type>Field:設置類型爲Type的字段的值

GetStatic<Type>Field:獲取類型爲Type的static的字段

SetStatic<Type>Field:設置類型爲Type的static的字段的值

Call<Type>Method:調用返回類型爲Type的方法

CallStatic<Type>Method:調用返回值類型爲Type的static方法

   2. C++中 JNIEnv 的定義

接着看C++中JNIEnv_的定義:

struct JNIEnv_ {
    const struct JNINativeInterface_ *functions;
#ifdef __cplusplus
 
    jint GetVersion() {
        return functions->GetVersion(this);
    }
    jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
                       jsize len) {
        return functions->DefineClass(this, name, loader, buf, len);
    }
    jclass FindClass(const char *name) {
        return functions->FindClass(this, name);
    }
    ···
}
能夠看到,C++中JNIEnv_定義了一個指針變量functions,它實際是C語言中JNINativeInterface_的指針別名,在看C++中其餘函數的實現,都是經過JNINativeInterface_的實現,到這裏也就明瞭C++和C語言中JNIEnv的區別了。

    3. 一級指針VS二級指針?

回到最開始的那段代碼,能夠看到jni生成方法的聲明中傳入的是JNIEnv*的指針變量,傳入的指針。

JNIEXPORT jstring JNICALL Java_com_will_jni_JNITest_getStringFromC
  (JNIEnv * env, jclass jcls){
    return (*env)->NewStringUTF(env, "Hellow world jni!");
}
如前面所說, JNIEnv在C++中是結構體JNIEnv_的別名,而在C中則是JNINativeInterface_指針的別名。那麼在C中,傳入JINEnv指針變量env,則是JNINativeInterface_的二級指針,而C++中則是JNIEnv_的一級指針。

那麼爲何一個用一級指針,一個用二級指針呢?JNIEnv類型實際上表明瞭Java環境,函數執行的過程當中須要JNIEnv做爲上下文的環境變量,而C++能夠獲取到當前指針this。

咱們經過模擬JNIEnv的NewStringUTF方法來實現這個指針的傳遞過程。

定義一個結構體

//結構體指針別名
typedef struct JNINativeInterface_ *JNIEnv;
//結構體定義
struct JNINativeInterface_{
    char* (*NewStringUTF)(JNIEnv*, char*);
};
 
//函數實現
char* NewStringUTF(JNIEnv* env, char* str){
    return str;
}
void main(){
    //首先要實例化結構體:
    struct JNINativeInterface_ struct_env;
    struct_env.NewStringUTF = NewStringUTF;
    //結構體指針
    JNIEnv e = &struct_env;
    //結構體二級指針
    JNIEnv *env = &e;
 
    char* result = (*env)->NewStringUTF(env, "Hello world!");
    printf("%s\n", result);
    getchar();
}
jclass和jobject類型
咱們將在上面的java類中新增一個native非靜態方法

public class JNITest {
    public native static String getStringFromC();
    public native String getStringC();
    
    public static void main(String[] args) {
        String text = getStringFromC();
        System.out.println(text);
        JNITest jniTest = new JNITest();
        String text2 = jniTest.getStringC();
        System.out.println(text2);
    }
 
    static {
        System.loadLibrary("c_jni");
    }
}
經過javah命令生成頭文件,發現新增了一個native函數:

/*
 * Class:     com_will_jni_JNITest
 * Method:    getStringFromC
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_will_jni_JNITest_getStringFromC
  (JNIEnv *, jclass);
 
/*
 * Class:     com_will_jni_JNITest
 * Method:    getStringC
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_will_jni_JNITest_getStringC
  (JNIEnv *, jobject);
能夠看到第一個函數對應的是java中的靜態方法,而第二個函數則對應非靜態方法。對比這兩個函數的參數,發現都傳了兩個參數,第一個是JNIEnv*,而第二個參數在靜態方法中傳入的是jclass類型,而非靜態方法傳入的是jobject類型。實際上,爲了可以在Native層訪問Java中的類和對象,jobject和jclass 分別指代了其所指代的類和對象,進而訪問成員方法和成員變量等。jclass和jobject的定義可在jni.h中找到:

class _jobject {}; class _jclass : public _jobject {}; ··· typedef _jobject *jobject; typedef _jclass *jclass; ···  當java中定義的native方法爲靜態方法時,則第二個參數爲jclass,JClass表明native方法所屬類的class自己。 當java中定義的native方法爲非靜態方法時,則第二個參數爲jclass,JClass表明native方法所屬類的實例對象。  

相關文章
相關標籤/搜索