开发环境:Linux(Ubuntu 11.04) + JDK 7
实例说明:利用JNI调用本地代码的方法来实现一个计算Int数组总和的功能
使用JNI调用本地代码,整个开发流程主要包括以下几个步骤:
1、创建一个Java类(IntArray.java);
2、使用javac编译该类(生成IntArray.class);
3、使用javah -jni 产生头文件(生成IntArray.h);
4、使用本地代码实现头文件中定义的方法(编写IntArray.c);
5、编译生成本地动态库(生成libIntArray.so);
6、使用Java运行程序。
一、创建一个Java类(IntArray.java)
class IntArray{
private native int sumArray(int[] arr);
public static void main(String[]args){
IntArray p = new IntArray();
int arr[] = new int[10];
for(int i =0;i<10;i++){
arr[i] = i;
}
int sum = p.sumArray(arr);
System.out.println("Sum = "+sum);
}
static{
System.loadLibrary("IntArray");
}
}注:
1、在Java代码中声明本地方法必须有"native"标识符,native修饰的方法,在Java代码中只作为声明存在。例如: private native int sumArray(int[] arr);
2、在调用本地方法前,必须首先装载含有该方法的本地库. 如IntArray.java中所示,置于static块中,在Java VM初始化一个类时,首先执行这部分代码,这可保证调用本地方法前,装载了本地库。
static{
System.loadLibrary("IntArray");
}
二、使用javac编译该类(生成IntArray.class)
javac IntArray.java
三、使用javah -jni 产生头文件(生成IntArray.h)
javah -jni IntArray
生成IntArray.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class IntArray */
#ifndef _Included_IntArray
#define _Included_IntArray
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: IntArray
* Method: sumArray
* Signature: ([I)I
*/
JNIEXPORT jint JNICALL Java_IntArray_sumArray
(JNIEnv *, jobject, jintArray);
#ifdef __cplusplus
}
#endif
#endif
四、使用本地代码实现头文件中定义的方法(编写IntArray.c)
复制IntArray.h成IntArray.c,对于IntArray.c做以下修改:
1、添加头文件:#include "IntArray.h"
2、去掉以下几句
#ifndef _Included_IntArray
#define _Included_IntArray
#endif
3、实现头文件中定义的方法
IntArray.c具体代码如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class IntArray */
#include "IntArray.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: IntArray
* Method: sumArray
* Signature: ([I)I
*/
JNIEXPORT jint JNICALL Java_IntArray_sumArray
(JNIEnv *env, jobject obj, jintArray arr)
{
jint buf[10] ={0};
jint i = 0,sum = 0;
(*env)->GetIntArrayRegion(env,arr,0,10,buf);
for(i=0;i<10;i++)
{
sum += buf[i];
}
return sum;
}
#ifdef __cplusplus
}
#endif
五、编译生成本地动态库(生成libIntArray.so)
根据本地代码(IntArray.h,IntArray.c)生成本地动态库,命令如下:
gcc -I/usr/lib/jvm/java-7-sun/include/ -I/usr/lib/jvm/java-7-sun/include/linux/ -fPIC -shared -o libIntArray.so IntArray.c注:
其中 -I后面是java的include文件夹地址,请根据您具体的java版本以及安装路径作相应改变;
-f后面的PIC表示生成的库中符号是与位置无关的(PIC就是Position Independent Code),关于PIC,可以参考这篇文章
introduction"="" style="box-sizing: border-box; color: rgb(51, 51, 51); text-decoration: none; outline: none medium; transition: all 0.25s; -webkit-transition: all 0.25s;">http://www.gentoo.org/proj/en/hardened/pic-guide.xml">Introduction to Position Independent Code
-shared表示共享,共享库后缀名.so可以认为是shared object的简称;
-o libIntArray.so,可以理解为编译后输出libIntArray.so库。
更详细地说:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L.:表示要连接的库在当前目录中
-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径
六、使用Java运行程序
java IntArray
可能出现以下结果:
snowdream@snowdream:~$ java IntArray
Exception in thread "main" java.lang.UnsatisfiedLinkError: no IntArray in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
at java.lang.Runtime.loadLibrary0(Runtime.java:845)
at java.lang.System.loadLibrary(System.java:1084)
at IntArray.
分析异常提示可知,我们之前生成的共享库不在系统默认的共享库路径中,程序找不到共享库报错。
解决方法有两个:
1、临时指定共享库libIntArray.so的路径。
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH注:该方法只在当前会话窗口有效,切换到另外一个终端窗口,则需要重新指定共享库路径。
2、运行时加上参数指定共享库libIntArray.so的路径。
java -Djava.library.path=. IntArray注:-D:设置Java平台的系统属性。 此时JavaVM可以在当前位置找到该库。
通过以上任意方法,您都可以得到正确的运行结果:
snowdream@snowdream:~$ java IntArray
Sum = 45
转载自:http://m.2cto.com/os/201108/101048.html
另外,该过程需要linux系统下安装有jdk和gcc/g++,推荐使用g++编译,因为gcc编译容易出错。
操作过程中注意.cpp文件内的extern C需要用__cplusplu包住,否则会提示jni.h和jni_m.h的错误和警告。
操作过程中.h文件一定要与java文件自动生成名称一致,否则会出现调用函数失败的错误,也就是运行报错但编译不会报错。