JNI的初级探索

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


第一步:编写带有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字节码文件。


第三步:使用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======这一行信息,我们是在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

(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======


到目前为止,JNI的用法已经基本可以明了了。但值得注意的是,此java文件并没有涉及到包,所以,遇到的问题是比较少的。如果在java程序中引入包的概念,又会有什么问题呢?

请看下回分解吧~~嘻嘻


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江公望

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值