JNI本地方法加载,注册,校验过程

JNI本地方法加载,注册,校验过程

函数调用方解释

JNI_OnLoad 函数是由 Java 虚拟机(JVM)在加载本地库时自动调用的。具体来说:

  • 当应用程序通过 System.loadLibraryRuntime.loadLibrary 加载一个包含本地代码的共享库(如 .so 文件)时,JVM 会查找并调用该库中的 JNI_OnLoad 函数。
  • 这个函数用于初始化 JNI 环境,并可以执行一些必要的设置工作,例如注册本地方法。

因此,JNI_OnLoad 不是由用户直接调用的,而是由 JVM 在特定时机自动触发。

控制流图(CFG)

由于 JNI_OnLoad 的调用是由 JVM 自动完成的,其控制流较为简单,主要体现在库加载的过程中:

加载共享库
找到后自动调用
应用程序调用 loadLibrary
JVM 查找 JNI_OnLoad
执行 JNI_OnLoad 初始化

这个流程图展示了从应用程序加载库到 JNI_OnLoad 被调用的过程。

1.JNI_OnLoad函数是jni.h里面定义的函数

jni.h:这个文件是java官方定义的,相等于一个协议,也相当于接口定义文件,java虚拟机通过这个文件声明了虚拟机提供的接口,这个文件是不能被修改的,只有官方人员能够修改,这个文件已经存在java啊安装目录里面,已经是一个文件,不是在编译的时候生成的,所以这个文件不是动态生成的,所以jni就不可能和编译的内容相关,所以jni里面不包含和具体相关的任何代码,只是工具接口的定义

jni.h:就是一个工具箱

/* This function will be called when the library firstly loaded */
// java虚拟机加载本地库,需要对本地库里面的函数签名进行验证,函数签名,包含函数名称,函数参数,函数返回值
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    // JavaVM:和context类一样,就是提供整个虚拟机的上下文,这个类的作用就是尽量全面的提供虚拟机信息
    UnionJNIEnvToVoid uenv;
    JNIEnv *env = NULL;
    LOGI("JNI_OnLoad!");
    // 获得uenv.venv,获得动态库里面的全部签名
    if (vm->GetEnv((void **) &uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed");
        return -1;
    }
    // 取得前面
    env = uenv.env;;
    // 对签名进行检查
    if (registerNativeMethods(env) != JNI_TRUE) {
        LOGE("ERROR: registerNativeMethods failed");
        return -1;
    }
    return JNI_VERSION_1_4;
}
/*
 * Register native methods for all classes we know about.
 *
 * returns JNI_TRUE on success.
 */
static int registerNativeMethods(JNIEnv* env)
{
    //注册本文件中的方法
    // g_clsRegisteNativePath = "com/astrob/navi/astrobnavilib/JavaToJni";
    // methods:数组,每个元素里面存储了一个映射,java中声明的额方法和底层的代码的映射
    // 里面存储c++的签名,分成参数返回值和函数名称部分,两者共同构成签名
    int result = registerMQNaviMethods(env, g_clsRegisteNativePath,
                                       methods, sizeof(methods) / sizeof(methods[0]));
    if(!result){
        return result;
    }

    //注册Search.cpp中的方法
    result = registerSearchNativeMethods(env);
    if(!result){
        return result;
    }

    //注册UserData.cpp中的方法
    result = registerUserDataNativeMethods(env);
    if(!result){
        return result;
    }

    //注册Utility.cpp中的方法
    result = registerUtilityNativeMethods(env);
    if(!result){
        return result;
    }

    //注册Config.cpp中的方法
    result = registerConfigNativeMethods(env);
    if(!result){
        return result;
    }

    //注册MapView.cpp中的方法
    result = registerMapViewNativeMethods(env);
    if(!result){
        return result;
    }

    return result;
}
// methods是一个数组,里面存储了java类里面声明的native方法和c++里面函数的映射
// (II)V:就是函数签名的一部分,括号里面的是参数的而类型,括号后面的的返回值类型。最后面记录的就是函数的名称,函数名称和函数的参数类型,返回值类型就构成了函数的签名,根据这个确定函数
static JNINativeMethod methods[] = {
        {"createAssetManager",            "(Landroid/content/res/AssetManager;)V",                    (void *) createAssetManager},
    
        {"destroyAssetManager",           "()V",                                                      (void *) destroyAssetManager},
    
        {"SetSystemDir",                  "(Ljava/lang/String;)V",                                    (void *) setSystemDir},
    
        {"SetUsbDir",                     "(Ljava/lang/String;)V",                                    (void *) setUsbDir},
    
        {"OnInit",                        "(II)Z",                                                    (void *) onInit},
    
        {"CreateGL",                      "(ZZ)I",                                                    (void *) CreateGL},
    
        {"CreateSecondaryGL",             "(IIIZIIII)I",                                              (void *) CreateSecondaryGL},
    
        {"SetWindowSizeGL",               "(II)V",                                                    (void *) SetWindowSizeGL},
    
        {"AddSecondaryWndGL",             "(IIIIFZIIIII)I",                                           (void *) AddSecondaryWndGL},
    
        {"SetSecondaryWndSize",           "(IIIIIZ)V",                                                (void *) SetSecondaryWndSize},
    
        {"DeleteSecondaryWndGL",          "(I)V",                                                     (void *) DeleteSecondaryWndGL},
    
        {"EnableWnd",                     "(I)V",                                                     (void *) EnableWnd},
    
        {"DisableWnd",                    "(I)V",                                                     (void *) DisableWnd},
    
    .......
}

