【转】JNI(Java Native Interface)的简介

来源:互联网 发布:帝国cms 图片系统模型 编辑:程序博客网 时间:2024/05/17 03:08

因为刚刚接触android和java平台,对jni到底是什么,还不是很了解,所以从csdn转载了一篇文章,以便自己对jni有一个认识,也跟大家分享一下。

jni是java native interface的缩写。从java 1.1开始,java native interface (jni)标准成为java平台的一部分,它允许java代码和其他语言写的代码进行交互。jni一开始是为了本地已编译语言,尤其是c和c++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。jni标准至少保证本地代码能工作在任何java 虚拟机实现下。

一、jni(java native interface)的设计目的

·the standard java class library may not support the platform-dependent features needed by your application.

·you may already have a library or application written in another programming language and you wish to make it accessible to java applications

·you may want to implement a small portion of time-critical code in a lower-level programming language, such as assembly, and then have your java application call these functions

二、jni(java native interface)的书写步骤

·编写带有native声明的方法的java类

·使用javac命令编译所编写的java类

·使用javah ?jni java类名生成扩展名为h的头文件

·使用c/c++实现本地方法

·将c/c++编写的文件生成动态连接库

1) 编写java程序:

这里以helloworld为例。

代码1:

class helloworld {

public native void displayhelloworld();

static {

system.loadlibrary("hello");

}

public static void main(string[] args) {

new helloworld().displayhelloworld();

}

}

声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明改方法为native的,并且不能实现。其中方法的参数和返回值在后面讲述。

load动态库:system.loadlibrary("hello");加载动态库(我们可以这样理解:我们的方法displayhelloworld()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以static块进行加载的。同时需要注意的是system.loadlibrary();的参数“hello”是动态库的名字。

main()方法是函数得入口点。

2) 编译

javac helloworld.java

3) 生成扩展名为h的头文件

javahhelloworld

头文件的内容:

/* do not edit this file - it is machine generated */

#include

/* header for class helloworld */

#ifndef _included_helloworld

#define _included_helloworld

#ifdef __cplusplus

extern "c" {

#endif

/*

* class: helloworld

* method: displayhelloworld

* signature: ()v

*/

jniexport void jnicall java_helloworld_displayhelloworld(jnienv *, jobject);

#ifdef __cplusplus

}

#endif

#endif

(这里我们可以这样理解:这个h文件相当于我们在java里面的接口,这里声明了一个java_helloworld_displayhelloworld (jnienv *, jobject);方法,然后在我们的本地方法里面实现这个方法,也就是说我们在编写c/c++程序的时候所使用的方法名必须和这里的一致)。

4) 编写本地方法(c/c++程序)

实现和由javah命令生成的头文件里面声明的方法名相同的方法。

代码2:

1 #include "jni.h"

2 #include "helloworld.h"

3 #include ......

4jniexport void jnicall java_helloworld_displayhelloworld(jnienv *env, jobject obj)

{

printf("hello world! ");

return;

}

注意第1行,需要将jni.h(该文件可以在%java_home%/include文件夹下面找到)文件引入,因为在程序中的jnienv、jobject等类型都是在该头文件中定义的;

另外在第2行需要将helloworld.h头文件引入(我是这么理解的:相当于我们在编写java程序的时候,实现一个接口的话需要声明才可以,这里就是将helloworld.h头文件里面声明的方法加以实现。当然不一定是这样)。然后保存为helloworldimpl.c就ok了。

5) 生成动态库

这里以在windows中为例,需要生成dll文件。在保存helloworldimpl.c文件夹下面,使用vc的编译器cl生成。

命令行如下:cl -i%java_home%\include -i%java_home%\include\win32 -ld helloworldimp.c -fehello.dll

注意:生成的dll文件名在选项-fe后面配置,这里是hello,因为在helloworld.java文件中我们loadlibary的时候使用的名字是hello。当然这里修改之后那里也需要修改。另外需要将-i%java_home%\include -i%java_home%\include\win32参数加上,因为在第四步里面编写本地方法的时候引入了jni.h文件。

6) 运行程序

java helloworld就ok。

三、jni(java native interface)的简要使用例子

下面是一个简单的例子实现打印一句话的功能,但是用的c的printf最终实现。一般提供给java的jni接口包括一个so文件(封装了c函数的实现)和一个java文件(需要调用path的类)。

1. jni的目的是使java方法中能够调用c实现的一些函数,比如以下的java类,就需要调用一个本地函数testjni(一般声明为private native类型),首先需要创建文件weiqiong.java,内容如下:

class weiqiong

{

static

{

system.loadlibrary("testjni");//载入静态库,test函数在其中实现

}

private native void testjni(); //声明本地调用

public void test()

{

testjni();

}

public static void main(string args[])

{

weiqiong haha = new weiqiong();

haha.test();

}

}

