企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
接续上篇[JNI开发系列①JNI概念及开发流程](http://www.jianshu.com/p/68bca86a84ce) ### 前情提要 JNI技术 , 是java世界与C/C++世界的通信基础 , java语言可以通过native方法去调用C/C++的函数 , 也可以通过C/C++来调用java的字段与方法 。 在上篇中 , 我们了解了JNI开发的基本流程 , 接下来我们来分析分析C语言代码以及头文件 。 ### .h头文件分析 > 头文件生成命令 : javah com.zeno.jni.HelloJni ```java public static native String getStringFromC() ; ``` 上述代码 通过`javah`命令 , 则会生成如下头文件中的函数: ```c /* 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 __cplusplus extern "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结构体的指针 。 ```c typedef jobject jstring; typedef struct _jobject *jobject; ``` 因为我们`getStringFromC()`方法返回的是一个String类型 , 所以C函数的返回值是`jstring`类型 。 ```c /* * 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的引用数据类型 ```c 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_`结构体中 , 定义很多操作函数 。例如: ```c 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的呢 ?源码如下: ```c++ 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 , 当前环境的指针 ```c++ jstring NewStringUTF(const char *utf) { return functions->NewStringUTF(this,utf); } ``` 示例: ```c++ /* * 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就成了一级指针了 , 为什么会是这样呢 ?我们在源码中找到这样一段代码: ```c /* * JNI Native Method Interface. */ struct JNINativeInterface_; struct JNIEnv_; #ifdef __cplusplus typedef JNIEnv_ JNIEnv; #else typedef const struct JNINativeInterface_ *JNIEnv; #endif ``` 由上可知 , 在C和C++两个环境中 , 使用了两个不同的JNIEnv , 一个是JNIEnv二级指针 , 一个是JNIEnv一级指针 。 ### 模拟C语言中的JNIEnv写法 ```c #include <stdio.h> #include <stdlib.h> // 定义一个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"); } ``` 输出: ```c 正在处理... 调用结果:模拟JNIEnv实现方式 ``` ###结语 .h头文件的分析就到这里 ,关键是了解清楚 , `native`方法在C中生成函数名称的规则 , 以及对JNIEnv有个良好的认识 。