有时,需要在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. 编译Java文件,生成.class文件
在HelloJni.java目录下,打开命令行工具,输入:
1 |
|
查看该文件夹,可以看到已经生成了相应的.class文件。
3. 生成本地方法对应的头文件
利用JDK中提供的javah程序,我们可以生成Java文件中对应的本地方法头文件,在命令行中输入:
1 |
|
在对应的目录下,可以看到一个名为HelloJni.h的文件,长成这样子:
1 |
|
4. 编写对应的C/C++方法
按照上述生成的HelloJni.h文件,注意函数的名称与返回值必须与HelloJni.h中完全一致:
1 |
|
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 |
|
一个时生成的库文件名前面需要加上lib,如libHelloJni.so,使用System.loadLibrary调用时则去掉lib:System.loadLibrary(“HelloJni”),否则调用时可能出现无法找到库文件的错误java.lang.UnsatisfiedLinkError,完整的编译命令如下:
1 |
|
6. 运行java程序
在命令行中输入(需要指定.class文件的搜索路径):
1 |
|
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库所在位置
参考文献
- http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html
- http://www.ibm.com/developerworks/java/tutorials/j-jni/j-jni.html
- http://www.bogotobogo.com/cplusplus/eclipse_CDT_JNI_MinGW_64bit.php