Native连接Java世界的JavaVM和JNIEnv

JavaVM 和 JNIEnv

JNI 定义了两个关键数据结构,即JavaVMJNIEnv。两者本质上都是指向函数表的二级指针。在 C++ 版本中,它们是一些类,这些类具有指向函数表的指针,并具有每个通过该函数表间接调用的 JNI 函数的成员函数。掌握 JavaVMJNIEnv 这两个结构体就是关键,这两个结构体就是通往 Java 世界的大门,重要性不言而喻。

JavaVM

JavaVM 这个结构体指针在简单的 JNI 开发中很少使用到,它是虚拟机的代表,从 JDK 1.2 开始,一个进程只允许创建一个虚拟机

当 Java 层访问 Nativce 层的时候会自动在 JNI 层创建一个 JavaVM 指针,而我们在 JNI 层通常所使用的都是从 JavaVM 中获取的 JNIEnv 指针。那么现在我们来看下 JavaVM 这个结构体

/*
 * C++ version.
 */
struct _JavaVM {
    const struct JNIInvokeInterface* functions;

#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};

JavaVM是虚拟机在JNI中的表示,一个JVM中只有一个JavaVM对象,这个对象是线程共享的。

通过JNIEnv我们可以获取一个Java虚拟机对象,其函数如下:

/**
 * 获取Java虚拟机对象
 * @param env JNIEnv对象
 * @param vm 用来存放获得的虚拟机的指针的指针
 * @return 成功返回0,失败返回其他
 */
jint GetJavaVM(JNIEnv *env, JavaVM **vm);

在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定义了该函数),第一个参数会传入JavaVM指针。可以在该函数中保存JavaVM指针来供全局使用。

JavaVM *javaVM = NULL;

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    javaVM = vm;
    ...
}

创建JavaVM

从 Java 层到 Native 层的开发的时候,我们并不需要手动创建 JavaVM 对象,因此虚拟机自动帮我们完成了这些工作。然而,如果从 Native 层到 Java 层开发的时候,我们就需要手动创建 JavaVM 对象,创建的函数原型如下

#inlcude <jni.h>

jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);

JNI_CreateJavaVM 函数不属于任何结构体,方法声明在 jni.h 头文件中。

参数解释

  1. p_vm: 是一个指向 JavaVM * 的指针,函数成功返回时会给 JavaVM *指针赋值。
  2. p_env: 是一个指向 JNIEnv * 的指针,函数成功返回时会给 JNIEnv * 指针赋值。
  3. vm_args: 是一个指向 JavaVMInitArgs 的指针,是初始化虚拟机的参数。

如果函数执行成功,返回 JNI_OK(值为0),如果失败返回负值。

基本上可以这样理解 JNI_CreateJavaVM() 函数,它就是为了给 JavaVM *指针 和 JNIEnv *指针赋值。我们得到这两个指针便可以操纵"万物",这里的"万物"指的是 Java 世界的"万物"。

JNIEnv

_JavaVM 结构体中有一个函数 getEnv(),与之相对应的函数原型如下

jint GetEnv(JavaVM *vm, void **env, jint version);

参数说明

  1. vm: 虚拟机对象。
  2. env: 一个指向 JNIEnv 结构的指针的指针。
  3. version: JNI版本,根据jdk的版本,目前有四种值,分别为 JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6

这个函数执行结果有几种情况:

  1. 如果当前线程没有附着到虚拟机中,也就是没有调用 JavaVM 的 AttachCurrentThread() 函数,那么就会设置 *env 的值为 NULL,并且返回 JNI_EDETACHED (值为-2)。
  2. 如果参数version锁指定的版本不支持,那么就会设置 *env 的值为 NULL,并且返回 JNI_EVERSION(值为-3)。
  3. 除去上面的两种异常情况,就会给 *env 设置正确的值,并且返回 JNI_OK(值为0)。

JNIEnv类型是一个指向全部JNI方法的指针,JNIEnv 提供了大部分 JNI 函数。JNIEnv只在创建它的线程有效,<font color='red'>不能跨线程传递</font>,不能再线程之间共享 JNIEnv

所有的本地接口函数都会以 JNIEnv 作为第一个参数。不管是静态注册的本地C/C++函数接口,还是动态注册的本地函数接口,函数的第一个参数都是JNIEnv

如果一段代码无法通过其他方法获取自己的 JNIEnv,可以通过全局有效的 JavaVM,然后使用 GetEnv 来获取当前线程的 JNIEnv(如果该线程包含一个 JNIEnv)。

JNIEnv* env = NULL;
if (javaVM->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;
}

GetEnv函数定义如下:

/**
 * 获取当前线程JNIEnv
 * @param env 用来存放获取JNIEnv对象的指针的指针
 * @param version JNI版本
 * @return 成功返回0,失败返回其他
 */
jint GetEnv(void** env, jint version)

对于本地库中创建的线程,需要使用AttachCurrentThread来附加到 JavaVM来获取一个可用的JNIEnv。线程退出或不再需要使用JNIEnv时,必须通过调用DetachCurrentThread来解除连接。

JNIEnv使用限制

函数执行结果的第一种情况来可以说明几个问题

  1. JNIEnv * env 是与线程相关,因此多个线程之间不能共享同一个 env
  2. 如果在Native层新建一个线程,要获取 JNIEnv * env,那么必须做到如下两点
    • 线程必须调用 JavaVMAttachCurrentThread() 函数。
    • 必须全局保存 JavaVM * mJavaVm,那么就可以在线程中通过调用 JavaVMgetEnv() 函数来获取 JNIEnv * env的值。

我们还记得 JNI_CreateJavaVM() 函数也设置 *env 的值吗?那么它肯定也会执行 AttachCurrentThread() 函数把当前线程附着到虚拟机中。这也就是解释了为何在没有明显调用 AttachCurrentThread() 的情况下,可以执行 JavaVMDetachCurrentThread() 函数。

C语言调用C++调用

JavaVMJNIEnv 在 C 语言环境下和 C++ 环境下调用是有区别的,以NewStringUTF函数为例:

C语言调用格式为:

(*env)->NewStringUTF(env, “Hellow World!”);

C++调用格式为:

env->NewStringUTF(“Hellow World!”);

建议使用 C++ 格式,这也是大部分代码使用的形式。但C++ 格式其实只是封装了 C 格式,使得调用更加简介方便。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容