有时,需要在Java中直接调用本地(native)方法,把一些耗时的操作使用效率更高C/C++实现。在Java中调用本地方法通常被称为Java Native Interface(JNI)。那么,什么时候需要用到JNI了?
需要在底层实现耗时更小、更快的程序
在Java中需要调用本地代码库
需要使用平台相关但Java标准库文件不支持的特性
接下来,我们就来看一看,如何在Java中通过JNI来调用本地方法?
准备工作 在开始JNI编程之前,需要确保在系统环境中有配置好了如下程序:
Java虚拟机程序, Java编译器(javac),一个本地方法的头文件生成程序(javah)
定义JNI的库文件以及本地头文件: jni.h,jvm.lib,jvm.dll,jvm.so
c/C++编译器,用于创建共享库代码
前面两条中的程序只要安装了JDK开发环境,都已包含在系统中了,可以直接使用。另外,需要注意的是,利用System.loadLibrary来加载本地库文件时,不同系统对于生成的库文件命名存在差异,例如在HelloWorld.java使用System.loadLibrary(“HelloWorld”):
Solaris: libHelloWorld.so
Linux: libHelloWorld.so
Win: HelloWorld.dll
Mac: libHelloWorld.jnilib
在生成本地库文件时需要留意。接下来,我们就一步步来看下如何在Java中调用本地方法(以下实现均基于Ubuntu 16.04 64bit版本)。
六步实现在Java中调用本地方法 1.编写Java文件 指定一个文件夹下,新建一个HelloJni.java文件,并在其中声明需要调用的本地方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class HelloJni { public native int add (int x, int y) ; public native String talkBack (String str) ; public native void sayHello (String str) ; static { System.loadLibrary("HelloJni" ); } public static void main (String[] args) { HelloJni jni = new HelloJni (); int add = jni.add(5 ,3 ); jni.sayHello("hello jni" ); System.out.println("add: " + add); System.out.println("talkBack: " + jni.talkBack("hello" )); } }
2. 编译Java文件,生成.class文件 在HelloJni.java目录下,打开命令行工具,输入:
查看该文件夹,可以看到已经生成了相应的.class文件。
3. 生成本地方法对应的头文件 利用JDK中提供的javah程序,我们可以生成Java文件中对应的本地方法头文件,在命令行中输入:
在对应的目录下,可以看到一个名为HelloJni.h的文件,长成这样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <jni.h> #ifndef _Included_HelloJni #define _Included_HelloJni #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_HelloJni_add (JNIEnv *, jobject, jint, jint) ; JNIEXPORT jstring JNICALL Java_HelloJni_talkBack (JNIEnv *, jobject, jstring) ; JNIEXPORT void JNICALL Java_HelloJni_sayHello (JNIEnv *, jobject, jstring) ; #ifdef __cplusplus } #endif #endif
4. 编写对应的C/C++方法 按照上述生成的HelloJni.h文件,注意函数的名称与返回值必须与HelloJni.h中完全一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include "HelloJni.h" #include <stdio.h> #include <string.h> JNIEXPORT jint JNICALL Java_HelloJni_add (JNIEnv *env, jobject obj, jint n1, jint n2) { return n1 + n2; } JNIEXPORT jstring JNICALL Java_HelloJni_talkBack (JNIEnv *env, jobject obj, jstring str) { const char *s = env->GetStringUTFChars(str,0 ); if (strcmp(s,"hello" ) == 0 || strcmp(s,"hi" ) == 0 ){ return env->NewStringUTF("jni from c/c++" ); } return env->NewStringUTF("unknow talking" ); } JNIEXPORT void JNICALL Java_HelloJni_sayHello (JNIEnv *env, jobject obj, jstring str) { const char *s = env->GetStringUTFChars(str,0 ); char chs[100 ]; strcpy(chs,s); printf(chs); }
5. 生成共享库文件 这里利用Ubuntu系统自带的编译器gcc来产生共享库文件。有关gcc命令的参数可参考 http://man7.org/linux/man-pages/man1/gcc.1.html;在这里,需要主义两点,一是编译时需要包含两个JNI相关的头文件jni.h以及jni_md.h,否则编译时会出现错误:
对于Ubuntu系统,JDK一般安装在:/usr/lib/jvm
1 2 3 4 5 6 :~/Java$ gcc -shared -fPIC libHelloJni.so HelloJni.cpp In file included from HelloJni.cpp:1 :0 : HelloJni.h:2 :17 : fatal error: jni.h: No such file or directory compilation terminated.
一个时生成的库文件名前面需要加上lib,如libHelloJni.so,使用System.loadLibrary调用时则去掉lib:System.loadLibrary(“HelloJni”),否则调用时可能出现无法找到库文件的错误java.lang.UnsatisfiedLinkError,完整的编译命令如下:
1 2 3 gcc -I/usr/lib/jvm/java-1.8 .0 -openjdk-amd64/include/ -I/usr/lib/jvm/java-1.8 .0 -openjdk-amd64/include/linux -fPIC -shared -o libHelloJni.so HelloJni.cpp
6. 运行java程序 在命令行中输入(需要指定.class文件的搜索路径):
1 2 3 java -Djava.library.path=/home/jason/Java HelloJni
java命令可参考:http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html
需要注意的地方
确保.c/.cpp文件中函数名称与生成的.h头文件中的一致
不同操作系统中,生成的共享库文件名称不一样,例如: 在HelloJni.java,System.loadLibrary(“HelloJni”); Solaris: libHelloJni.so Linux: libHelloJni.so Win: HelloJni.dll Mac: libHelloJni.jnilib
执行Java程序时,添加参数 -Djava.library.path=PATH,这里PATH就是JNI库所在位置
参考文献