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方法所属类的实例对象。  

相关文章
相关标签/搜索