JNI
(Java Native Interface
)
JNI
允许运行在JVM上的语言与本地语言
( C / C++ )进行互访问
。
在Java语言中声明一个方法,该方法使用native
关键字进行修饰,它告诉编译器,该方法的实现要使用本地语言
。
示例:
package com.fpliu.newton;
public class Test {
public static native void doSomething();
public native void doSomething2();
}
在Kotlin语言中声明一个方法,该方法使用external
关键字进行修饰,它告诉编译器,该方法的实现要使用本地语言
。
示例:
package com.fpliu.newton
class Test {
companion object {
private external func doSomething()
}
private external func doSomething()
}
生成头文件可以使用javah命令,该命令从JDK10
开始被删除掉了。 被javac -h命令所取代。
javah命令处理的是.class
文件, 因此,他对是什么源文件并不关心。
对于Java语言的源文件,生成.class
:
javac -cp . com/fpliu/newton/Test.java
对于Kotlin语言的源文件,生成.class
:
kotlinc -cp . com/fpliu/newton/Test.kt
根据.class
文件生成头文件
:
javah -jni -cp . com.fpliu.newton.Test
javah -jni -cp . com.fpliu.newton.Test$Companion
javah -jni -cp . -o xx.h com.fpliu.newton.Test com.fpliu.newton.Test$Companion
生成的头文件
示例:
#include <jni.h>
#ifndef _Included_com_fpliu_newton_Test
#define _Included_com_fpliu_newton_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_fpliu_newton_Test
* Method: doSomething
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_fpliu_newton_Test_doSomething(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
生成的头文件中的每个函数声明中都有JNIEXPORT
和JNICALL
两个单词。
JNIEXPORT
的定义如下:
#define JNIEXPORT __attribute__ ((visibility ("default")))
__attribute__
是由编译器
定义的宏
,GCC和Clang都支持它
。
visibility ("default")
就相当于其他语言中的public
,表示该函数可以被外部调用,如果没有此宏
修饰, 默认是只能在该动态库
内部使用。
JNICALL
的定义如下:
#define JNICALL
JNICALL
就是起一个标识
的作用。
函数名的组成:Java_包名_类名_函数名
。如果函数名不正确,会导致UnsatisfiedLinkError
。
每个函数至少有2
个参数,第一个参数是JNIEnv* env
。
使用C / C++实现的功能,最终必须编译为动态库
。 在Java或Kotlin中加载
这个编译好的动态库
。
不同的操作系统支持的动态库
的格式不一样:
操作系统 | 动态库 |
---|---|
.so | |
Windows | .dll |
macOS | .dylib |
编译为对应操作系统支持的动态库
可以在对应操作系统中编译,也可以通过交叉编译
。
public static void System.load(String fileName)
public static void System.loadLibrary(String libName)
操作系统 | 动态库的文件名 |
---|---|
lib${libName}.so | |
Windows | lib${libName}.dll |
macOS | lib${libName}.dylib |
public static void System.loadLibrary(String libName)
该方法是去java.library.path
这个JVM
变量的值中去查找。
获取java.library.path
这个JVM
变量的值:
String libraryPath = System.getProperty("java.library.path");
System.out.printf("libraryPath = %s\n", libraryPath);
追加java.library.path
这个JVM
变量的值:
String JAVA_LIBRARY_PATH = "java.library.path";
System.setProperty(JAVA_LIBRARY_PATH, System.getProperty(getProperty) + ":/usr/lib");
public static void System.load(String fileName)
该方法的参数fileName
必须是完整的文件名,包含前缀lib
和后缀(.so
、.dylib
、.dll
)。
这两个方法
都会自动加载依赖的动态库
,但是,他们都是去java.library.path
这个JVM
变量的值指定的路径中去寻找。 如果使用public static void System.load(String fileName)
加载的动态库
的依赖动态库
不在java.library.path
中, 你应该提前加载好它。
这两个方法
的使用情况,应该根据自己的业务实际情况选择。
对于Java语言来说,通常将加载的方法调用放在一个类
的静态代码块
中, 因为类加载器
在加载一个类
的过程中先执行静态域
和静态代码块
。
示例:
package com.fpliu.newton;
public class Test {
static {
System.loadLibrary("test");
}
public static native void doSomething();
public native void doSomething2();
}
只要在调用native
方法之前将其加载好,就可以,时机可以自行决定。比如放在构造方法
中,都是可以的。
函数签名 | 作用 |
---|---|
| 加载时的回掉函数 |
| 卸载时的回掉函数 |