在Java程序中使用JNI调用本机代码
测试环境:
- Windows 10
- Liberica JDK 1.8.0
- MSYS2 g++ 11.2.0
- PowerShell
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
。