JNI开发系列②.h头文件分析

接续上篇JNI开发系列①JNI概念及开发流程

前情提要

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 __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结构体的指针 。

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 __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef 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有个良好的认识 。

本文由老司机学院【动脑学院】特约提供 。

做一家受人尊敬的企业,做一位令人尊敬的老师

参考文献:
Java Native Interface 6.0 Specification

你可能感兴趣的:(JNI开发系列②.h头文件分析)