怎么理解jclass,将jclass存储在内存里面的一个加密的文件,只有jni的函数才能正确地解析这个文件,所以,每个函数前面都要家伙是那个JNIEnv指针,这个指针里面存储了解密工具

/*
 * Register several native methods for one class.
 */
static int registerMQNaviMethods(JNIEnv *env, const char *className,
                                 JNINativeMethod *gMethods, int numMethods) {
    // c++中定义一个存储java类的对象
    // 为什么c++中可以存储java对象,因为c++对象占据一块连续的数据块,java对象也占据一个连续的数据块,c++可以完全不理解java对象的内部接口,直接当作一个数据块存储在里面
    // 对数据块里面的数据是jni提供的函数进行解析
    
    jclass clazz; // 认为是加密数据块,需要JNIEnv里面提供的工具进行解密

    clazz = env->FindClass(className); // 解密工具FindClass
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'", className);
        return JNI_FALSE;
    }
    // java官方提供的工具,用于对方法进行注册
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s', %d", className, numMethods);
        return JNI_FALSE;
    }

	if (registerMapDownloadNativeMethods(env, clazz) < 0) {
		return JNI_FALSE;
	}
	HTTPRequest::RegisterNativeHTTPRequest(*env);
	return JNI_TRUE;
}
// jni里面的函数
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
        jint nMethods)
    {
    // RegisterNatives:java核心代码,没有源代码,使用指针调用,隐藏实现
    	return functions->RegisterNatives(this, clazz, methods, nMethods); 
	}

关于jni的解释

jni.h 是 Java Native Interface (JNI) 的头文件,提供了在 C/C++ 中与 Java 虚拟机 (JVM) 交互的接口定义。它包含了==一组宏类型定义函数声明==,用于实现 Java 和本地代码之间的通信。

以下是 jni.h 的主要内容和用途:


1. 基础数据类型定义

