在上一篇文章中,简单的介绍了eclipse下生成jni头文件以及java调用C语言的流程,其中,在生成的头文件方法声明中,需要传入一个JNIEnv类型的变量,这里我们就来看一下JNIEnv这个变量类型
JNIEXPORT jstring JNICALL Java_com_will_jni_JNITest_getStringFromC
(JNIEnv *, jclass);
我们右击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中声明的区别,一个是结构体的别名,一个是结构体指针的别名。为什么会有这种区别呢,我们在后面再说。
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对象
New
Array:创建类型为Type的数组对象 Get
Field:获取类型为Type的字段 Set
Field:设置类型为Type的字段的值 GetStatic
Field:获取类型为Type的static的字段 SetStatic
Field:设置类型为Type的static的字段的值 Call
Method:调用返回类型为Type的方法 CallStatic
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();
}
我们将在上面的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方法所属类的实例对象。