JNI学习

JNI学习
一、静态注册

​ 定义:通过 JNIEXPORT 和 JNICALL 两个宏定义声明,在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法。

​ 原理:根据函数名来建立java方法与JNI函数的一一对应关系;

​ 实现流程:

编写java代码
利用javah指令生成对应.h文件
对.h中的声明进行实现(使用c++)

弊端:

1、编写不方便,JNI方法名字必须遵循规则且名字很长

2、编写过程步骤多,不方便

3、程序运行效率低,因为初次调用native函数时需要根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时

​ Java + 包名 + 类名 + 方法名

二、动态注册

原理:利用RegisterNatives方法来注册java方法与JNI函数的一一对应关系

实现流程:

利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;
实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册;
调用 FindClass 方法,获取 java 对象;
调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;

优点:

流程更加清晰可控;
效率更高;

​ 当虚拟机加载SO后,会首先到符号表中找一个名为JNI_OnLoad的函数,如果找到了,会立刻调用它。简单来说当我们在java层调用System.loadLibrary时,如果SO中存在JNI_OnLoad函数会立刻调用他。所以JNI_OnLoad是SO的入口函数,是SO中第一个调用的函数,并且只会被调用一次。
​ JNI_OnLoad函数的第一个参数的类型是JavaVM,JavaVM代表java虚拟机,我们调用

JNIEnv *env;
vm->GetEnv((void**)&env,JNI_VERSION_1_4)

可以得到当前线程的JNIEnv对像,得到JNIEnv对像后,我们调用

env->RegisterNatives(classTest, methods, sizeof(methods)/sizeof(JNINativeMethod));

来注册JNI函数。
RegisterNatives的声明如下:

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)

clazz可以通过env->FindClass函数来得到,参数是该类的全类名,即类名+包名,注意要将.号替换为/。

JNINativeMethod有如下定义:

typedef struct {
 const char* name;
 const char* signature;
 void*       fnPtr;
} JNINativeMethod;

fnPtr代表C/C++层函数的地址。
name代表java层native函数的方法名。
signature代表native函数的签名,签名其实就是函数参数类型列表和返回值的编码。将函数的参数类型和返回值类型按照固定规则的编码,编码为一个字符串,就是函数的签名。

使用动态注册时,C/C++层的jni函数不需要使用extern "C"和JNIEXPORT来声明,只需要将JNI_OnLoad函数使用extern "C"和JNIEXPORT声明即可,再配合fvisibility=hidden标志进行编译。这样可以隐藏我们的jni入口函数,给别人破解我们的SO带来一定的阻力。

posted @ 2021-06-21 11:15  鼬神无悔  阅读(316)  评论(0)    收藏  举报