jni.h 定义了与 ==Java 基本数据类型==对应的 C 类型,例如:

  • jint:对应于 Java 的 int
  • jlong:对应于 Java 的 long
  • jfloat:对应于 Java 的 float
  • jdouble:对应于 Java 的 double
  • jboolean:对应于 Java 的 boolean(值为 JNI_TRUEJNI_FALSE

此外,还定义了==指向 Java 对象引用类型==,例如:

  • jobject:任意 Java 对象
  • jstring:Java 字符串
  • jclass:Java 类对象
  • jarray:Java 数组,及其子类型如 jintArrayjbyteArray

2. JNI 函数表与 JNIEnv

JNIEnv 是一个==指针结构,提供了一系列==与 JVM 交互的函数,常见的函数有:

  • 创建 Java 对象NewObjectNewStringUTF
  • 调用 Java 方法CallObjectMethodCallStaticIntMethod
  • 访问字段GetFieldIDSetObjectField
  • 数组操作GetArrayLengthGetIntArrayElements
  • 异常处理ThrowExceptionOccurred

例子:调用 Java 方法

// 根据包名访问类
jclass cls = env->FindClass("com/example/MyClass");
// 获得包里面类的签名
jmethodID mid = env->GetMethodID(cls, "myMethod", "(I)V");
// 从obj对象里面调用函数
env->CallVoidMethod(obj, mid, 42);

3. 宏定义

jni.h 定义了一些与 JNI 操作相关的宏:

  • JNIEXPORT
    

    JNICALL
    

    :用于==标记调用== JNI 方法

    JNIEXPORT void JNICALL Java_com_example_MyClass_myNativeMethod(JNIEnv *env, jobject obj) {
        // 本地方法实现
    }
    
  • NULL:表示空指针

  • 错误码:

    • JNI_OK:操作成功
    • JNI_ERR:一般错误
    • JNI_EDETACHED:线程未附加到 JVM
    • JNI_ENOMEM:内存不足

4. JVM 与线程管理

jni.h 提供了 JVM 和线程的管理接口,例如:

  • JavaVM
    

    :表示 JVM 实例的指针

    JavaVM *vm;
    vm->AttachCurrentThread((void**)&env, NULL);
    vm->DetachCurrentThread();
    
  • 本地线程与 JVM 的绑定:AttachCurrentThreadDetachCurrentThread


5. 全局与局部引用

  • NewGlobalRefDeleteGlobalRef:管理全局引用
  • DeleteLocalRef:释放局部引用

6. 内存管理

JNI 提供了内存操作的接口:

  • NewByteArray:分配 Java 字节数组
  • ReleaseStringUTFChars:释放通过 GetStringUTFChars 获取的字符串内存

jni.h 是 JNI 开发的核心文件,其内容结构清晰,涵盖了 C/C++ 与 Java 之间互操作的==所有基础工具==。在使用时,需要确保线程与 JVM 的正确绑定,避免内存泄漏或引用管理问题。

jni.h 文件是由 Java 开发团队 创建的,最初由 Sun Microsystems 在 Java 1.1 版本(1997 年)中引入,并随着 Java 平台的演化逐步完善。Sun Microsystems 是 Java 编程语言和 Java 虚拟机 (JVM) 的发明者,后来在 2010 年被 Oracle 收购,因此当前 jni.h 的维护和更新由 Oracle 负责。


背景与设计动机

jni.h 的设计目的是为了解决 Java 应用程序需要与底层系统或本地代码(如 C/C++、汇编语言)交互的问题。例如:

  1. Java 无法直接访问操作系统特定的功能或硬件资源。
  2. 性能瓶颈:某些高性能任务在纯 Java 实现中效率较低,需要通过本地代码优化。
  3. 兼容性:整合现有的 C/C++ 库或模块,而不需要将它们完全重写为 Java。

创建过程

  1. 设计者
    • 最初由 Java 团队的核心成员,如 James Gosling(Java 的创始人之一),以及与 JVM 和 Java API 相关的工程师共同设计。
    • JNI 规范及其实现是 JVM 规范的一部分,因此在设计时由 JVM 和 Java API 的开发者密切协作完成。
  2. 开发语言
    • jni.h 使用 C 语言编写,因为 JVM 本身是用 C 和 C++ 实现的,C 语言的 ABI(应用二进制接口)也最适合作为跨语言桥梁。
  3. 发布时间
    • jni.h 首次随 Java 1.1 发布,成为 Java 平台的重要组成部分。
  4. 后续维护
    • 由 Sun Microsystems 的 Java 开发团队维护,收购后由 Oracle 公司接手。
    • 随着 Java 平台的升级,jni.h 也逐步完善。例如,新增了对 64 位数据类型的支持,以及与现代 Java 语言特性的兼容。

开放性与标准化

Java Native Interface (JNI) 是 Java 平台的开放标准,相关的设计和规范在 Java SE 的文档中详细记录。开发者可以通过 JNI 官方规范 查看其详细说明。

总结来说,jni.h 是由 Sun Microsystems 的 Java 开发团队设计和创建的,目标是提供一个标准化的接口,帮助 Java 应用程序高效地与本地代码交互。


JavaVM是什么

JavaVM 是 Java Native Interface (JNI) 中的一个关键==结构体表示 Java 虚拟机 (Java Virtual Machine, JVM) 的实例。它允许本地代码通过 JNI 与 JVM 交互,管理线程加载类执行方法以及控制 JVM 的运行==。

在 JNI 中,JavaVM 是一个==抽象表示,实际由 JVM 实现提供具体的功能==。


JavaVM 的主要特点

  1. 代表 JVM 的实例
    一个应用程序可以包含一个或多个 JVM 实例(尽管在标准的 Java 应用中通常只有一个 JVM 实例)。
  2. 线程安全
    JavaVM 的设计支持多线程,并为本地线程提供了附加到 JVM 的能力。
  3. 与 JNIEnv 相关
    JavaVMJNIEnv 密切相关:
    • JavaVM 是 JVM 的全局表示,可以跨线程使用。
    • JNIEnv 是线程本地的,每个线程都有自己的 JNIEnv 指针,无法跨线程共享。

JavaVM 的主要用途

  1. 管理线程 本地线程需要附加到 JVM 才能与其交互。JavaVM 提供了以下函数:

    • AttachCurrentThread: 将本地线程附加到 JVM,获得 JNIEnv
    • DetachCurrentThread: 将本地线程从 JVM 分离。
    • GetEnv: 获取当前线程的 JNIEnv

    示例:

    JNIEnv* env;
    jint result = jvm->AttachCurrentThread((void**)&env, NULL);
    if (result == JNI_OK) {
        // 使用 env 与 JVM 交互
        jvm->DetachCurrentThread();
    }
    
  2. 加载类和方法
    通过 JavaVM 可以间接加载 Java 类或调用 Java 方法(通常需要先获取 JNIEnv)。

  3. 控制 JVM 的生命周期
    JavaVM 提供启动和关闭 JVM 的接口,例如:

    • JNI_CreateJavaVM: 创建 JVM 实例。
    • DestroyJavaVM: 销毁 JVM 实例,释放资源。

JavaVM 的函数接口

JavaVM 的函数主要通过指针调用。以下是部分常用函数:

  • 创建 JVM

    JavaVM* jvm;
    JNIEnv* env;
    JavaVMInitArgs vm_args; // JVM 初始化参数
    vm_args.version = JNI_VERSION_1_8; // 选择 JNI 版本
    jint res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
    if (res == JNI_OK) {
        // JVM 创建成功
    }
    
  • 获取当前线程的 JNIEnv

    JNIEnv* env;
    jint res = jvm->GetEnv((void**)&env, JNI_VERSION_1_8);
    if (res == JNI_OK) {
        // 已成功获取
    }
    
  • 销毁 JVM

    jvm->DestroyJavaVM();
    

JavaVMJNIEnv 的关系

  • JavaVM 是 JVM 的全局实例,线程间共享。
  • JNIEnv 是线程本地的接口,用于本地代码与 JVM 的交互。
  • 每个 JavaVM 维护了所有 JNIEnv 的上下文关系。

图示关系:

+---------------+
|    JavaVM     |  (全局 JVM 实例)
+---------------+
         |
         +---------------------+
         |                     |
+----------------+    +----------------+
|   JNIEnv (T1)  |    |   JNIEnv (T2)  |  (线程本地环境)
+----------------+    +----------------+

使用场景

  1. 嵌入式 JVM
    在嵌入式系统中,C/C++ 程序通过 JavaVM 加载 JVM 并运行 Java 代码。
  2. 多线程 JNI 编程
    使用 JavaVM 管理多个线程间与 JVM 的交互。
  3. 控制 JVM 生命周期
    在需要动态加载或销毁 JVM 的场景下,例如运行独立的 Java 子程序。

总结来说,JavaVMJNI 提供的全局接口用于表示和控制 JVM 的实例,主要负责线程管理、与 JNIEnv 的关联以及 JVM 生命周期的管理。


JNIEnv是什么

JNIEnv 是 Java Native Interface (JNI) 的==核心数据结构之一,它是一个指向函数表的指针,提供了本地代码与 Java 虚拟机 (JVM) 交互的主要接口==。JNIEnv线程本地 的,不能跨线程使用,每个线程都需要单独获取它。


JNIEnv 的主要特点

  1. 线程本地性
    每个附加到 JVM 的线程都会分配一个独立的 JNIEnv 实例,该实例只在该线程的上下文中有效。
  2. 操作 Java 对象和 JVM 功能的接口
    JNIEnv 提供了一系列函数,用于在本地代码中操作 Java 对象、调用方法、访问字段、处理异常等。
  3. 自动回收
    JNIEnv 的生命周期由 JVM 管理,程序员无需显式销毁它。
  4. 指向函数表的指针
    JNIEnv 是一个结构体指针,内部==存储大量与 JVM 交互的函数==,称为 JNI 函数表

JNIEnv 的主要用途

  1. 加载 Java 类和对象 使用 FindClass 查找 Java 类,通过 NewObject 创建 Java 对象

    jclass cls = env->FindClass("java/lang/String"); // 查找类定义
    jmethodID ctor = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V"); // 根据函数名和曾,参数返回值,确定函数签名
    // 第一行包名确定一个包,第二行函数签名确定一个函数,这两个就唯一的确定一个函数
    jobject obj = env->NewObject(cls, ctor, env->NewStringUTF("Hello JNI"));
    
  2. 调用 Java 方法 使用 CallXXXMethodCallStaticXXXMethod 调用 Java 实例方法或静态方法。

    jmethodID mid = env->GetMethodID(cls, "length", "()I");
    jint length = env->CallIntMethod(obj, mid);
    
  3. 访问 Java 字段 使用 GetFieldIDGetXXXField 获取或设置 Java 对象的字段。

    jfieldID fid = env->GetFieldID(cls, "value", "Ljava/lang/String;");
    jstring value = (jstring)env->GetObjectField(obj, fid);
    
  4. 数组操作 操作 Java 数组(如 jintArray, jbyteArray)。

    jintArray array = env->NewIntArray(5);
    jint elements[5] = {1, 2, 3, 4, 5};
    env->SetIntArrayRegion(array, 0, 5, elements);
    
  5. 异常处理 检测和处理 Java 代码抛出的异常。

    if (env->ExceptionOccurred()) {
        env->ExceptionDescribe(); // 打印异常堆栈信息
        env->ExceptionClear();    // 清除异常状态
    }
    
  6. 全局和局部引用管理

    • 局部引用:默认创建的对象引用在函数结束后自动释放。
    • 全局引用:使用 NewGlobalRef 创建,需手动释放。
    jobject globalRef = env->NewGlobalRef(obj);
    env->DeleteGlobalRef(globalRef);
    

JNIEnv 的结构与工作原理

JNIEnv 是一个指针,指向一个包含函数表的结构体。这个函数表是 JVM 提供的,包含了所有可用的 JNI 函数。通过 JNIEnv 可以访问 JVM 提供的接口。

伪代码结构:

struct JNIEnv {
    const struct JNINativeInterface* functions;
};

访问某个 JNI 函数时,通过函数表间接调用。例如:

env->FindClass("java/lang/String");

等价于:

env->functions->FindClass(env, "java/lang/String");

如何获取 JNIEnv

  1. 本地线程附加到 JVM 本地线程需要附加到 JVM 才能获取 JNIEnv

    JNIEnv* env;
    jvm->AttachCurrentThread((void**)&env, NULL);
    
  2. Java 调用本地方法 当 Java 调用本地方法时,JNIEnv 会作为参数传入:

    JNIEXPORT void JNICALL Java_com_example_MyClass_myNativeMethod(JNIEnv* env, jobject obj) {
        // 使用 env 与 JVM 交互
    }
    
  3. 通过 JavaVM 获取 JNIEnv 如果线程已经附加到 JVM,可以通过 JavaVM 获取:

    JNIEnv* env;
    jvm->GetEnv((void**)&env, JNI_VERSION_1_8);
    

常见注意事项

  1. 线程安全
    JNIEnv 是线程本地的,不能跨线程共享。同一线程内的 JNIEnv 是安全的。
  2. 引用管理
    局部引用超出作用域会自动释放,但全局引用需手动管理,否则会导致内存泄漏。
  3. 异常处理
    本地代码需要显式检查和清理异常状态,避免 JNI 操作中断。
  4. 性能问题
    频繁调用 Java 方法可能会有性能开销,可以考虑缓存方法 ID (jmethodID) 或减少 JNI 调用次数。

总结

JNIEnv 是 JNI 编程中操作 JVM 的核心接口,用于在本地代码中调用 Java 方法、操作 Java 对象和管理 JVM 资源。它为 C/C++ 和 Java 提供了桥梁,但需要严格遵守线程安全和引用管理的规则以避免错误和内存泄漏。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值