首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

java JNI的施用

2012-10-29 
javaJNI的使用JAVA本地接口(JNI),是JAVA比较特殊的课题,因为JAVA本地接口(JNI)设计不只是JAVA语言设计,它

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();


#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);

...

热点排行