JNI使用说明

JNI

编辑

JNIJava Native Interface的缩写,中文为JAVA本地调用。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是CC++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

目录

1定义

2设计目的

3书写步骤

4使用例子

5调用问题

6数据处理

7软件开发

 活动状态

 视频捕捉

1定义编辑

使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下

JNI

这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。

2设计目的编辑

标准的java类库可能不支持你的程序所需的特性。

JNI

或许你已经有了一个用其他语言写成的库或程序,而你希望在java程序中使用它。

你可能需要用底层语言实现一个小型的时间敏感代码,比如汇编,然后在你的java程序中调用这些功能。

 

3书写步骤编辑

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

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

JNI

,然后使用javah + java类名生成扩展名为h的头文件

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

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

·ok

1) 编写java程序:这里以HelloWorld为例。

代码1

1

2

3

4

5

6

7

8

9

10

publicclassHelloWorld {

    publicnativevoiddisplayHelloWorld();//所有native关键词修饰的都是对本地的声明

    static{

        System.loadLibrary("hello");//载入本地库

    }

 

    publicstaticvoidmain(String[] args) {

        newHelloWorld().displayHelloWorld();

    }

}

声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明该方法为native的,并且不能实现。其中方法的参数和返回值在后面讲述。 Load动态库:System.loadLibrary("hello");加载动态库(我们可以这样理解:我们的方法 displayHelloWorld()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以static块进行加载的。同时需要注意的是System.loadLibrary();的参数“hello”是动态库的名字。

2) 编译

没有什么好说的了javac HelloWorld.java

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

jni HelloWorld 头文件的内容:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class HelloWorld */

 

#ifndef _Included_HelloWorld

#define _Included_HelloWorld

#ifdef __cplusplus

extern"C"{

#endif

/*

 * Class:     HelloWorld

 * Method:    displayHelloWorld

 * Signature: ()V

 */

JNIEXPORTvoidJNICALL Java_HelloWorld_displayHelloWorld

  (JNIEnv *, jobject);

 

#ifdef __cplusplus

}

#endif

#endif

JNI

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

4) 编写本地方法实现和由javah命令生成的头文件里面声明的方法名相同的方法。

代码2

1

2

3

4

5

6

7

8

9

10

#include "jni.h"

#include "HelloWorld.h"

 

JNI3//#include other headers

 

JNIEXPORTvoidJNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj)

{

    printf("Hello world!\n");

    return;

}

JNI

注意代码2中的第1行,需要将jni.h(该文件可以在%JAVA_HOME%/include文件夹下面找到)文件引入,因为在程序中的JNIEnv、 jobject等类型都是在该头文件中定义的;另外在第2行需要将HelloWorld.h头文件引入(我是这么理解的:相当于我们在编写java程序的时候,实现一个接口的话需要声明才可以,这里就是将HelloWorld.h头文件里面声明的方法加以实现。当然不一定是这样)。然后保存为 HelloWorldImpl.cok了。

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文件。

如果配置了MinGW,也可以这样来编译:gcc -Wall -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -Id:/java/include Id:/java/include/win32 -shared -o (输出的dll文件名,如sum.dll) (输入的c/c++源文件,abc.c)

6) 运行程序

javaHelloWorldok.

如果用eclipse,需将dllso文件放在项目下,而不是src及其子目录下。

如果用命令行编译,把dll文件放在该包的同目录下。[1]

4使用例子编辑

下面是一个简单的例子实现打印一句话的功能,但是用的cprintf最终实现。一

JNI

般提供给javajni接口包括一个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.然后设置classpath为你当前的工作目录,如直接输入命令行:set classpath = weiqiong.class所在的完整目录(如 c:\test)再执行javah weiqiong,会生成一个文件weiqiong.h文件,其中有一个函数的声明如下:

JNIEXPORT void JNICALL Java_weiqiong_testjni (JNIEnv *, jobject);

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

1. include

2. include

JNIEXPORT void JNICALL Java_weiqiong_testjni (JNIEnv *env, jobject obj) { printf("haha---------go into c!!!\n"); }

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

libtestjni.s o: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”。

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

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

5调用问题编辑

在首次使用JNI的时候有些疑问,后来在使用中一一解决,下面就是这些问题的备忘: 1。 javac是如何互通的?

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

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

Java 类型

本地 类型

实际表示的 类型

Win32

说明

boolean

jboolean

unsigned char

无符号,

byte

jbyte

signed char

有符号,

char

jchar

unsigned short

无符号,16 

short

jshort

short

有符号,16 

int

jint

long

有符号,32 

long

jlong

__int64

有符号,64 

float

jfloat

float

32 

double

jdouble

double

64 

void

void

N/A

N/A

JNI 还包含了很多对应于不同 Java 对象的引用类型如下图:

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

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传出,因为可以制定copylength,如果copystring,可能到’\0’就截断了。

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

一种是在jni中直接new一个byte数组,然后调用函数(*env)->SetByteArrayRegion(env, bytearray, 0, len, buffer);buffer的值copybytearray中,函数直接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,如何传递出去呢?

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

6数据处理编辑

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

char *tmpdata = (char*)(*env)->GetByteArrayElements(env, bytearray, NULL); (*env)->ReleaseByteArrayElements(env, bytearray, tmpdata, 0);

7软件开发编辑

基于JNI嵌入式手机软件开发实例 下面通过一个实例来描述运用JNI技术在手机上操纵摄像头,捕捉视频并存储图片的过程。

活动状态

2为捕捉视频并存储图片的活动/状态图

JNI

根据图2的活动/状态,具体的对应步骤如下:

①发起该流程。

②发起流程后,建立文件用于存储图片。

③用指针获得分配的缓冲器,用于存储获得的帧。

④将指针压栈(序列化缓冲器)。由于手机的内存较小,为了防止内存泄漏,Symbian操作系统有一个Cleanupstack的要求,即在使用指针时,用PushL指针压入栈中,使用完后再用Pop弹出栈.如果在中间调用导致崩溃的函数时果真出现了问题,那么Cleanupstack可以通过调用该指针的析构函数回收占用的空间。

⑤操纵摄像头,捕捉视频,并将图像流从摄像头端传到缓冲器

⑥将摄像头内的图像流存入缓冲器内,并将缓冲器内的流转化为文件流,存为jpg格式的文件,将指向缓冲器的指针弹栈。

⑦在过程⑥中,如果使用完了序列化的缓冲器,则要重新序列化缓冲器,以备后面使用。

⑧当接收到停止视频捕捉的信号后,关闭文件。

⑨流程结束。

视频捕捉

子功能捕捉视频的实现是由操纵摄像头、视频播放(解码器准备)以及建立摄像头和手机之间的连接会话三个活动组成的。其中操纵摄像头是通过调用底层设备的驱动来实现的,需要利用JNI来实现,完成的方法包括准备、建立、删除、销毁摄像头等。视频播放的一系列过程也是通过c++代码来实现的,除了准备、建立、删除、销毁解码器外,还有开始、暂停、停止解码等。建立摄像头手机之间的连接类似建立客户端和服务器连接,视频流从摄像头传到手机界面是通过多媒体会话来完成的。多媒体会话的建立、关闭、摧毁以及会话建立后的发送、取消、读取数据等也是JNI的应用范畴。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值