jni.h
1.0、参考
1.1、JNI简介

JNI(Java Native Interface)

JNI允许运行在JVM上的语言与本地语言( C / C++ )进行互访问

1.2、声明本地方法
1.2.1、在Java语言中声明本地方法

Java语言中声明一个方法,该方法使用native关键字进行修饰,它告诉编译器,该方法的实现要使用本地语言

示例:

package com.fpliu.newton;

public class Test {
    public static native void doSomething();
    public        native void doSomething2();
}
1.2.2、在Kotlin语言中声明本地方法

Kotlin语言中声明一个方法,该方法使用external关键字进行修饰,它告诉编译器,该方法的实现要使用本地语言

示例:

package com.fpliu.newton

class Test {
    companion object {
        private external func doSomething()
    }

    private external func doSomething()
}
1.3、生成头文件

生成头文件可以使用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

生成的头文件中的每个函数声明中都有JNIEXPORTJNICALL两个单词。

JNIEXPORT的定义如下:

#define JNIEXPORT  __attribute__ ((visibility ("default")))

__attribute__是由编译器定义的GCCClang都支持

visibility ("default")就相当于其他语言中的public,表示该函数可以被外部调用,如果没有此修饰, 默认是只能在该动态库内部使用。

JNICALL的定义如下:

#define JNICALL

JNICALL就是起一个标识的作用。

函数名的组成:Java_包名_类名_函数名。如果函数名不正确,会导致UnsatisfiedLinkError

每个函数至少有2个参数,第一个参数是JNIEnv* env

1.4、编写实现文件

可以使用C或者C++或者二者混合实现头文件中声明的函数

1.5、编译为动态库

使用C / C++实现的功能,最终必须编译为动态库。 在JavaKotlin加载这个编译好的动态库

不同的操作系统支持的动态库的格式不一样:

操作系统动态库
.so
Windows.dll
macOS.dylib

编译为对应操作系统支持的动态库可以在对应操作系统中编译,也可以通过交叉编译

1.6、加载动态库
1.6.1、加载动态库的方法
public static void System.load(String fileName)
public static void System.loadLibrary(String libName)
1.6.2、动态库的文件名格式
操作系统动态库的文件名
lib${libName}.so
Windowslib${libName}.dll
macOSlib${libName}.dylib
1.6.3、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");
1.6.4、public static void System.load(String fileName)

该方法的参数fileName必须是完整的文件名,包含前缀lib和后缀(.so.dylib.dll)。

1.6.5、这两个方法的异同点

这两个方法都会自动加载依赖的动态库,但是,他们都是去java.library.path这个JVM变量的值指定的路径中去寻找。 如果使用public static void System.load(String fileName)加载的动态库的依赖动态库不在java.library.path中, 你应该提前加载好它。

这两个方法的使用情况,应该根据自己的业务实际情况选择。

1.6.6、加载时机

对于Java语言来说,通常将加载的方法调用放在一个静态代码块中, 因为类加载器在加载一个的过程中先执行静态域静态代码块

示例:

package com.fpliu.newton;

public class Test {
    static {
        System.loadLibrary("test");
    }
    public static native void doSomething();
    public        native void doSomething2();
}

只要在调用native方法之前将其加载好,就可以,时机可以自行决定。比如放在构造方法中,都是可以的。

1.7、jni.h中定义的函数
函数签名作用
JNIEXPORT jint JNI_OnLoad(
    JavaVM* vm,
    void* reserved
)
加载时的回掉函数
JNIEXPORT void JNI_OnUnLoad(
    JavaVM* vm,
    void* reserved
)
卸载时的回掉函数