JNI 方法注册与签名

最近了解了关于Java JNI接口的一些关于方法注册与签名相关的知识,在此进行一下总结。
使用JNI接口时,我们首先需要把Java方法声明为native:

1
public native void f();

然后编写对应的C/C++代码,并编译成为动态链接库(.dll或.so),在调用Java方法前载入动态链接库即可调用:

1
2
3
static {
System.loadLibrary("native-lib");
}

那么,Java文件中的native方法是如何与native文件中的方法一一对应的呢?
在此有两种方法:静态注册与动态注册,下面将一一介绍:

静态注册

采用静态注册时,Java层的native方法与native层的方法在名称上具有一一对应的关系,具体要求如下:
native层的方法名为:Java_<包名><类名><方法名>(__<参数>)

其中,包名使用下划线代替点号进行分割
只有当native方法出现需要重载的时候,native层的方法名后才需要跟上参数(括号里的内容),参数的编写形式与JNI签名相关(后面会介绍)
通常而言,我们可以把native方法集中在一个类中,然后调用:

1
javah -jni 包名.类名

自动生成对应的c层头文件
下面是静态注册的例子:
Java层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.app.superxlcr.jnitest;

/**
* Created by superxlcr on 2017/5/25.
*/

public class NativeTest {

public native void f();

public native int f(int a, double b);

public native void f(Object a, String b);

public native void g();

}

native层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_app_superxlcr_jnitest_NativeTest */

#ifndef _Included_com_app_superxlcr_jnitest_NativeTest
#define _Included_com_app_superxlcr_jnitest_NativeTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_app_superxlcr_jnitest_NativeTest
* Method: f
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__
(JNIEnv *, jobject);

/*
* Class: com_app_superxlcr_jnitest_NativeTest
* Method: f
* Signature: (ID)I
*/
JNIEXPORT jint JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__ID
(JNIEnv *, jobject, jint, jdouble);

/*
* Class: com_app_superxlcr_jnitest_NativeTest
* Method: f
* Signature: (Ljava/lang/Object;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__Ljava_lang_Object_2Ljava_lang_String_2
(JNIEnv *, jobject, jobject, jstring);

/*
* Class: com_app_superxlcr_jnitest_NativeTest
* Method: g
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_g
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

我们可以看到,对于拥有重载的f 方法,其native方法名称后都带有参数,而没有重载的g 方法则没带有
静态注册JNI方法的弊端非常明显,就是方法名会变得很长,因此下面我们介绍另外一种动态注册的方法

动态注册

使用动态注册时,我们需要准备好需要自己想要对应的native方法,然后构造JNINativeMethod数组,JNINativeMethod是一种结构体,源码如下:

1
2
3
4
5
6
7
8
typedef struct {
// Java层native方法名称
const char* name;
// 方法签名
const char* signature;
// native层方法指针
void* fnPtr;
} JNINativeMethod;

然后重写JNI_OnLoad方法(该方法会在Java层通过System.loadLibrary加载完动态链接库后被调用),我们在其中进行动态注册工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv *env = NULL;
jint result = -1;

// 获取JNI env变量
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
// 失败返回-1
return result;
}

// 获取native方法所在类
const char* className = "com/app/superxlcr/jnitest/MainActivity";
jclass clazz = env->FindClass(className);
if (clazz == NULL) {
return result;
}

// 动态注册native方法
if (env->RegisterNatives(clazz, methods, 1) < 0) {
return result;
}

// 返回成功
result = JNI_VERSION_1_4;
return result;
}

动态注册的大致步骤如下:

  1. 通过vm(Java虚拟机)参数获取JNIEnv变量
  2. 通过FindClass方法找到对应的Java类
  3. 通过RegisterNatives方法,传入JNINativeMethod数组,注册native函数

对于JNINativeMethod结构而言,签名是其非常重要的一项元素,它用于区分Java中native方法的各种重载形式,下面将介绍方法的签名

方法签名

方法签名对于区分Java层native重载方法有重大意义
总的来说,方法签名的组成规则为:

1
(参数类型标识1参数类型标识2...参数类型标识n)返回值类型标识

类型标识对应关系如下:

类型标识 Java类型
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L包名/类名; 各种引用类型
V void

另外,当Java类型为数组时,在标识前会有“[”符号,例如:String[] 类型标识为 [Ljava/lang/String;

下面举几个例子:

1
2
3
4
5
6
7
8
9
10
11
// Signature: ()V
public native void f();

// Signature: (ID)I
public native int f(int a, double b);

// Signature: (Ljava/lang/Object;Ljava/lang/String;)V
public native void f(Object a, String b);

// Signature: ()V
public native void g();