JNI:Java Native Interface(Java本地接口)
使用了JNI接口的JAVA程序,不再像以前那样自由的跨平台。因为JNI底层是用C/C++实现的,后者是依赖操作平台的,要实现跨平台,就必须将本地代码在不同的操作系统平台下编译出相应的动态库。下面简单介绍下JNI的开发流程。
从编写到运行得到结果共需要执行6步:
(1)编写Java源代码、(2)将Java源代码编译成class字节码文件、(3)用javah命令生成.h头文件、(4)用本地代码实现.h头文件中的函数、(5)将本地代码编译成动态库、(6)运行Java程序
操作环境:Windows
1、编写Java源代码
<span style="font-size:14px;">package com.evan;
public class Hello{
public static void main(String[] args){
System.out.println("---------basic type test---------");
System.out.println(""+5+"+"+8+"="+sum(5,8));
System.out.println("---------String type test---------");
if(args.length == 0){
System.out.println("input the data");
return;
}
String newString = addHeaderString(args[0]);
System.out.println(newString);
System.out.println("length="+newString.length());
System.out.println("---------Array type test---------");
int[] data = new int[10];
System.out.println("primitive data is:");
for(int ii=0;ii<data.length;ii++){
data[ii] = ii;
System.out.print(data[ii]+" ");
}
System.out.println();
System.out.println("improved data is:");
int[] result = improve(data,data.length);
for(int ii=0;ii<result.length;ii++){
System.out.print(result[ii]+",");
}
System.out.println();
System.out.println("primitive data is:");
for(int ii=0;ii<data.length;ii++){
data[ii] = ii;
System.out.print(data[ii]+" ");
}
}
private static native String addHeaderString(String str);
private static native int sum(int a,int b);
private static native int[] improve(int[] data,int size);
static{
System.loadLibrary("Hello");
/*when classload load the class input jvm,it will load the methods of Hello.dll into native method of jvm;
this way suppport the input with absolute path.such as xx/xx/Hello.dll;
*/
}
}
</span>
2、将Java源代码编译成class字节码文件
操作:
javac Hello.java -d ./
备注:
-d指明了产生的Hello.class文件放置的位置;
结果:
得到./com/evan/Hello.class
3、用javah命令生成.h头文件
操作:
javah -classpath ./ -d ./ com.evan.Hello
备注:
-classpath 指定class目标文件所在目录, 程序将会从指定的目录去寻找目标类,即最后的com.evan.Hello
结果:
得到com_evan_Hello.h文件
#include <jni.h>
/* Header for class com_evan_Hello */
#ifndef _Included_com_evan_Hello
#define _Included_com_evan_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_evan_Hello
* Method: addHeaderString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_evan_Hello_addHeaderString
(JNIEnv *, jclass, jstring);
/*
* Class: com_evan_Hello
* Method: sum
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_evan_Hello_sum
(JNIEnv *, jclass, jint, jint);
/*
* Class: com_evan_Hello
* Method: improve
* Signature: ([II)[I
*/
JNIEXPORT jintArray JNICALL Java_com_evan_Hello_improve
(JNIEnv *, jclass, jintArray, jint);
#ifdef __cplusplus
}
#endif
#endif
4、用本地代码实现.h头文件中的函数
操作:
编写一个c或者c++文件,这里创建了一个Hello.c文件;
#include "com_evan_Hello.h"
#include <String.h>
JNIEXPORT jstring JNICALL Java_com_evan_Hello_addHeaderString
(JNIEnv *env, jclass jcl, jstring jstr)
{
const char *cstr = (*env)->GetStringUTFChars(env,jstr,0);
//获取参数jstr的指针;之后通过cstr就可以访问jstr中的数据;最后一个参数是isCopy?
jsize size = (*env)->GetStringUTFLength(env,jstr);
//获取String数据的长度
char buff[256] = {0}; //注意C语言必须要将所有的类型声明放在函数最开始的位置!
if(cstr==NULL){
printf("out of memory \n");
}
(*env)->ReleaseStringUTFChars(env,jstr,cstr);
//释放本函数生成的指向jstr的指针,类似于将cstr置空
//该方法与GetStringUTFChars方法成对出现
sprintf(buff,"evan %s %d",cstr ,size); //这是一个C方法,用于打印字符串到有一个字符数组当中
return (*env)->NewStringUTF(env,buff);
//这里是创建一个新的jstring对象,新对象的值由buff决定,长度是buff实际的值;即一般是小于256
}
JNIEXPORT jint JNICALL Java_com_evan_Hello_sum
(JNIEnv * env, jclass jcl, jint a, jint b)
{
return a+b;
}
JNIEXPORT jintArray JNICALL Java_com_evan_Hello_improve
(JNIEnv *env, jclass jcl, jintArray array,jint size)
{
jint* intarray = (*env)->GetIntArrayElements(env,array,0);
//本地获取到一个访问java堆中数组的指针
jintArray data = (*env)->NewIntArray(env,size);
//生成一个长度为size的IntArray数组
int index = 0;
jint buff[100];
int length=0;
if(size<100) length=size;
else length =100;
if(data==NULL){
printf("out of memory \n");
}
for(;index<length;index++){
intarray[index]++;
//这里虽然改了这个指针所指向的数据,
//但是在java堆中所存储的数组并不会改变,即java类中定义的数组并不会发生变化
buff[index] = 2+intarray[index];
}
(*env)->ReleaseIntArrayElements(env,array,intarray,0);
//释放intarray指针
(*env)->SetIntArrayRegion(env,data, 0,length,buff);
//将buff的数据复制到data中
return data;
}
5、将本地代码编译成动态库
关于动态库的说明:
即将上面的Hello.c变成Hello.dll等动态库文件;
windows对应动态库为*.dll ,linux/unix对应动态库为 *.so ,mac os x对应动态库为 *.jnilib
下面以windows环境来说明如何实现xx.c-->xx.dll文件的转变
操作:
cl -I %JAVA_HOME%\include -I %JAVA_HOME%\include\win32 -LD Hello.c -FeHello.dll
备注:
在cmd中输入是没有该命令的,推荐进入《VS2012 X64工具命令提示》输入cl是有该命令的;
-I指定包含编译的头文件,所在的目录
-LD 指定被编译动态库的目标文件
-Fe 指定生成的动态链接库的文件名;千万注意Fe和后面的名字不能有空格!!!
结果:
得到文件Hello,dll
6、运行Java程序
注意:运行java程序前需要确保java程序能够找得到对应的dll文件
法一:
java -Djava.library.path=./ com.evan.Hello
备注:
-D设置环境变量java.library.path的值,因为我们上面生成的Hello.dll放在当前目录,所以我们就指定了java.library.path=./
法二:
将Hello.dll放入你的jdk安装包的bin目录下;
然后执行java com.evan.Hello;
补充:
你可以运行一下System.out.println(System.getProperty("java.library.path"));
然后从结果中选择任何一个目录放入你的Hello.dll文件,也能达到上面同样的效果;
执行结果如下:
---------basic type test---------
5+8=13
---------String type test---------
evan hjk 3
length=10
---------Array type test---------
primitive data is:
0 1 2 3 4 5 6 7 8 9
improved data is:
3,4,5,6,7,8,9,10,11,12,
primitive data is:
0 1 2 3 4 5 6 7 8 9
补充:
native实现方法中常用的几种数据处理
- 基本数据
- 与平时的操作没有太大的区别,只是在类型前加上一个j符号,如int变成jint; boolean变成jboolean
- String类型数据
- 以函数参数(JNIEnv *env, jobject obj, jstring string)为例
- 获得jstring的指针:const char* str = (*env)->GetStringUTFChars(env,string,0);
- 释放上面得到的指针(减少引用): (*env)->ReleaseStringUTFChars(env,string,str);
- 获得jstring的长度:jsize size = (*env)->GetStringUTFLength(env,string);
- 创建一个jstring数据(一般是作为一个jstring返回值时才用):(*env)->NewStringUTF(env,string,buff);
- 上面的buff是我们在函数内部定义的形如 char buff[256]的一个对象;
- 上面得到的jstring长度等于buff存储的数据实际长度,不等于buff定义的长度(256);
- 注意:return (*env)->NewStringUTF(env,string,buff); 不报错;jstring mstr = (*env)->NewStringUTF(env,string,buff)报错;
- 数组 (下面以int数组为例,其他数组直接将int换成对应的关键字即可)
- 以函数参数(JNIEnv *env, jobject obj, jintArray array)为例
- 获得jintArray的指针:jint* data = (*env)->GetIntArrayElements(env,array,0);
- 释放上面得到的指针(减少引用): (*env)->ReleaseIntArrayElements(env,array,data);
- 获得jintArray的长度:jsize size = (*env)->GetIntArrayLength(env,array);
- 创建一个jintArray数据(一般是作为一个jintArray返回值时才用):jintArray data = (*env)->NewIntArray(env,100); (与string数据不同之处)
- 这里开辟了一块大小为100的jintArray数组区间;
- 对上面开辟的数据赋值:(*env)->SetIntArrayRegion(env, data,0,100,buff); (与string数据不同之处)
- 上面的buff是我们在函数内部定义的形如 jint buff[256]的一个对象;
- 括号中的0 100是限制data的 ,buff是从0开始
- 上面语句的含义就是将buff 0-99的数据复制到 data的0-99位置
- 对于其他类型如自定义object等,参考链接:http://hubingforever.blog.163.com/blog/static/17104057920115992032177/
更多方法的参数和返回值参考:XX/jdk/include/jni.h;xx为的java jdk安装目录;