JNI初步 -- Hello JNI示例

来源:互联网 发布:mac远程协助windows 编辑:程序博客网 时间:2024/05/16 08:40

由于要在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:~$ lsbin               HelloJni.class  simplegit-progit  workspace  视频  下载examples.desktop  HelloJni.java   ticgit            公共的     图片  音乐HelloJni.c~       HelloJni.java~  Ubuntu One        模板       文档  桌面


第三步:生成头文件


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

javah HelloJni

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

zhangjg@MyUbuntu:~$ lsbin               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 __cplusplusextern "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:~$ lsbin               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.pathat 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/amd64zhangjg@MyUbuntu:/develop/jdk1.6.0_31/jre/lib/amd64$ ls | grep hellojni.sohellojni.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 HelloJniHelloJni! This is my first jni call.

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