由于要在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调用示例全部完成。