JNI中反向访问java对象

来源:互联网 发布:卸载office2016软件 编辑:程序博客网 时间:2024/05/21 01:31

在比较JNI和JNA的时候说了,JNA中本地代码无法访问Java端的对象。这是JNA的一个小缺陷(因为这点在实际情况中很少使用)。在实际系统中,大多就是Java端访问本地代码的相关接口,最后本地代码将一些信息返回即可,这就是最普遍的正向调用。如果系统在设计时,真的产生需要本地代码反向调用Java端对象的时候,就只能采用JNI了,这里就此结合最近的一个小项目给出一点简介和使用心得。

先稍微介绍一下这个小项目的需求:一个读IC卡的设备,设备厂商提供了最常用的3个接口:openDevice,readCardId,closeDevice。我们Java端系统的设计是,提供两个本地接口,open,close。通过用户一个操作调用open,其中进行的操作是:打开设备并且让设备不断循环进行读卡(读卡通过一个布尔控制器控制)。在调用open时,用户还需传递一个Java端处理器对象handler给本地代码,本地代码读取到有效的卡片ID后,就反调handler的相关处理卡片ID的方法(这个处理方法通常会另起一个线程去处理,不影响读卡线程继续读卡操作)。对于close操做,就是将读卡中的布尔控制器置false,然后设备停止读卡操作,关闭设备并退出。

上述设计整体并不复杂,我这次主要也就其中JNI如何反向调用这块给出介绍。利用javah命令,把带有native方法的class文件生成.h头文件时,我们看各个native方法都会对应.h头文件中的一个接口。并且这些接口参数前两个都是一样的:(JNIEnv * env , jobject obj , ......)。这两个参数是Java平台在调用发生时提供的,env表示Java端的环境对象,obj就是当前你用来调用native方法的对象。env是一个非常有用的对象,在写C相关代码是,你使用“env->”会发现这个对象提供了数以百计的函数。我们今天就介绍几个,反调当然也是用的这里的某几个函数。

【一:本地代码向Java端抛异常】

在JNI端执行代码时,不免因为一些参数,配置问题要中断执行,并向Java端抛出运行时异常。我们直接看例子吧:

先在Java端定义自己的异常类(如果能用系统提供的异常来描述也可以):

[java] view plaincopyprint?
  1. package cn.test;  
  2.   
  3. /** 
  4.  * 自定义的一个异常,用于封装调用JNI端时发生的异常信息 
  5.  * 
  6.  */  
  7. public class CallJniException extends RuntimeException{  
  8.   
  9.     private static final long serialVersionUID = 1L;  
  10.   
  11.     public CallJniException(String message) {  
  12.           
  13.         super(message);  
  14.     }  
  15.       
  16. }  


定义上述异常类时,有个注意事项,就是该类必须显式定义一个只有一个字符串参数的构造函数!!这个构造函数会被本地代码端反调!

在本地代码出通过调用env->ThrowNew(...)去构造异常并抛出,具体操作如下:

[cpp] view plaincopyprint?
  1. // 通过异常类的完全限定名,获取其对应的jclass对象   
  2. jclass callJniExceptionClass = env->FindClass("cn/test/CallJniException");  
  3. // 判断某个条件,如果需要抛出异常则抛出   
  4. if(....){  
  5.     env->ThrowNew(callJniExceptionClass, "Oh,Gosh! I'm crushed!! Help Me !!! ");  
  6.     return;  
  7. }  

上述代码中,涉及到两个env的方法:FindClass 和 ThrowNew。FindClass函数通过传入类的完全限定名(注意JNI这边类的完全限定名通过"/"分隔,而不是Java那边的"."),即可得到一个对应的jclass对象。ThrowNew函数需要的第一个参数就是Java端异常类对应的jclass对象,第二个参数是异常描述(JNI会反调异常类的只有一个字符串的构造器去构造异常对象)。调用后,必须return!!这样这个异常会抛到Java端并且本地调用终止。如果不return,这个异常无法抛到Java端,本地调用也会继续进行!!

【二:本地代码反调Java对象的方法】

步骤为:先获取本地对象对应的jclass对象,然后根据jclass对象和方法名称获取该方法对应的jmethodID对象。然后就可以通过这个jmethodID对象调用这个方法了。整个过程和Java中的反射调用非常相像!!我们先看一个调用的例子,然后再给出一些简单解释:

先给出本地要调用的Java端对象对应的类:

[java] view plaincopyprint?
  1. package cn.test;  
  2.   
  3. public class JHandler {  
  4.   
  5.     public void doAction(String info) {  
  6.           
  7.         System.out.println("我被调用了!信息是:" + info);  
  8.     }  
  9. }  

native方法和其生成的头文件中接口定义为:

[java] view plaincopyprint?
  1. public native void testCall(JHandler jhandler);  
[cpp] view plaincopyprint?
  1. JNIEXPORT void JNICALL Java_cn_test_NewCardReader_testCall  
  2.   (JNIEnv *, jobject, jobject);  

cpp文件中对该接口的实现为:

[cpp] view plaincopyprint?
  1. JNIEXPORT void JNICALL Java_cn_test_NewCardReader_testCall  
  2. (JNIEnv * env, jobject obj, jobject jhandler)  
  3. {  
  4.     jclass jhandlerClass = env->GetObjectClass(jhandler);  
  5.     jmethodID doActionMethodId = env->GetMethodID(jhandlerClass, "doAction""(Ljava/lang/String;)V");  
  6.     env->CallVoidMethod(jhandler, doActionMethodId, "Hello,World!");  
  7. }  

这里涉及到了env的3个函数调用,我们分别介绍一下。

GetObjectClass: 根据一个jobject对象得到这个对象的jclass对象。这事获取jclass的另一种方式。

GetMethodID:根据jclass和方法名,以及一个签名(sign)来获取相应的jmethodID对象。即对应Java端对象的某个方法。前两个参数比较好理解,第三个参数就是用来描述方法的参数和返回值的,以区分Java对象中重载的方法。签名是类型进行特定匹配的。Java在bin中提供了javap命令,用于查看一个类方法的签名(通过加-s参数):

[java] view plaincopyprint?
  1. E:\eclipseworkspaces\multithread\ThreadTest1\bin>javap -s cn.test.JHandler  
  2. Compiled from "JHandler.java"  
  3. public class cn.test.JHandler extends java.lang.Object{  
  4. public cn.test.JHandler();  
  5.   Signature: ()V  
  6. public void doAction(java.lang.String);  
  7.   Signature: (Ljava/lang/String;)V  
  8. }  

输出的最后一行,Signature后就是这个方法的签名,这个串就可以作为GetMethodID函数的第三个参数。

CallVoidMethod:调用Java对象返回值为void的方法(env还提供对其他各类返回值的支持),第一个参数是Java对象在本地代码对应的jobject对象,第二个为jmethodID对象,第三个开始顺序提供这个方法需要的参数!

 

综上:上面简单描述了本地代码反调Java端相关对象的步骤。env还提供了各类其他的方法,比如可以得到某个对象的成员变量等。大家在实际使用JNI的过程中,可以多研究一下这个对象。