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 TypeNative TypeDescription
booleanjbooleanunsigned 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsigned 16 bits
intjintsigned 32 bits
longjlongsigned 64 bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidnot 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 <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");
}

输出:

正在处理...
调用结果:模拟JNIEnv实现方式

结语

.h头文件的分析就到这里 ,关键是了解清楚 , native方法在C中生成函数名称的规则 , 以及对JNIEnv有个良好的认识 。

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

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

参考文献:
Java Native Interface 6.0 Specification

    原文作者:逝我
    原文地址: https://www.jianshu.com/p/cba836f6a08c
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