提起 Java,我们的耳边似乎总会响起这样的论调:
Java 使用虚拟机,性能效率不行,跟 C/C++ 没法比
好像说,Java 跟性能是绝缘的,C/C++ 才是性能的典范。那么,能否将两者结合起来呢?当然可以,就是我们今天的主题 —— Java JNI 。
简单地说,Java JNI 就是将 Java 类中的一些方法用 C/C++ 甚至是汇编来实现,以弥补 Java 某些方面的不足。下面我们通过一个简单的 Hello world 来阐述整个流程是怎么样的。
流程
首先,我们在 jni_first.java 中定义一个类,并且在类中声明一个方法,这个方法就是我们要使用 C/C++ 来实现的:
public class jni_first {
static {
System.loadLibrary("first");
}
public native void disp_jni();
public static void main(String[] args) {
new jni_first().disp_jni();
}
}
**注意,上面的 System.loadLibrary("balabala")
里面的参数是有讲究的。他就是我们 native 方法所在的 C/C++ 文件编译之后的库的文件名。所以在 Windows 下,我们的 C/C++ 文件要编译为 balabala.dll
,在 Linux 下要编译为 libbalabala.so
**,具体我们后面讲。
然后我们首先要将该 Java 文件编译成 class 文件:
javac jni_first.java
然后我们使用 javah
来生成头文件,头文件里面定义了我们 native 方法的函数名。也就是说,我们的 native 方法的函数名使不能让我们随便取的,而是要让机器生成的:
javah jni_first
接下来,我们就会发现我们当前的目录下出现了一个 jni_first.h 的文件,打开该文件看看它的内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class jni_first */
extern "C" {
/*
* Class: jni_first
* Method: disp_jni
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_jni_1first_disp_1jni
(JNIEnv *, jobject);
}
于是我们可以看到我们的 native 方法应该叫什么:Java_jni_1first_disp_1jni
:
接下来,实现我们的 native 方法:
JNIEXPORT void JNICALL Java_jni_1first_disp_1jni(JNIEnv *, jobject)
{
printf("From %s: ", __func__);
printf("Hello, Java world.\n");
return ;
}
编译成库:
g++ -shared -fPIC -I /usr/lib/jvm/java-8-openjdk-amd64/include -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux jni_first.cpp -o libfirs.so
注意这里的 -I 参数,第一个 -I 指定了 jni.h 文件的位置,第二个 -I 指定了 jni_md.h 文件的位置(在 jni.h 中被引用)。同时注意最后生成的库名,在 Windows 应该以 .dll 为后缀,在 Linux 应该以 lib 为前缀,.so 为后缀。
最后,执行:
java -Djava.library.path=. jni_first
这里的 -Djava.library.path=. 指定了库所在的位置是当前路径,不然会报错。于是,我们就可以看到以下输出了:
$ java -Djava.library.path=. jni_first
From Java_jni_1first_disp_1jni: Hello, Java world.
好了,至此,我们就把整个流程都走了一遍了。可以看到,Java JNI 和 Android JNI 的一个区别就是 Android JNI 可以通过 JNINativeMethod 数组来指定 native 方法的函数名,而 Java JNI 只能按照机器生成的头文件来。
EOF
Author: simowce
Permalink: https://blog.simowce.com/jni-programming/
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。