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带来一定的阻力。