Java JNI入门

Java JNI入门

JNI 是什么

JNI 是Java Native Interface的缩写,中文:Java本地接口;通过 JNI,我们可以在 Java 代码中调用 C/C++ 函数(动态链接库)。因此,对于 Windows 来说,需要提供 .dll 动态链接文件;对于 Linux 来说,需要提供 .so 动态链接文件。

JNI 开发步骤

  1. 编写 .java 源文件,源文件中含有 native 的成员方法、静态方法
  2. 编译 .java 源文件 -> .class 类文件
  3. 使用 javah 命令javah ClassName,生成 .h 头文件
  4. 编写 .c/.cpp 实现文件
  5. 编译并生成 .dll/.so 动态链接库
  6. 运行,java -Djava.library.path="/path/to/library" ClassName

java.library.path系统属性是 JVM 查找 JNI 动态链接库的路径列表(多个使用;分隔);如果链接失败,则抛出UnsatisfiedLinkError错误。在 Linux 中,我们可以使用LD_LIBRARY_PATH环境变量指定动态链接库的搜索路径,因为 JVM 启动时会使用该环境变量进行初始化java.library.path属性。

LD_LIBRARY_PATH-Djava.library.path方式的区别:
1) LD_LIBRARY_PATH方式,追加java.library.path列表中;
2) -Djava.library.path="/path/to/jni"方式,覆盖java.library.path的默认值。

JNI 命名规则

每个部分之间使用_下划线连接:
1) 前缀Java
2) 全限定类名包名_类名,无名包则为类名
3) 方法名MethodName

例如:
1) com.zfl9.jni.Test类的 native 方法 func:Java_com_zfl9_jni_Test_func
2) 无名包的类 Test 的 native 方法 func:Java_Test_func

JNI HelloWorld

1) 编写 Java 源文件,JNIDemo.java:

2) 使用 javah 命令生成 .h 头文件:

3) 我们来看一下生成的头文件内容:

简单的分析一下头文件的内容:
1) #include <jni.h>,引入 JNI 头文件
2) extern "C" { ... },以 C 方式编译
3) JNIEXPORT void JNICALL Java_JNIDemo_instanceHello(JNIEnv *, jobject)
4) JNIEXPORT void JNICALL Java_JNIDemo_staticHello(JNIEnv *, jclass)

JNIEXPORTJNICALL两个宏是什么?

跨平台相关的两个宏,不需要我们关心。

JNIEnv *参数:
我们先看一下 JNIEnv 是什么:
1) 在 C 语言中,JNIEnv 是一个指针、指向一个结构体
2) 在 C++ 中,JNIEnv 是一个结构体(类)
这个结构体中定义了许多 JNI 方法,方便我们在 C/C++ 中操作 Java 中的数据类型。

因此,如果我们需要在函数中调用该结构体的 JNI 方法:
1) 在 C 中,因为JNIEnv *env的 env 是一个二级指针,因此需要先解一级引用,再调用该结构体中的函数(函数指针),类似(*env) -> func(env, param...)
2) 在 C++ 中,env 只是一个结构体指针,因此只需通过->访问成员函数即可,并且不需要显示传递 this 指针,类似env -> func(param...)

很显然,使用 C++ 编写 JNI 程序更优美一些。

jclassjobject参数:
如果是成员方法,那么第二个参数的类型就是 jobject
如果是静态方法,那么第二个参数的类型就是 jclass

4) 好了,了解这些基本知识之后,我们开始编写 C++ 实现文件:

5) 编译并生成 .so 动态链接库,libjnitest.so:

6) 设置LD_LIBRARY_PATH环境变量,运行 Java 程序:

好了,现在我们已经基本熟悉了 JNI 开发的基本步骤。
请大家思考一个问题,main() 函数可以是 native 的吗?答案是可以。有兴趣的可以自己试一试。

JNI 头文件

下面的头文件是经过简单整理的,并且是使用 g++ 环境(64-bit/Linux)编译的,适用于 C++