JNI基础之JNIEnv,jclass和jobject

在上一篇文章中,简单的介绍了eclipse下生成jni头文件以及java调用C语言的流程,其中,在生成的头文件方法声明中,需要传入一个JNIEnv类型的变量,这里我们就来看一下JNIEnv这个变量类型

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中声明的区别,一个是结构体的别名,一个是结构体指针的别名。为什么会有这种区别呢,我们在后面再说。

  1. c语言中的JNIEnv

接着跳到C语言中JNINativeInterface_结构体的定义中

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对象

NewArray:创建类型为Type的数组对象

GetField:获取类型为Type的字段

SetField:设置类型为Type的字段的值

GetStaticField:获取类型为Type的static的字段

SetStaticField:设置类型为Type的static的字段的值

CallMethod:调用返回类型为Type的方法

CallStaticMethod:调用返回值类型为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中的类和对象,jobjectjclass 分别指代了其所指代的类和对象,进而访问成员方法和成员变量等。jclass和jobject的定义可在jni.h中找到:

class _jobject {};
class _jclass : public _jobject {};
···
typedef _jobject *jobject;
typedef _jclass *jclass;
···
  1.  当java中定义的native方法为静态方法时,则第二个参数为jclass,JClass代表native方法所属类的class本身。
  2. 当java中定义的native方法为非静态方法时,则第二个参数为jclass,JClass代表native方法所属类的实例对象。

 

你可能感兴趣的:(jni)