JNI简介
JNI是Java Native Interface的缩写,中文为Java本地接口。使用此种方式,可以对C/C++代码进行调用,但是,其在本质上是对C/C++生成的动态库进行调用而不是直接对C/C++代码进行调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。
使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。
JNI其实是JAVA和其他编程语言代码的桥梁,通过JNI,两种代码之间可以进行无障碍的对接。从下图的结构中,可以清晰知道JAVA、JNI和其他语言之间的关系。
JNI编程实例
下面我们通过代码实例,来进一步理解JNI的实现过程。
JNI的实现过程可以简单概括为如下几个步骤:
(1)编写带有native声明的方法的java类
(2)使用javac命令编译所编写的java类 (操作命令如:javac TestJni.java)
(3)使用javah命令生成扩展名为.h的头文件 (操作命令如:javah TestJni)
注意:进行生成头文件时候不要在文件名后面加上后缀
(4)使用C/C++实现JAVA本地方法
(5)将C/C++编写的文件,生成动态连接库(.so后缀文件)
(6)OK
(2)使用javac命令编译所编写的java类 (操作命令如:javac TestJni.java)
(3)使用javah命令生成扩展名为.h的头文件 (操作命令如:javah TestJni)
注意:进行生成头文件时候不要在文件名后面加上后缀
(4)使用C/C++实现JAVA本地方法
(5)将C/C++编写的文件,生成动态连接库(.so后缀文件)
(6)OK
第一步:编写带有native声明的方法的java类
(说明:如下代码在Linux redhat中实现)
public class TestJni
{
public native void show();
static
{
//本处使用的是System.load(),所以使用的是绝对路径。而System.loadLibrary()使用的是java.library.ptah路径,且不带后缀和lib前缀
System.load("/home/zdh/c_test/TestJni/libTestJni.so");
//System.loadLibrary("TestJni");
}
public static void main(String args[])
{
TestJni obj = new TestJni();
System.out.println("======1======");
obj.show();
System.out.println("======2======");
}
}
注意:本处的java程序并没有引入package。目的是减少CLASSPATH对理解JNI的干扰。
第二步:使用javac命令编译所编写的java类
将以上代码保存为TestJni.java,并保存在TestJni这个文件夹中。
进入TestJni文件夹,输入命令javac TestJni.java,对java文件进行编译。如果java文件没有错误,会在TestJni目录中生成TestJni.class字节码文件。
进入TestJni文件夹,输入命令javac TestJni.java,对java文件进行编译。如果java文件没有错误,会在TestJni目录中生成TestJni.class字节码文件。
第三步:使用javah命令生成扩展名为.h的头文件
在TestJni目录中输入命令javah TestJni。这么命令执行之后,会在当前目录中生成和java类同名的C/C++头文件TestJni.h。
打开TestJni.h文件,看看里面的内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJni */
#ifndef _Included_TestJni
#define _Included_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TestJni
* Method: show
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_TestJni_show
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
头文件中声明了JNIEXPORT void JNICALL Java_TestJni_show(JNIEnv *, jobject);
这样一个函数。其实这个就是经过JNI包装的,需要我们用C/C++代码实现JNI接口(即JAVA本地方法)。在我们后面加载的动态库中,我们将用C/C++代码来实现这个函数。
第四步:使用C/C++实现JAVA本地方法
创建TestJni.cpp文件,在其中输入如下代码:
#include "TestJni.h" //这个头文件是javah生成的头文件,在这里将它包含进来
#include "stdio.h"
#include "string.h"
JNIEXPORT void JNICALL Java_TestJni_show(JNIEnv *, jobject)
{
printf("=====hzdeng jni======\n");
}
这个C++代码中仅实现了一个接口,接口中只进行了一个操作,即打印“
=====hzdeng jni======”这一行信息。
第五步:将C/C++编写的文件,生成动态连接库
这里需要注意的是,需要知道你是用的Linux版本中,JDK的安装目录。
首先需要知道当前Linux的JDK版本号
在控制台运行java -version即可输出当前系统中的JDK版本号。
JDK的安装目录一般都在/usr/lib/jvm中,不同系统可能会有些差异。需要知道JDK安装目录的原因是:我们要知道jni.h和jni_md.h这两个头文件的路径。待会儿编译cpp文件的时候,需要将他们链接进来。
在我的系统中,jni.h和jni_md.h的路径分别为:
/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include
和
/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include/linux/
知道了这两个关键头文件的路径之后,下面我们就可以将cpp文件编译成so后缀的动态库了。
运行如下命令:
g++ -I/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include/linux/ -I/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include -fPIC -shared -o libTestJni.so Testjni.c
执行完毕后,将会在当前目录生成libTestJni.so动态库文件。现在好了,我们需要的C/C++动态库已经生成了。
注意:在编译的时候如果提示找不到,jni.h头文件,那么就打开TestJNI.h头文件将include <jni.h> 改为#include "jni.h"既可以解决
第六步:运行查看结果
在当前目录输入如下命令:java TestJni 即可看到如下的效果
======1======
=====hzdeng jni======
======2======
=====hzdeng jni======
======2======
=====hzdeng jni======这一行信息,我们是在C++代码中打印出来的。
如今,我们运行java代码,也能够输出这个信息。足矣说明,我们已经成功在JAVA代码中调用了C++的代码了。
常见问题:
在这些JNI 的步骤中,最容易出现问题的,其实是库路径的设置问题。
此处我们用的是绝对路径,不会有什么问题。如果我们将System.load()改成System.loadLibrary()。
然后运行java TestJni,会看到如下打印:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no libTestJni in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1698)
at java.lang.Runtime.loadLibrary0(Runtime.java:840)
at java.lang.System.loadLibrary(System.java:1047)
at TestJni.<clinit>(TestJni.java:9)
此打印是说明,程序在java.library.path指定的路径下,没有找到相应的动态库文件。
这样的话,就需要做相应的修改了。
首先,需要设置Linux下的java.library.path环境变量。
方法如下:
(1)打开用户工作目录下的配置文件 vim .bash_profile
在这个文件中添加如下内容:
LD_LIBRARY_PATH=/home/zdh/lib_java
export LD_LIBRARY_PATH
export LD_LIBRARY_PATH
(2)退出当前控制台,重新登录新的控制台。
(3)将libTestJni.so动态库拷贝到,上一步环境变量设置的目录/home/zdh/lib_java中。
(3)将java代码中,加载动态库的语句改为:System.loadLibrary("TestJni");
注意:此处库不带后缀,也不带lib前缀
(4)重新编译JAVA文件,然后运行
javac TestJni.java
java TestJni
进行上面的操作后,程序可以正常打印了
======1======
=====hzdeng jni======
======2======
=====hzdeng jni======
======2======
到目前为止,JNI的用法已经基本可以明了了。但值得注意的是,此java文件并没有涉及到包,所以,遇到的问题是比较少的。如果在java程序中引入包的概念,又会有什么问题呢?
请看下回分解吧~~嘻嘻