Keuin's

在Java程序中使用JNI调用本机代码

测试环境:

0. 编写JVM端桩函数

要调用原生代码,需要先编写桩函数,定义原生代码的接口。例子如下:

文件 NativeClass.java

public class NativeClass {
    native void myNativeMethod();
}

在其目录下运行:

javac -h .\NativeClass.java

得到生成的.h头文件:

文件 NativeClass.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeClass */

#ifndef _Included_NativeClass
#define _Included_NativeClass
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     NativeClass
 * Method:    myNativeMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_NativeClass_myNativeMethod
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

该头文件包含我们需要在原生代码中实现的C函数签名,以及一些标注,用来指示符号导出等过程。

1. 编写原生代码

这个头文件可以被C/C++程序使用。我们的测试C++实现如下:

文件 NativeClass.cpp

#include "NativeClass.h"
#include <iostream>

JNIEXPORT void JNICALL Java_NativeClass_myNativeMethod
  (JNIEnv *, jobject) {
    std::cerr << "Hello, world!" << std::endl;
  }

2. 编译本机代码到动态链接库

编译为目标文件:

g++ -c -I"$Env:JAVA_HOME"\include -I"$Env:JAVA_HOME"\include\win32 .\NativeClass.cpp -o NativeClass.o

编译为DLL动态链接库文件:

g++ -shared -o NativeClassDLL.dll NativeClass.o "-Wl,--add-stdcall-alias"

3. 在Java代码中调用本机函数

文件:Program.java

public class Program {
    public static void main(String[] args) {
        System.loadLibrary("NativeClassDLL");
        NativeClass nativ = new NativeClass();
        nativ.myNativeMethod();
    }
}

编译:

javac .\Program.java

(在生成头文件时NativeClass.class就被编译好了。)

运行:

PS C:\Users\Keuin\...\jnitest\src> java "-Djava.library.path=." Program
Hello, world!

4. Linux环境下的一些差异

JVM的参数java.library.path用来指定库文件的搜索路径。JNI用到的动态链接库(*.dll, *.so)需要放到这些路径里,由JVM动态加载。Java程序使用System.loadLibrary("NativeClassDLL");来让JVM加载指定的库。但是Linux环境下,搜索的库文件名必须加上lib前缀。即本例在Windows环境下面运行,需要把DLL文件命名为NativeClassDLL.dll,而在Linux环境下,就需要把so文件命名为libNativeClassDLL.so

5. 参考

https://www.baeldung.com/jni