Java安全详谈-JNI 底层分析
source link: https://qftm.github.io/2022/05/29/Java-JVM-JNI/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Java安全详谈-JNI 底层分析
JNI (Java Native Interface
,JAVA 本地接口) 允许 Java 代码和其它编程语言编写的代码进行交互,主要为Java和Native层(C/C++)相互调用的接口规范,但是并不妨碍扩展其他语言。 JNI 在 Java1.1 中正式推出,在 Java1.2 中加入了JNI_OnLoad
和JNI_OnUnload
方法。
JNI 设计交互 Native层 C/C++ 代码的主要优点为:特定功能场景下,提升应用性能;代码层保护,增加反编译难度;Native库文件,可重复使用及不同项目间扩展移植。说完优点那么唯一的一个缺点就是C/C++代码编译生成的动态链接库不可跨平台使用。
正常情况下编写的Java代码是不能直接调用C/C++代码,需要通过JNI标准接口进行间接调用访问。Java代码要想使用JNI调用Native层C/C++代码就需要先向JVM注册Native函数,其注册主要分为静态注册和动态注册两大类,通过注册可实现 Java Native 方法与 C/C++ 方法的对应关系。静态和动态注册主要区别在于查找效率:静态注册,首次调用Java Native方法时,会按照JNI命名规则去寻找;而动态注册,由于存在一张映射表JNINativeMethod,因此查找效率高。另外,静态注册多用于NDK开发,而动态注册多用于Framework开发。最终,无论静态注册方法,还是动态注册方法,都需要将相应的C/C++文件编译成平台所需要的动态库。
JNI静态注册在Java代码首次调用 Java Native 函数时完成
通过编写Java程序,调用native层的整数求和函数来了解JNI静态注册实现过程。Java代码调用静态Native函数,需要在类中加载相应动态链接库文件,并声明要调用的Java Native函数
package org.qftm.learn.jni.demo1;
/**
* Created by IntelliJ IDEA.
* User: Qftm
* Date: 2022/5/11
* Time: 17:36
*/
public class IntSum {
// 声明 native 本地函数
public native int sums(int num1, int num2);
// 静态代码块,加载动态链接库
static {
System.loadLibrary("IntSum");
}
public static void main(String[] args) {
// 实例化调用 native 本地函数
System.out.println((new IntSum()).sums(10, 5));
}
}
编译Java程序
λ Qftm >>>: javac org\qftm\learn\jni\demo1\IntSum.java
利用 javah 生成编译native库所需的相应 .h
头文件,javah 会自动识别 class 文件里声明的 java native 本地方法并进行解析处理生成相应 .h
头文件 (也可以根据JNI静态注册规则手动编写头文件)
λ Qftm >>>: javah -o IntSum.h org.qftm.learn.jni.demo1.IntSum
查看 javah 处理生成的 IntSum.h
头文件并进行分析,其中定义了一个JNI Native调用的 C/C++方法Java_org_qftm_learn_jni_demo1_IntSum_sums
,可以看到方法结构与Java方法类似,同样包含方法名、参数、返回类型、修饰符
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class org_qftm_learn_jni_demo1_IntSum */
#ifndef _Included_org_qftm_learn_jni_demo1_IntSum
#define _Included_org_qftm_learn_jni_demo1_IntSum
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_qftm_learn_jni_demo1_IntSum
* Method: sums
* Signature: (II)D
*/
JNIEXPORT jdouble JNICALL Java_org_qftm_learn_jni_demo1_IntSum_sums (JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
默认javah解析生成的JNI头文件中,C/C++ 函数都为JNI Native对应的静态注册函数,其通过 JNIEXPORT 和 JNICALL 两个宏定义声明。除了两个宏定义外,函数还存在返回类型和方法名及函数参数,其中静态注册函数名遵循特定JNI命名规范,即为Java_<PackageName>_<ClassName>_<MethodName>
,这里需要注意下,如果Java代码声明的本地方法名称中本来就包含下划线,那么该部分将使用下划线加数字替换。另外会发现函数多了两个类型参数JNIEnv *
和jobject
,这两个参数由javah
自动添加,并且其参数值由虚拟机自动传入。
- jni.h
#include <jni.h>
引入了JVM所声明的所有JNI接口规范,jni.h
位于$JAVA_HOME/include
目录内,这是JNI中所有的类型、函数、宏等定义的地方。
- extern
extern "C"
声明是为了避免编绎器按照C++的方式去编绎C函数,也就是告诉编译器这部分代码使用C语言的规则进行编译和链接,这样处理的原因主要是涉及到C和C++的函数编译差异。C不支持函数的重载,编译之后函数名不变;而C++支持函数的重载,无论函数是否同名,编译之后函数名会发生改变,即被编译成函数名+参数类型的特殊修饰格式,Windows下修饰格式为:? + 函数名 + @ + 类名/名称空间 + @ + YG/YA/YI(__stdcall、__cdecl、__fastcall) + 函数返回值类型 + 参数类型表(X-void、D-char、E-unsigned char、F-short、H-int、I-unsigned int、J-long、K-unsigned long、M-float、N-double、_N-bool、PA+指针类型-指针) + @Z
;Linux下修饰格式为:函数名 + 参数类型表(d-double、P+指针类型、i-int)
。
举例在 Windows 下测试代码如下
// define1.h 定义自定义函数
extern "C" {
int add1(int num1, int num2);
void info1(char* name, int age);
}
int add2(int num1, int num2);
void info2(char* name, int age);
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "stdio.h"
#include "define1.h"
int add1(int num1, int num2) {
return num1 + num2;
}
void info1(char* name, int age) {
printf("");
}
int add2(int num1, int num2) {
return num1 + num2;
}
void info2(char* name, int age) {
printf("");
}
查看extern "C"
使用C编译方式的函数,可以看到函数被编译后,函数名未发生改变
而,使用C++编译方式的函数,可以看到函数被编译后,函数名发生改变
int add2(int num1, int num2) -> ?add2@@YAHHH@Z
void info2(char* name, int age) -> ?info2@@YAXPADH@Z
那么JNI静态注册调用的C/C++函数必须要使用extern "C"
声明,否则JNI静态查找规则调用Native时会抛出Exception in thread "main" java.lang.UnsatisfiedLinkError
异常。
/*
* Class: org_qftm_learn_jni_demo1_IntSum
* Method: sums
* Signature: (II)D
*/
JNIEXPORT jdouble JNICALL Java_org_qftm_learn_jni_demo1_IntSum_sums (JNIEnv *, jobject, jint, jint);
函数的注释分为三部分,解释如下
Class: 对应的Java层Class完整类
Method: 对应的Java层Native方法
Signature: 对应的Java层Native方法的签名
- JNIEXPORT、JNICALL
后续的分析,在 Windows 平台下生成特定的 dll 动态链接库,使用 VS2019 创建生成 DLL 动态链接库项目(Linux 下使用 gcc -fPIC -shared xxx.c -o yyy.so
编译生成特定的 so 动态链接库)
DLL项目最初结构如下
点击左侧头文件, 选择添加新建项, 新建头文件命名 intsum.h
进行添加,添加后打开该头文件并复制上面 IntSum.h
头文件内容,复制完毕会发现报错找不到 jni.h
头文件
jni.h
头文件位于 JDK 项目 include 目录下,该目录下的所有 .h
文件主要为编译本地代码时使用的 C/C++
头文件,所以需要引入 <JDK_HOME>/include
和 <JDK_HOME>/include/win32
目录作为附加包含目录(导航栏依次选择 项目 => 属性 => C/C++ => 常规 => 附加包含目录),
附加包含目录引入后可以在项目结构的外部依赖项中查看到所有的 .h
头文件依赖
定位关键字JNIEXPORT、JNICALL定义在 jni_md.h
宏定义中,不同平台下JDK所定义的宏定义内容有所不同,如下
// Windows JDK 1.8
#define JNIEXPORT __declspec(dllexport)
#define JNIIMPORT __declspec(dllimport)
#define JNICALL __stdcall
// Linux JDK 1.8
#if (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4) && (__GNUC_MINOR__ > 2))) || __has_attribute(visibility)
#define JNIEXPORT __attribute__((visibility("default")))
#define JNIIMPORT __attribute__((visibility("default")))
#else
#define JNIEXPORT
#define JNIIMPORT
#endif
#define JNICALL
宏定义代表的是define常量右边定义的内容,JNIEXPORT 用来表示函数是否可导出(即:方法的可见性),Linux下__attribute__((visibility("default")))
描述的是”可见性”属性 visibility
,取值如下
default:表示外部可见,类似于public修饰符,可以被外部发现调用
hidden :表示隐藏,类似于private修饰符,只能被内部发现调用
如果JNIEXPORT由hidden属性描述,则被JNIEXPORT修饰的C/C++函数 __attribute__((visibility("hidden"))) jdouble JNICALL Java_org_qftm_learn_jni_demo1_IntSum_sums(JNIEnv*, jobject, jint, jint);
将不会出现在导出表里面,那么JNI静态注册的Native函数被JNI静态调用规则调用时就无法在相应动态链接库的导出表内找到对应的C/C++函数,导致JNI静态规则调用失败;另外,如果Java Native函数对应的C/C++函数没有通过正常的JNIEXPORT修饰(即:去掉JNIEXPORT关键字),那么该C/C++函数同样不会出现在导出表里面,也就无法被JNI静态注册调用规则正常调用,但是可以利用JNI动态注册调用。
JNICALL 用来表示C/C++函数的调用规范(如:__stdcall、__cdecl、__fastcall
),Linux下JNICALL右边是空的,说明只是个空定义,库文件代码可以直接使用右边的内容(空)替换调JNICALL(即:去掉JNICALL关键字)
- JNI 静态调用 Native
编辑 dllmain.cpp
实现intsum.h
头文件定义的C/C++函数,代码如下
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include
#include "intsum.h"
// JNIEnv *env 为JVM环境指针, 用于和JVM交互、jobject obj 表示当前操作的对象
JNIEXPORT jdouble JNICALL Java_org_qftm_learn_jni_demo1_IntSum_sums(JNIEnv* env, jobject obj, jint num1, jint num2) {
return num1+num2;
}
点击导航栏的生成 => 生成解决方案,即可生成所需的 dll 动态链接库,将 Windows DLL 项目生成的 dll 拷贝到 Java JNI 项目下,并测试通过 Java JNI 调用静态 Native 函数
通过上面可以发现,java代码native函数和dll代码里面c/c++函数声明的差异
// java
public native double sums(int num1, int num2);
// c++
JNIEXPORT jdouble JNICALL Java_org_qftm_learn_jni_demo1_IntSum_sums(JNIEnv*, jobject, jint, jint);
通过对比可以发现java native sums函数名在c++中变得很长,这跟JNI native函数注册方式有关,这里使用的是JNI Native静态函数注册,而JNI Native静态注册在编写动态链接库C++代码中必须按照JNI接口规范的命名规则注册,即java native函数对应的C/C++函数名为Java_<PackageName>_<ClassName>_<MethodName>
。那么,当我们在Java中调用静态Native方法时,JVM 就会根据JNI命名规则来查找相应dll库的导出表,即调用Java静态Native方法所对应的 C/C++ 方法。
- JNIENV
JNIENV
直译为 JNI 环境,和具体线程相关。函数默认的第一个参数JNIEnv *env
由虚拟机自动传入,通过JNIEnv *
指针可以对 Java 层代码进行操作,如:获取Java类/父类Class对象、创建Java对象、调用Java对象的方法等。
定位JNIEnv
实现在jni.h
中:Windows JDK 1.8
/*
* JNI Native Method Interface.
*/
struct JNINativeInterface_;
struct JNIEnv_;
#ifdef __cplusplus // C++
typedef JNIEnv_ JNIEnv;
#else // C
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
C++
中JNIEnv
宏定义为一个JNIEnv_
结构体,JNIEnv_
结构体定义如下
/*
* We use inlined functions for C++ so that programmers can write:
*
* env->FindClass("java/lang/String")
*
* in C++ rather than:
*
* (*env)->FindClass(env, "java/lang/String")
*
* in C.
*/
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
#ifdef __cplusplus
jint GetVersion() {
return functions->GetVersion(this);
}
jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
jsize len) {
return functions->DefineClass(this, name, loader, buf, len);
}
jclass FindClass(const char *name) {
return functions->FindClass(this, name);
}
jmethodID FromReflectedMethod(jobject method) {
return functions->FromReflectedMethod(this,method);
}
jfieldID FromReflectedField(jobject field) {
return functions->FromReflectedField(this,field);
}
jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) {
return functions->ToReflectedMethod(this, cls, methodID, isStatic);
}
.......
从上述声明可以看出,函数由结构体指针JNINativeInterface
定义,其定义了JNI函数调用的接口
/*
* JNI Invocation Interface.
*/
struct JNIInvokeInterface_;
struct JavaVM_;
#ifdef __cplusplus
typedef JavaVM_ JavaVM;
#else
typedef const struct JNIInvokeInterface_ *JavaVM;
#endif
struct JNINativeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2;
void *reserved3;
jint (JNICALL *GetVersion)(JNIEnv *env);
jclass (JNICALL *DefineClass)
(JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
jsize len);
jclass (JNICALL *FindClass)
(JNIEnv *env, const char *name);
jmethodID (JNICALL *FromReflectedMethod)
(JNIEnv *env, jobject method);
jfieldID (JNICALL *FromReflectedField)
(JNIEnv *env, jobject field);
jobject (JNICALL *ToReflectedMethod)
(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);
.....
从定义的函数接口可以看出结构体JNINativeInterface_
是操作 Java
层的入口,那么相应C/C++
函数代码通过JNIEnv
指针就可对Java
层进行操作
- jclass、jobject
jclass
表示Java层静态native函数对应Java类的Class对象
jobject
表示Java层非静态native函数对应Java类的实例对象,相当于Java中的this
- Java、JNI、C/C++ 数据类型映射关系
javah
生成的头文件里使用的类型都是jni.h
所定义规范的,目的是为了做到与平台无关,比如:在所有平台上都能保证jint
是32位的有符号整型。
通过定位查看jni.h
等源码,可以得出 Java、JNI、C/C++ 中基本数据类型和引用数据类型的映射关系表,核心源码如下位于jni.h
中
// Windows JDK 1.8
/*
* JNI Types
*/
#ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;
#ifdef __cplusplus
class _jobject {};
class _jclass : public _jobject {};
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};
typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;
#else
struct _jobject;
typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;
#endif
typedef jobject jweak;
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
struct _jfieldID;
typedef struct _jfieldID *jfieldID;
struct _jmethodID;
typedef struct _jmethodID *jmethodID;
(1)基本数据类型关系表
JNI 类型 | Java 类型 | C/C++ 类型 | 本地类型 |
---|---|---|---|
jsize/jint | int | int | int32_t |
jshort | short | short | int16_t |
jlong | long | long/__int64 | int64_t |
jbyte | byte | signed char | char |
jboolean | boolean | unsigned char | uint8_t |
jchar | char | unsigned short | uint16_t |
jfloat | float | float | float |
jdouble | double | double | double |
(2)引用数据类型关系表
JNI 类型 | Java 类型 | C 类型 | C++ 类型 |
---|---|---|---|
jobject | 类实例化对象 | _jobject* | _jobject* |
jclass | 类 | _jobject* | _jclass* |
jstring | java.lang.String | _jobject* | _jstring* |
jthrowable | java.lang.Throwable | _jobject* | _jthrowable* |
jintArray | int[] | _jobject* | _jintArray* |
注意,除了上面定义的String
,Class
,Throwable
、数组
,其它的类都是以jobject
的形式出现。
在面向对象的程序语言中一切皆对象,如果JNI对应的C/C++函数使用C编译器进行编译是没有对象继承的概念,则此时jstring
、jclass
、数组
等皆为jobject
- JNI 签名(描述符)
signature
译为签名,用于Java里面成员的描述符,其概念如下
(1) 如果是字段,表示字段类型的描述符
(2) 如果是函数,表示函数结构的描述符,即:(每个参数类型描述符) + 返回值类型描述符
(1)Java 数据类型描述符关系表
Java 类型 | 字段描述符(签名) | 备注 |
---|---|---|
int | I | int 首字母大写 |
float | F | float 首字母大写 |
double | D | double 首字母大写 |
short | S | short 首字母大写 |
long | L | long 首字母大写 |
char | C | char 首字母大写 |
byte | B | byte 首字母大写 |
boolean | Z | Z (因为 B 已被 byte 使用) |
object | L + /分隔完整类名 | java.lang.String 如: Ljava/lang/String |
array | [ + 类型描述符 | int[] 如:[I |
(2)Java 函数结构描述符关系表
Java 函数 | 函数描述符(签名) | 备注 |
---|---|---|
void | V | 无返回值类型 |
Method | (每个参数字段描述符)函数返回值字段描述符 | double sums(int num1, int num2) => (II)D |
举例 Java Native 函数sums
的签名解读
// Signature: (II)D
public native double sums(int num1, int num2);
// int 类型描述符:I
// double 类型描述符:D
// 参数字段类型描述符:II
// 函数结构描述符:(II)D
动态注册相比静态注册查找效率高、函数名任意。
通过JNI下接口RegisterNatives()
可动态实现JNI中Native函数的注册,即动态将JVM中某个类调用的Native方法与动态链接库中的特定方法做绑定和映射,这样JNI可直接利用JNINativeMethod
映射表来搜索调用Java Native函数对应的C/C++函数。
针对动态注册的核心RegisterNatives()
接口,可通过Java JNI 化静为动
或Native层 JNI_OnLoad()
两种方式进行调用实现
Java JNI 化静为动
以Windows JDK 1.8
源码为例,通过java.lang.Object
类来了解Java静态块中自定义的JNI静态方法registerNatives()
如何调用JNI下RegisterNatives()
接口动态注册特定Java Native函数。
Object
类中首先声明了JNI静态注册函数registerNatives()
,然后通过静态块进行调用
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
.......
查看Java Native函数registerNatives()
对应的JNI静态注册规则下的C/C++函数:Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
// openjdk\jdk\src\share\native\java\lang\Object.c
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}
JNIEXPORT jclass JNICALL
Java_java_lang_Object_getClass(JNIEnv *env, jobject this)
{
if (this == NULL) {
JNU_ThrowNullPointerException(env, NULL);
return 0;
} else {
return (*env)->GetObjectClass(env, this);
}
}
Java_java_lang_Object_registerNatives
函数内通过JNIEnv调用JNI接口下的RegisterNatives()函数进行Java Native函数和C/C++函数的动态注册绑定。
其中methods
结构体数组定义了某个类下Java Native方法与动态链接库中C/C++特定函数的映射关系,而JNINativeMethod
结构体代码定义如下
/*
* used in RegisterNatives to describe native method name, signature,
* and function pointer.
*/
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
JNINativeMethod
结构体成员字段如下表
字段 | 含义 |
---|---|
char *name | Java Native 方法名 |
char *signature | Java Native 方法签名 |
void *fnPtr | C/C++ 方法的函数指针 |
通过上面Object
类JNI动态注册流程的简单分析,可总结出如下流程:核心在于先注册一个JNI静态命名规则方法,然后再利用该方法去调用JNI接口RegisterNatives()
实现JNI动态注册
(1) Java 类中声明一个 registerNatives() 静态Java Native方法
(2) 动态链接库中定义一个JNI动态注册的映射表,JNINativeMethod 类型的结构体数组
(3) 动态链接库中定义并实现一个符合JNI静态注册规则的 Java_<PackageName>_<ClassName>_registerNatives C/C++方法
(4) Java 类中通过静态块加载相应动态链接库
(5) Java 类中通过静态块调用 registerNatives() 静态Java Native方法
(6) JNI 动态注册开始,Java_<PackageName>_<ClassName>_registerNatives C/C++方法调用JNI动态注册接口 RegisterNatives()
(7) JNI 动态注册完成,Java 类中可调用相应JNI动态注册的Native方法
有了JNI静态注册间接实现JNI动态注册的流程,那么我们就可以很方便的编写属于自己的JNI动态注册项目,这里编写一个乘法运算的JNI动态注册调用案例
Multi.java
文件,Java Native方法的声明与调用
package org.qftm.learn.jni.demo2;
/**
* Created by IntelliJ IDEA.
* User: Qftm
* Date: 2022/6/6
* Time: 21:34
*/
public class Multi {
// 声明 registerNatives() 静态Java Native方法
private static native void registerNatives();
// 声明 multi 静态Java Native方法
public static native double multi(int num1, double num2);
static {
// 静态块加载动态链接库
System.loadLibrary("demo2");
// 通过JNI静态命名规则进行查找调用
registerNatives();
}
public static void main(String[] args) {
// 通过JNI动态注册映射表进行查找调用
System.out.println(multi(2, 3.3));
}
}
multi.h
头文件,声明JNI静态命名规则C/C++函数
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class org_qftm_learn_jni_demo2_Multi */
#ifndef _Included_org_qftm_learn_jni_demo2_Multi
#define _Included_org_qftm_learn_jni_demo2_Multi
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_qftm_learn_jni_demo2_Multi
* Method: registerNatives
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_org_qftm_learn_jni_demo2_Multi_registerNatives (JNIEnv*, jclass);
#ifdef __cplusplus
}
#endif
#endif
dllmain.cpp
文件,具体C/C++函数的实现
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include
#include "multi.h"
jdouble MultiNumber(JNIEnv* env, jclass cls, jint num1, jdouble num2) {
return num1 * num2;
}
// typedef struct { char *name; char *signature; void *fnPtr;} JNINativeMethod;
static JNINativeMethod methods[] = {
{(char*)"multi", (char*)"(ID)D", (void*)MultiNumber},
};
// C: env->FindClass("java/lang/String")、C++: (*env)->FindClass(env, "java/lang/String")
// jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, jint nMethods)
// jclass clazz: Java Native 层对应的类
// const JNINativeMethod *methods: 结构体数组,数组的元素是JNI定义的一个结构体 JNINativeMethod
// jint nMethods: methods 结构体数组的大小
JNIEXPORT void JNICALL Java_org_qftm_learn_jni_demo2_Multi_registerNatives(JNIEnv* env, jclass cls) {
env->RegisterNatives(cls, methods, sizeof(methods) / sizeof(methods[0]));
}
Native JNI_OnLoad()
JVM在执行System.loadLibrary()
加载动态链接库时,底层会调用到JNI_OnLoad()
,其主要有两个作用:指定JNI版本、资源初始化。
由于JNI_OnLoad()
执行了初始化资源的操作,所以可以在该方法中调用JNI接口下RegisterNatives()
方法动态注册Java Native函数,则无需在Java类中声明registerNatives()
静态Java Native方法进行操作。
与上面Java JNI 化静为动
的实现流程相比,Native层JNI_OnLoad()
实现JNI动态注册流程,如下
(1) 动态链接库中定义一个JNI动态注册的映射表,JNINativeMethod 类型的结构体数组
(2) 动态链接库中重写 JNI_OnLoad() 方法
(3) Java 类中通过静态块加载相应动态链接库
(4) JNI 动态注册开始,JNI_OnLoad() 方法调用JNI动态注册接口 RegisterNatives()
(5) JNI 动态注册完成,Java 类中可调用相应JNI动态注册的Native方法
下面以编写一个减法运算的JNI动态注册调用案例来了解JNI_OnLoad()
,具体实现代码如下所示
Sub.java
,Java Native方法的声明与调用
package org.qftm.learn.jni.demo3;
/**
* Created by IntelliJ IDEA.
* User: Qftm
* Date: 2022/6/7
* Time: 7:54
*/
public class Sub {
// 声明 sub 静态Java Native方法
public static native double sub(int num1, double num2);
static {
// 静态块加载动态链接库
System.loadLibrary("demo3");
}
public static void main(String[] args) {
// 通过JNI动态注册映射表进行查找调用
System.out.println(sub(8, 3.4));
}
}
dllmain.cpp
文件,具体C/C++函数的实现
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include
jdouble SubNumber(JNIEnv* env, jclass cls, jint num1, jdouble num2) {
return num1 - num2;
}
// typedef struct { char *name; char *signature; void *fnPtr;} JNINativeMethod;
static JNINativeMethod methods[] = {
{(char*)"sub", (char*)"(ID)D", (void*)SubNumber},
};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
// 获取 JNIENV
if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK)
{
return -1;
}
// 获取 Java Native 对应的 Class
jclass jClassName = env->FindClass("org/qftm/learn/jni/demo3/Sub");
// C: env->FindClass("java/lang/String")、C++: (*env)->FindClass(env, "java/lang/String")
// jint RegisterNatives(jclass clazz, const JNINativeMethod *methods, jint nMethods)
// jclass clazz: Java Native 层对应的类
// const JNINativeMethod *methods: 结构体数组,数组的元素是JNI定义的一个结构体 JNINativeMethod
// jint nMethods: methods 结构体数组的大小
jint ret = env->RegisterNatives(jClassName, methods, sizeof(methods)/sizeof(methods[0]));
return JNI_VERSION_1_6;
}**>
文章首发于跳跳糖:https://tttang.com/archive/1622/
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK