在上一篇文章中,簡單的介紹了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方法所屬類的實例對象。