2.然后执行javac weiqiong.java,如果没有报错,会生成一个weiqiong.class。

3.然后执行javah weiqiong,会生成一个文件weiqiong.h文件,其中有一个函数的声明如下:

jniexport void jnicall java_weiqiong_testjni(jnienv *, jobject);

4.创建文件testjni.c将上面那个函数实现,内容如下:

#include "jni.h"

#include weiqiong.h

jniexport void jnicall java_weiqiong_testjni(jnienv *env, jobject obj)

{

printf("haha---------go into c!!! ");

}

5.为了生成.so文件,创建makefile文件如下:

libtestjni.so:testjni.o makefile

gcc -wall -rdynamic -shared -o libtestjni.so testjni.o

testjni.o:testjni.c weiqiong.h

gcc -wall -c testjni.c -i./ -i/usr/java/j2sdk1.4.0/include -i/usr/java/j2sdk1.4.0/include/linux

cl:

rm -rf *.o *.so

注意:gcc前面是tab空,j2sdk的目录根据自己装的j2sdk的具体版本来写,生成的so文件的名字必须是loadlibrary的参数名前加“lib”。

6.export ld_library_path=.,由此设置library路径为当前目录,这样java文件才能找到so文件。一般的做法是将so文件copy到本机的ld_library_path目录下。

7.执行java weiqiong,打印出结果:“haha---------go into c!!!”

四.jni(java native interface)调用中考虑的问题

在首次使用jni的时候有些疑问,后来在使用中一一解决,下面就是这些问题的备忘:

1。 java和c是如何互通的?

其实不能互通的原因主要是数据类型的问题,jni解决了这个问题,例如那个c文件中的jstring数据类型就是java传入的string对象 ,经过jni函数的转化就能成为c的char*。

对应数据类型关系如下表:

java 类型

本地c类型

说明

boolean

jboolean

无符号,8 位

byte

jbyte

无符号,8 位

char

jchar

无符号,16 位

short

jshort

有符号,16 位

int

jint

有符号,32 位

long

jlong

有符号,64 位

float

jfloat

32 位

double

jdouble

64 位

void

void

n/a

jni 还包含了很多对应于不同 java 对象的引用类型

2. 如何将java传入的string参数转换为c的char*,然后使用?

java传入的string参数,在c文件中被jni转换为jstring的数据类型,在c文件中声明char* test,然后test = (char*)(*env)->getstringutfchars(env, jstring, null);注意:test使用完后,通知虚拟机平台相关代码无需再访问:(*env)->releasestringutfchars(env, jstring, test);

3. 将c中获取的一个char*的buffer传递给java?

这个char*如果是一般的字符串的话,作为string传回去就可以了。如果是含有’\0’的buffer,最好作为bytearray传出,因为可以制定copy的length,如果copy到string,可能到’\0’就截断了。

有两种方式传递得到的数据:

一种是在jni中直接new一个byte数组,然后调用函数(*env)->setbytearrayregion(env, bytearray, 0, len, buffer);将buffer的值copy到bytearray中,函数直接return bytearray就可以了。

一种是return错误号,数据作为参数传出,但是java的基本数据类型是传值,对象是传递的引用,所以将这个需要传出的byte数组用某个类包一下,如下:

class retobj

{

public byte[] bytearray;

}

这个对象作为函数的参数retobj传出,通过如下函数将retobj中的byte数组赋值便于传出。代码如下:

jclass cls;

jfieldid fid;

jbytearray bytearray;

bytearray = (*env)->newbytearray(env,len);

(*env)->setbytearrayregion(env, bytearray, 0, len, buffer);

cls = (*env)->getobjectclass(env, retobj);

fid = (*env)->getfieldid(env, cls, "retbytes", "[b"]);

(*env)->setobjectfield(env, retobj, fid, bytearray);

4. 不知道占用多少空间的buffer,如何传递出去呢?

在jni的c文件中new出空间,传递出去。java的数据不初始化,指向传递出去的空间即可。

五.jni中对java传入数据的处理

如果传入的是bytearray的话,作如下处理得到buffer:

char *tmpdata = (char*)(*env)->getbytearrayelements(env, bytearray, null);

(*env)->releasebytearrayelements(env, bytearray, tmpdata, 0);

六.java调用本地接口后的数据处理问题

1. java得到的数据是string的话,直接处理就可以了。

2. 得到的如果是bytearray的话,作如下处理:

datainputstream in = new datainputstream(new bytearrayinputstream(bytearray));

byte []byte1 = new byte[36];

in.read(byte1,0,36);

string string = new string(byte1);

system.out.println("读出的第一个字段为:"+string);

转载地址:http://blog.csdn.net/drean725/archive/2007/05/07/1599116.aspx

作者:crazypebble

出处:http://crazypebble.cnblogs.com/

欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

绿色通道:好文要顶关注我收藏该文与我联系


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
原创粉丝点击