0%

用JNI实现Java调用C++程序

用deep-learning-4j在Spring项目中集成深度学习模块的时候,目录中出现了一个.so动态链接库文件,我猜到这也许就是所谓的深度学习Backend,用于使用GPU加速计算,于是便对Java中调用本地程序的过程产生了兴趣,想写个Demo来验证以下。

环境:Linux

以一个Fibonacci数列为例,我们想让他作为native方法存在,而不是用Java实现。定义一个Fibonacci类,声明两个方法:

public class Fibonacci {
public native static void init();
public native static int get(int i);
}

本地方法需要native关键字修饰,且无方法体。init()用于初始化一个Fibonacci表,get(i)用于返回序列某个值。用javah Fibonacci生成一个Fibonacci.h头文件:

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

#ifndef _Included_Fibonacci
#define _Included_Fibonacci
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Fibonacci
* Method: init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Fibonacci_init
(JNIEnv *, jclass);

/*
* Class: Fibonacci
* Method: get
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_Fibonacci_get
(JNIEnv *, jclass, jint);

#ifdef __cplusplus
}
#endif
#endif

头文件中包含了上述两个本地方法的声明,第一个参数是JNIEnv*类型,由于Java中是静态方法,第二个参数是jClass类型,之后的参数对应是Java方法的参数列表。接着只需要实现他们即可,创建Fibonacci.cpp,在其中对上述两个方法写实现。

// Fibonacci.cpp
#include "Fibonacci.h"

#define MAXN 100

int value[MAXN];

JNIEXPORT void JNICALL Java_Fibonacci_init
(JNIEnv *env, jclass klass) {
value[0] = 1;
value[1] = 1;
for(int i = 2; i < MAXN; i++) {
value[i] = value[i - 1] + value[i - 2];
}
}

JNIEXPORT jint JNICALL Java_Fibonacci_get
(JNIEnv *env, jclass klass, jint i) {
if(i < MAXN) {
return value[i];
} else {
return -1;
}
}

接着将本地程序编译成动态链接库即可。编译动态链接库需要用到jni.hjni_md.h,其中jni.h位于$JAVA_HOME/include/jni.h,而jni_md.h在Linux下位于$JAVA_HOME/include/linux/jni_md.h,在Windows下位于$JAVA_HOME/include/win32。将他们拷贝到当前路径下,修改头文件中的#include <jni.h>#include "jni.h",然后编译即可。

cp $JAVA_HOME/include/jni.h .
cp $JAVA_HOME/include/linux/jni_md.h . #$JAVA_HOME/include/win32 if windows
g++ -shared -fPIC Fibonacci.cpp -o libfibonacci.so

编译生成动态链接库libfibonacci.so,只需要将他加载进内存,就可以调用绑定他的native方法了。可以用System.loadLibrary("fibonacci")加载fibonacci库,也可以用System.load("/xxx/xxxx/libfibonacci.so")指定.so文件的绝对路径。一般加载过程写在绑定他的类的静态代码块中,在类加载的时候加载。动态链接库的加载可以写在任何地方,加载一次即可。

public class Fibonacci {
static {
System.loadLibrary("fibonacci");
}

public native static void init();
public native static int get(int i);
}

需要注意的是,System.loadLibrary的加载方式必须把库的位置添加到环境变量java.library.path

最后在调用Fibonacci提供的本地方法测试:

public class Main {
public static void main(String[] args) {
Fibonacci.init();
for(int i = 0; i < 10; i++) {
System.out.println(Fibonacci.get(i));
}
}

}

输出正常:
1
1
2
3
5
8
13
21
34
55

更多详细JNI样例见:Java JNI进阶编程

参考资料

Disqus评论区没有正常加载,请使用科学上网