接续上篇
前情提要
JNI技术 , 是java世界与C/C++世界的通信基础 , java语言可以通过native方法去调用C/C++的函数 , 也可以通过C/C++来调用java的字段与方法 。 在上篇中 , 我们了解了JNI开发的基本流程 , 接下来我们来分析分析C语言代码以及头文件 。
.h头文件分析
头文件生成命令 : javah com.zeno.jni.HelloJni
public static native String getStringFromC() ;
上述代码 通过javah
命令 , 则会生成如下头文件中的函数:
/* DO NOT EDIT THIS FILE - it is machine generated */#include "jni.h"/* Header for class com_zeno_jni_HelloJni */#ifndef _Included_com_zeno_jni_HelloJni#define _Included_com_zeno_jni_HelloJni#ifdef __cplusplusextern "C" {#endif/* * Class: com_zeno_jni_HelloJni * Method: getStringFormC * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif
通过上述代码, 我们可以看出getStringFromC()
方法 , 生成的函数是Java_com_zeno_jni_HelloJni_getStringFromC (JNIEnv *, jclass)
函数 。其实 ,我们不用javah
命令 , 也能写出头文件 , 除了#endif,#ifdef __cplusplus
中间是变化的 , 其他的都不变 , javah
通过native
方法生成的函数 , 命名都是有规律 。
函数名称规则:Java_完整类名_方法名 , 包名的.号 , 以`_`表示
其中jstring是返回的java的String类型 , jstring类型是jni里面定义的类型 , 标准C里面是没有的 。那么 , jstring是什么类型呢 ? 使用VS的转到定义功能 , 我们可以看到 , jstring在jni.h的定义 , jstring是jobject的别名 , jobject是一个_jobject结构体的指针 。
typedef jobject jstring;typedef struct _jobject *jobject;
因为我们getStringFromC()
方法返回的是一个String类型 , 所以C函数的返回值是jstring
类型 。
/** Class: com_zeno_jni_HelloJni* Method: getStringFormC* Signature: ()Ljava/lang/String;*/JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC(JNIEnv *Env, jclass jclazz) { return (*Env)->NewStringUTF(Env, "Jni C String");}
在C语言系列的最后一篇 , 我们分析了jni.h的头文件 , 了解了JNIEnv结构体指针 , 大致知道里面都有些什么函数 , NewStringUTF(Env, "Jni C String")
这个函数 , 就是将C语言中的字符指针转换成java的String
类型的字符串。
JNI数据类型对应java的标准数据类型
Java Type | Native Type | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | not applicable |
JNI数据类型对应java的引用数据类型
struct _jobject;typedef struct _jobject *jobject;typedef jobject jclass;typedef jobject jthrowable;typedef jobject jstring;typedef jobject jarray;typedef jarray jbooleanArray;typedef jarray jbyteArray;typedef jarray jcharArray;typedef jarray jshortArray;typedef jarray jintArray;typedef jarray jlongArray;typedef jarray jfloatArray;typedef jarray jdoubleArray;typedef jarray jobjectArray;
在jni源码中 , 我们可以看到如上定义 , 可以发现 , 所有的引用类型都是_jobject
结构体指针类型。
JNIEnv分析
我们知道 ,JNIEnv是JNINativeInterface_
结构体的指针别名 , 在JNINativeInterface_
结构体中 , 定义很多操作函数 。例如:
jstring (JNICALL *NewStringUTF) (JNIEnv *env, const char *utf);jsize (JNICALL *GetStringUTFLength) (JNIEnv *env, jstring str);const char* (JNICALL *GetStringUTFChars)(JNIEnv *env, jstring str, jboolean *isCopy); void (JNICALL *ReleaseStringUTFChars)(JNIEnv *env, jstring str, const char* chars);
由上述函数可以看出,每个函数都需要一个JNIEnv指针,但是为什么需要呢 ?
有两点: 第一:函数需要 , 在函数中仍然需要JNINativeInterface_结构体中的函数做处理 第二:区别对待C和C++ 我们知道 , jni是支持C/C++的,在jni.h头文件中 , 那么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); } jmethodID FromReflectedMethod(jobject method) { return functions->FromReflectedMethod(this,method); }
在C++环境下 ,还是使用的NINativeInterface_
结构体指针调用函数, 使用NewStringUTF
函数时, 则不需要传入Env
这个二级指针 ,因为C++是面向对象的语言 , 传入了this , 当前环境的指针
jstring NewStringUTF(const char *utf) { return functions->NewStringUTF(this,utf); }
示例:
/** Class: com_zeno_jni_HelloJni* Method: getStringFormC* Signature: ()Ljava/lang/String;*/JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromCPP(JNIEnv * env, jclass jclazz) { return env->NewStringUTF("From C++ String");}
在C++环境中 , JNIEnv就成了一级指针了 , 为什么会是这样呢 ?我们在源码中找到这样一段代码:
/* * JNI Native Method Interface. */struct JNINativeInterface_;struct JNIEnv_;#ifdef __cplusplustypedef JNIEnv_ JNIEnv;#elsetypedef const struct JNINativeInterface_ *JNIEnv;#endif
由上可知 , 在C和C++两个环境中 , 使用了两个不同的JNIEnv , 一个是JNIEnv二级指针 , 一个是JNIEnv一级指针 。
模拟C语言中的JNIEnv写法
#include#include // 定义一个JNIEnv , 是JavaNativeInterface结构体指针的别名typedef struct JavaNativeInterface* JNIEnv;// 模拟java本地化接口结构体struct JavaNativeInterface { void(*hadlefunc)(JNIEnv*); char*(*NewStringUTF)(JNIEnv*, char*);};// 模拟 , 在调用NewStringUTF函数的时候 , 需要处理一些事情void handleFunction(JNIEnv* env) { printf("正在处理...\n");}// 最终调用的函数实现char* NewStringUTF(JNIEnv* env,char* utf) { (*env)->hadlefunc(env); return utf;}void main() { //实例化结构体 struct JavaNativeInterface jnf; jnf.hadlefunc = handleFunction; jnf.NewStringUTF = NewStringUTF; // 结构体指针 JNIEnv e = &jnf; // 二级指针 JNIEnv* env = &e; // 通过二级指针掉用函数 char* res = (*env)->NewStringUTF(env, "模拟JNIEnv实现方式\n"); // 打印 printf("调用结果:%s", res); system("pause");}
输出:
正在处理...调用结果:模拟JNIEnv实现方式
###结语 .h头文件的分析就到这里 ,关键是了解清楚 , native
方法在C中生成函数名称的规则 , 以及对JNIEnv有个良好的认识 。
本文由老司机学院【】特约提供 。
做一家受人尊敬的企业,做一位令人尊敬的老师
参考文献: [ ]()