JNI本地方法加载,注册,校验过程
函数调用方解释
JNI_OnLoad 函数是由 Java 虚拟机(JVM)在加载本地库时自动调用的。具体来说:
- 当应用程序通过
System.loadLibrary或Runtime.loadLibrary加载一个包含本地代码的共享库(如.so文件)时,JVM 会查找并调用该库中的JNI_OnLoad函数。 - 这个函数用于初始化 JNI 环境,并可以执行一些必要的设置工作,例如注册本地方法。
因此,JNI_OnLoad 不是由用户直接调用的,而是由 JVM 在特定时机自动触发。
控制流图(CFG)
由于 JNI_OnLoad 的调用是由 JVM 自动完成的,其控制流较为简单,主要体现在库加载的过程中:
这个流程图展示了从应用程序加载库到 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 的intjlong:对应于 Java 的longjfloat:对应于 Java 的floatjdouble:对应于 Java 的doublejboolean:对应于 Java 的boolean(值为JNI_TRUE或JNI_FALSE)
此外,还定义了==指向 Java 对象的引用类型==,例如:
jobject:任意 Java 对象jstring:Java 字符串jclass:Java 类对象jarray:Java 数组,及其子类型如jintArray、jbyteArray
2. JNI 函数表与 JNIEnv
JNIEnv 是一个==指针结构,提供了一系列==与 JVM 交互的函数,常见的函数有:
- 创建 Java 对象:
NewObject、NewStringUTF - 调用 Java 方法:
CallObjectMethod、CallStaticIntMethod - 访问字段:
GetFieldID、SetObjectField - 数组操作:
GetArrayLength、GetIntArrayElements - 异常处理:
Throw、ExceptionOccurred
例子:调用 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:线程未附加到 JVMJNI_ENOMEM:内存不足
4. JVM 与线程管理
jni.h 提供了 JVM 和线程的管理接口,例如:
-
JavaVM:表示 JVM 实例的指针
JavaVM *vm; vm->AttachCurrentThread((void**)&env, NULL); vm->DetachCurrentThread(); -
本地线程与 JVM 的绑定:
AttachCurrentThread和DetachCurrentThread
5. 全局与局部引用
NewGlobalRef和DeleteGlobalRef:管理全局引用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++、汇编语言)交互的问题。例如:
- Java 无法直接访问操作系统特定的功能或硬件资源。
- 性能瓶颈:某些高性能任务在纯 Java 实现中效率较低,需要通过本地代码优化。
- 兼容性:整合现有的 C/C++ 库或模块,而不需要将它们完全重写为 Java。
创建过程
- 设计者
- 最初由 Java 团队的核心成员,如 James Gosling(Java 的创始人之一),以及与 JVM 和 Java API 相关的工程师共同设计。
- JNI 规范及其实现是 JVM 规范的一部分,因此在设计时由 JVM 和 Java API 的开发者密切协作完成。
- 开发语言
jni.h使用 C 语言编写,因为 JVM 本身是用 C 和 C++ 实现的,C 语言的 ABI(应用二进制接口)也最适合作为跨语言桥梁。
- 发布时间
jni.h首次随 Java 1.1 发布,成为 Java 平台的重要组成部分。
- 后续维护
- 由 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 的主要特点
- 代表 JVM 的实例
一个应用程序可以包含一个或多个 JVM 实例(尽管在标准的 Java 应用中通常只有一个 JVM 实例)。 - 线程安全
JavaVM的设计支持多线程,并为本地线程提供了附加到 JVM 的能力。 - 与 JNIEnv 相关
JavaVM与JNIEnv密切相关:JavaVM是 JVM 的全局表示,可以跨线程使用。JNIEnv是线程本地的,每个线程都有自己的JNIEnv指针,无法跨线程共享。
JavaVM 的主要用途
-
管理线程 本地线程需要附加到 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(); } -
加载类和方法
通过JavaVM可以间接加载 Java 类或调用 Java 方法(通常需要先获取JNIEnv)。 -
控制 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 创建成功 } -
获取当前线程的
JNIEnvJNIEnv* env; jint res = jvm->GetEnv((void**)&env, JNI_VERSION_1_8); if (res == JNI_OK) { // 已成功获取 } -
销毁 JVM
jvm->DestroyJavaVM();
JavaVM 与 JNIEnv 的关系
JavaVM是 JVM 的全局实例,线程间共享。JNIEnv是线程本地的接口,用于本地代码与 JVM 的交互。- 每个
JavaVM维护了所有JNIEnv的上下文关系。
图示关系:
+---------------+
| JavaVM | (全局 JVM 实例)
+---------------+
|
+---------------------+
| |
+----------------+ +----------------+
| JNIEnv (T1) | | JNIEnv (T2) | (线程本地环境)
+----------------+ +----------------+
使用场景
- 嵌入式 JVM
在嵌入式系统中,C/C++ 程序通过JavaVM加载 JVM 并运行 Java 代码。 - 多线程 JNI 编程
使用JavaVM管理多个线程间与 JVM 的交互。 - 控制 JVM 生命周期
在需要动态加载或销毁 JVM 的场景下,例如运行独立的 Java 子程序。
总结来说,JavaVM 是 JNI 提供的全局接口,用于表示和控制 JVM 的实例,主要负责线程管理、与 JNIEnv 的关联以及 JVM 生命周期的管理。
JNIEnv是什么
JNIEnv 是 Java Native Interface (JNI) 的==核心数据结构之一,它是一个指向函数表的指针,提供了本地代码与 Java 虚拟机 (JVM) 交互的主要接口==。JNIEnv 是 线程本地 的,不能跨线程使用,每个线程都需要单独获取它。
JNIEnv 的主要特点
- 线程本地性
每个附加到 JVM 的线程都会分配一个独立的JNIEnv实例,该实例只在该线程的上下文中有效。 - 操作 Java 对象和 JVM 功能的接口
JNIEnv提供了一系列函数,用于在本地代码中操作 Java 对象、调用方法、访问字段、处理异常等。 - 自动回收
JNIEnv的生命周期由 JVM 管理,程序员无需显式销毁它。 - 指向函数表的指针
JNIEnv是一个结构体指针,内部==存储了大量与 JVM 交互的函数==,称为 JNI 函数表。
JNIEnv 的主要用途
-
加载 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")); -
调用 Java 方法 使用
CallXXXMethod和CallStaticXXXMethod调用 Java 实例方法或静态方法。jmethodID mid = env->GetMethodID(cls, "length", "()I"); jint length = env->CallIntMethod(obj, mid); -
访问 Java 字段 使用
GetFieldID和GetXXXField获取或设置 Java 对象的字段。jfieldID fid = env->GetFieldID(cls, "value", "Ljava/lang/String;"); jstring value = (jstring)env->GetObjectField(obj, fid); -
数组操作 操作 Java 数组(如
jintArray,jbyteArray)。jintArray array = env->NewIntArray(5); jint elements[5] = {1, 2, 3, 4, 5}; env->SetIntArrayRegion(array, 0, 5, elements); -
异常处理 检测和处理 Java 代码抛出的异常。
if (env->ExceptionOccurred()) { env->ExceptionDescribe(); // 打印异常堆栈信息 env->ExceptionClear(); // 清除异常状态 } -
全局和局部引用管理
- 局部引用:默认创建的对象引用在函数结束后自动释放。
- 全局引用:使用
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
-
本地线程附加到 JVM 本地线程需要附加到 JVM 才能获取
JNIEnv。JNIEnv* env; jvm->AttachCurrentThread((void**)&env, NULL); -
Java 调用本地方法 当 Java 调用本地方法时,
JNIEnv会作为参数传入:JNIEXPORT void JNICALL Java_com_example_MyClass_myNativeMethod(JNIEnv* env, jobject obj) { // 使用 env 与 JVM 交互 } -
通过
JavaVM获取JNIEnv如果线程已经附加到 JVM,可以通过JavaVM获取:JNIEnv* env; jvm->GetEnv((void**)&env, JNI_VERSION_1_8);
常见注意事项
- 线程安全
JNIEnv是线程本地的,不能跨线程共享。同一线程内的JNIEnv是安全的。 - 引用管理
局部引用超出作用域会自动释放,但全局引用需手动管理,否则会导致内存泄漏。 - 异常处理
本地代码需要显式检查和清理异常状态,避免 JNI 操作中断。 - 性能问题
频繁调用 Java 方法可能会有性能开销,可以考虑缓存方法 ID (jmethodID) 或减少 JNI 调用次数。
总结
JNIEnv 是 JNI 编程中操作 JVM 的核心接口,用于在本地代码中调用 Java 方法、操作 Java 对象和管理 JVM 资源。它为 C/C++ 和 Java 提供了桥梁,但需要严格遵守线程安全和引用管理的规则以避免错误和内存泄漏。

953

被折叠的 条评论
为什么被折叠?



