java JNI的使用

JAVA本地接口(JNI),是JAVA比较特殊的课题,因为JAVA本地接口(JNI)设计不只是JAVA语言设计,它还是JAVA与C或C++程序设计语言结合的课题.

通常来说一般会在如下情况使用JNI技术:

1,应用需要调用JAVA语言不支持的依赖于系统平台的特性.

2,为了整合一些遗留下来的非JAVA语言开发的系统.

3,为了创建节省时间的应用,不得不采用低级语言.然后通过JAVA调用.
JNI实现了java和c,c++ ,汇编等语言的双向调用。

简单的例子:

JAVA:
Employee中的一个本地方法native showSalary();

class Employee {

[color=red]public native void showSalary();[/color]

static {

System.loadLibrary("Employee"); // c/c++语言的库文件,windows用dll,linux/unix用.so文件,本例中是Employee.dll文件
该文件必须放在java应用可以找到的路径下,windows下一般放在Windows/System下,这样java应用就可以装载这个库文件了。

}

public static void main(String[] args) {

(new Employee()).showSalary();

}

}


C

头文件:*.h

JNI对于本地方法的声明格式有特殊要求,但是JAVA提供了一个非常好用的工具,操作如下:

>javac Employee.java // 得到class文件

>javah Employee // 为Employee.class文件生成对应的Employee.h

生成文件大致如下(省略一些注释和没用的代码,熟悉c语言的人应该很容易看懂):

#include <jni.h> //该文件存在于jdk厂商提供的jdk安装包内,通常位于安装目录include目录下

#ifndef _Included_Employee

#define _Included_Employee

#ifdef __cplusplus

extern "C" {

#endif

JNIEXPORT void JNICALL Java_Employee_showSalary(JNIEnv*,jobject);
//这个方法就是本地方法的声明部分,本地方法在c语言端的声明
#ifdef __cplusplus

}

#endif

#endif

创建c语言的主文件:
创建*.c文件:

#include <jni.h>

#include "Employee.h"

#include <stdio.h>

JNIEXPORT void JNICALL Java_Employee_showSalary(JNIEnv* env, jobject obj){

printf("HELLO WORLD");

return;

}

编译成库:
之后放入指定的文件夹就可以被引用到了。
windows :cl -Ic:\java\include -Ic:\java\include\win32 -LD Employee.c -F employee

linux :gcc -shared -i$JDK_HOME/include -I/usr/include Employee.c -o libemployee.co

UNIX/Solaris:cc -G -I/usr/local/java/include -I/usr/local/java/include/solaris Employee.c -o libemployee.so

执行:

将库文件放入系统目录,WINDOWS为WINNT/System32,linux/unix为LD_LIBRARY_PATH,也可以设置系统路径

setenv LD_LIBRARY_PATH /*.so路径/:$LD_LIBRARY_PATH

>java -cp . Employee

JNI技术中数据类型与处理方法

在jni.h文件中定义有对访问对象实例函数与用来访问对象实例属性的方法.GetXXXField/GetStaticXXXField用来获取相应变量域的值和静态域的值,还有对应的用来设置值的函数SetXXXField/SetStaticXXXField函数.除了对域访问的函数以外还有对应的访问方法的函数一般格式如下CallXXXMethod/CallStaticXXXMethod,其中XXX代表访问类型.由于函数众多不一一介绍,具体定义都在jni.h文件里

访问JNI本地数据类型的方法

如下:

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv* env, jobject obj, jstring prompt) {

char buff[128];

const char *str = (*env)->GetStringUTFChars(env, prompt,0); //从java获取数据

printf("%s",str);

(*env)->ReleaseStringUTFChars(env,prompt,str); //释放资源

scanf("%s\n",buff);

return (*env)->NewStringUTF(env,buff); // 返回jstring类型

}

使用JNIEnv接口指针:

本地方法中使用JAVA对象,例如String,都要通过本地环境接口指针来完成,如上例.而且他本身也是调用函数的第一个参数.

在JNI本地方法中访问数组:

JNIEXPORT jint JNICALL Java_IntArray_releaseArray(JNIEnv *env, jobject obj, jintArray arr){

...

int i;

int sum = 0;

jsize len = (*env)->GetArrayLength(env,arr);

jint *body = (*env)->GetFloatArrayElements(env,arr,0);

for (i=0;i<len;i++){ sum+= body[i];}

(*env)->ReleaseIntArrayElements(env,arr,body,0);

return sum;

}

当然也有对应于不同基本类型数组的模版函数Get<type>ArrayElements/Release<type>ArrayElements,但是如果是一个非常巨大的数组的话,也要一次取全部数组?这样会有很大的内存消耗的.JNI因此也提供了一个可以指定读取某一处元素的函数Get/Set<type>ArratRegin();

当元素为一个对象的时候可以使用GetObjectArrayElement()/SetObjectArrayElement()来取和更新指定元素.

JNI中的主要技术:

局部引用与全局引用

由于JNI一般情况下将为JAVA对象创建一个本地引用,这是因为本地引用可以保证被JAVA虚拟机最终在系统中释放,归还系统资源,当程序在那个创建局部引用的本地方法中执行了返回操作后,这个局部引用也就无效了.因此应该避免如下写法:

static jclass cls = 0;

JNIEXPORT void JNICALL Java_FieldAccess_accessFields(JNIEnv* env, jobject obj) {

cls = (*env)->GetObjectClass(env,obj);

......

}

当第二次调用该函数的时候,由于cls指向的引用已经失效,所以会引起错误.正确的方法是:

static jclass cls = 0;

JNIEXPORT void JNICALL Java_FieldAccess_accessFields(JNIEnv* env, jobject obj) {

jclass clsTmp = (*env)->GetObjectClass(env,obj);

clsTmp= (*env)->NewGlobalRef(env,cls1);

......

}

这样这个引用对象会一直存在,当不在使用的时候会自动被JVM回收或直到调用函数DeleteGlobalRef(),所以要记得用完后释放该全局引用对象.但是当创建大量局部引用,或者大数据量对象的时候,为了避免局部引用表溢出,需要显式调用DeleteGlobalRef(),

处理本地方法引起的JAVA错误:

...

(*env)->ExceptionDescribe(env); // 输出一些调试信息

(*env)->ExceptionClear(env); // 清除以前的异常信息

jthrowable newExceCls = (*env)->FindClass(env,"java/lang/IllegalArgumentException");

if(newExceCls==0) return ;

(*env)->ThrowNew(env,newExecCls,"thrown from c code"); //如果存在异常,抛出交由JAVA端处理.

...

当然也可以使用ExceptionOccurred()

...

jthrowable cls = (*env)->ExceptionOccurred(env);

if(cls != 0) (*env)->ThrowNew(env,cls ,"thrown from c code");

线程与本地方法

1,JNI接口指针JNIEnv*仅在当前线程有效,不能传递或者保留到全局引用中以备后用.

2,绝对不可以将一个局部引用从一个线程传递给另一个线程.

3,合理仔细使用全局变量.

本地方法的线程同步

...

(*env)->MonitorEnter(env,obj);

...//同步代码

(*env)->MonitorExit(env,obj);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值