现在的位置: 首页 > 综合 > 正文

JNI初步 — Hello JNI示例

2013年03月12日 ⁄ 综合 ⁄ 共 4173字 ⁄ 字号 评论关闭

由于要在android平台上做一个so动态链接库,所以今天抽空看了一下jni。以前只是听说过java通过jni和C/C++互调,但是由于项目中没有真正用到这项技术,所以也没有抽出时间专门学习。现在正好趁这个机会系统学习一下jni。今天参考某本书,做了一个小的demo(传说中的HelloWorld)。参考书是开发的windows平台中的dll动态库,而我做的是LInux平台上的so动态库。由于之前一直做java,对linux上的GNU环境不是很熟悉,遇到一些问题,也在这篇博客里做一下记录。

首先介绍一下我的开发环境:jdk1.6,Ubuntu 12.04 64bit,gcc 4.6.3

第一步:在我的家目录中创建java文件

在java文件中声明native方法,并且在类加载的时候加载so动态库。这里的java文件没有使用包名。java代码如下:

public class HelloJni{
	
	static{
		System.loadLibrary("hellojni");	
	}
	public static void main(String[] args){
		HelloJni helloJni = new HelloJni();
		helloJni.sayHello();
	}
	
	native void sayHello();
}

第二步:编译java文件,生成class文件

执行以下命令

javac HelloJni.java

可以看到当前目录下生成HelloJni.class

zhangjg@MyUbuntu:~$ ls
bin               HelloJni.class  simplegit-progit  workspace  视频  下载
examples.desktop  HelloJni.java   ticgit            公共的     图片  音乐
HelloJni.c~       HelloJni.java~  Ubuntu One        模板       文档  桌面

第三步:生成头文件

执行javah命令,生成c头文件

javah HelloJni

可以看到当前目录下出现HelloJni.h头文件。

zhangjg@MyUbuntu:~$ ls
bin               HelloJni.h        ticgit      模板  下载
examples.desktop  HelloJni.java     Ubuntu One  视频  音乐
HelloJni.c~       HelloJni.java~    workspace   图片  桌面
HelloJni.class    simplegit-progit  公共的      文档

该文件的全部内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJni */

#ifndef _Included_HelloJni
#define _Included_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJni
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJni_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

第四步:创建c文件

在当前目录下创建HelloJni.c文件,在这个c文件中包含上一步中创建的HelloJni.h头文件,并且实现头文件中声明的方法,代码如下:

#include <stdio.h>
#include "HelloJni.h"

/*该方法在HelloJni.h中声明
  JNIEXPORT和JNICALL都是JNI中的关键字
  JNIEnv对应java线程中调用的JNI环境,通过这个参数可以调用一些JNI函数
  jobject对应当前java线程中调用本地方法的对象
*/
JNIEXPORT void JNICALL Java_HelloJni_sayHello
  (JNIEnv * env, jobject obj)
{
	printf("HelloJni! This is my first jni call.");
}

第五步:编译c文件成动态库

执行gcc命令,将c文件编译成动态库,输入以下命令

zhangjg@MyUbuntu:~$ gcc -shared -o hellojni.so HelloJni.c

得到以下错误提示

In file included from HelloJni.c:2:0:
HelloJni.h:2:17: 致命错误: jni.h:没有那个文件或目录
编译中断。

这是因为在HelloJni.h头文件中又包含了其他jni.h头文件,jni.h中又包括了jni_md.h头文件,这两个头文件在jdk的安装目录中而不在系统的头文件目录中,gcc不知道去哪里找这两个头文件,所以要在gcc的命令中指定这两个头文件所在的路径

执行以下命令:

gcc -fPIC -D_REENTRANT -I/develop/jdk1.6.0_31/include -I//develop/jdk1.6.0_31/include/linux -shared -o hellojni.so HelloJni.c

可以看到当前目录下出现了hellojni.so动态库。

zhangjg@MyUbuntu:~$ ls
bin               HelloJni.class  hellojni.so       workspace  图片  桌面
examples.desktop  HelloJni.h      simplegit-progit  公共的     文档
HelloJni.c        HelloJni.java   ticgit            模板       下载
HelloJni.c~       HelloJni.java~  Ubuntu One        视频       音乐

第六步:执行class文件,验证jni调用是否成功

执行如下命令:

java HelloJni

抛出如下错误:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no hellojni in java.library.path
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1738)
	at java.lang.Runtime.loadLibrary0(Runtime.java:823)
	at java.lang.System.loadLibrary(System.java:1028)
	at HelloJni.<clinit>(HelloJni.java:4)
Could not find the main class: HelloJni.  Program will exit.

错误提示很明白,意思是说java虚拟机不能在java.library.path路径中找到hellojni动态库。所以,虚拟机在加载动态链接库时,并不是在当前目录下寻找,而是在java.library.path指定的路径下寻找。而java.library.path路径到底是什么呢?可以看出这应该是虚拟机的一个属性,下面编写一段程序,打印出这个路径。代码如下:

public class TestLibraryPath{
	
	public static void main(String[] args){

		String path = System.getProperty("java.library.path");
		System.out.println(path);
	}
}

执行之后打印出的结果为

/develop/jdk1.6.0_31/jre/lib/amd64/server:/develop/jdk1.6.0_31/jre/lib/amd64:/develop/jdk1.6.0_31/jre/../lib/amd64:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib

/develop/jdk1.6.0_31是我的jdk的安装目录。java.library.path指定的共享库目录并不只有一个,而是有多个,之间用:分割。也就是说必须把共享库放到这写目录中的其中一个中,虚拟机才能加载这个动态库。我们把这个动态库放到/develop/jdk1.6.0_31/jre/lib/amd64目录中。执行如下命令:

zhangjg@MyUbuntu:~$ sudo cp hellojni.so /develop/jdk1.6.0_31/jre/lib/amd64/hellojni.so

将目录切换到/develop/jdk1.6.0_31/jre/lib/amd64中,可以看到hellojni.so

zhangjg@MyUbuntu:~$ cd /develop/jdk1.6.0_31/jre/lib/amd64
zhangjg@MyUbuntu:/develop/jdk1.6.0_31/jre/lib/amd64$ ls | grep hellojni.so
hellojni.so

再次用java命令执行HelloJni,发现还是报相同的错误,也就是链接错误,找不到要加载的动态库。

修改目录/develop/jdk1.6.0_31/jre/lib/amd64中的hellojni.so的权限试一下

zhangjg@MyUbuntu:/develop/jdk1.6.0_31/jre/lib/amd64$ sudo chmod 777 hellojni.so

改变权限后,执行java HelloJni, 还是出现同样的错误。

这就奇怪了, 明明将动态库放入系统指定的目录中,并且增加了所有权限,为什么还是无法加载呢?经过调查得知,linux的动态库命名必须以lib开头,即libXXX.so。XXX是动态库的名字,也就是加载的时候指定名字为XXX而不是libXXX。所以只要将hellojni.so改名为libhellojni.so即可。执行以下命令更改名字:

zhangjg@MyUbuntu:/develop/jdk1.6.0_31/jre/lib/amd64$ sudo mv hellojni.so libhellojni.so

再次执行java HelloJni,打印出c函数中要输出的字符串:

zhangjg@MyUbuntu:~$ java HelloJni
HelloJni! This is my first jni call.

到此为止, Linux上的简单的jni调用示例全部完成。



【上篇】
【下篇】

抱歉!评论已关闭.