在native线程利用JNI 反射自定义类

来源:互联网 发布:贴图绘制软件 编辑:程序博客网 时间:2024/06/06 03:53

分类: JNI 659人阅读 评论(0) 收藏 举报
jnindk

NDK编程中遇到的一些细节问题,希望对大家有帮助

                                                                                                   -----题记

 

在JNI中,有时候出于业务要求需要实现异步事件机制,例如网络通讯的收发

这时就会在C++中回调java类的方法,于是就会用到java反射机制

在JNI中,实现类反射主要用到以下几个方法:(本例以反射静态方法为例)

JavaVM             jint GetEnv(void **penv, jint version)

JavaVM             jint AttachCurrentThread(void **penv, void *args)

JNIEnv              jclass FindClass(const char *name)

JNIEnv              jmethodID GetStaticMethodID(jclass clazz, const char *name,  const char *sig)

JNIEnv            void CallStaticVoidMethod(jclass cls, jmethodID methodID, ...)

 JavaVM           jint DetachCurrentThread()

假设我们要反射的类是:

[java] view plaincopy
  1. package com.genius.test;  
  2. public class InflectClass {  
  3.       
  4.     public static void test(int cmd,String value,String data){  
  5.   
  6.     }  
  7.       
  8. }  

那么C++中回调的JNI代码就是:

[java] view plaincopy
  1. void testInflect(int cmd, const char* value, const char* data)  
  2. {  
  3.     if (g_vm == NULL)  
  4.     {  
  5.         return ;  
  6.     }  
  7.     int status;  
  8.     JNIEnv *env = NULL;  
  9.     bool isAttach = false;  
  10.     status = g_vm->GetEnv((void **) &env, JNI_VERSION_1_4);  
  11.     if(status != JNI_OK)   
  12.     {  
  13.         status = g_vm->AttachCurrentThread(&env, NULL);  
  14.         if(status < 0) {  
  15.             return;  
  16.         }  
  17.         isAttach = true;  
  18.     }     
  19.   
  20.     jstring valueString = NULL;  
  21.     jstring dataString = NULL;  
  22.     jclass inflectClass = NULL;  
  23.     jmethodID inflectMethod = NULL;  
  24.   
  25.     jclass inflectClass = env->FindClass("com/genius/test/InflectClass");  
  26.     if (inflectClass == NULL)  
  27.     {  
  28.         return;  
  29.     }  
  30.   
  31.     jmethodID inflectMethod= env->GetStaticMethodID(inflectClass, "test""(ILjava/lang/String;Ljava/lang/String;)V");  
  32.     if (inflectMethod == NULL)  
  33.     {  
  34.         return ;  
  35.     }  
  36.   
  37.     valueString = env->NewStringUTF(value);  
  38.     dataString = env->NewStringUTF(data);  
  39.   
  40.     env->CallStaticVoidMethod(inflectClass, inflectMethod, cmd, valueString, dataString);  
  41.   
  42. end:  
  43.     if (env->ExceptionOccurred())  
  44.     {  
  45.         env->ExceptionDescribe();  
  46.         env->ExceptionClear();  
  47.     }  
  48.     if (isAttach)  
  49.     {  
  50.         g_vm->DetachCurrentThread();  
  51.     }  
  52.   
  53.     env->DeleteLocalRef(dataString);  
  54.   
  55. }  

其中g_vm是全局的虚拟机实例,可以在

[java] view plaincopy
  1. JNIEXPORT jint JNICALL   JNI_OnLoad(JavaVM *vm, void *reserved);  

函数中保存该实例

 

jint GetEnv(void **penv, jint version)是获取当前线程对应的JNI环境指针

在JNI中,多线程间JNIEnv 是不可以共享的,所以不能全局保存使用

获取是有可能失败的,比如当前是native线程

何为native线程?即在jni中开启的线程。

而java线程则是调用native方法的宿主线程,可以是主线程也可以是子线程

在获取失败时,调用jint AttachCurrentThread(void **penv, void *args)来将当前线程附加到虚拟机并获取JNI环境指针

 

之后就是通过

jclass FindClass(const char *name)

GetStaticMethodID(jclass clazz, const char *name,  const char *sig)

void CallStaticVoidMethod(jclass cls, jmethodID methodID, ...)

来获取类方法并调用

 

GetStaticMethodID方法的第三个参数是函数签名

主要是c++中重载函数的存在使得函数名不能作为区别函数方法的唯一标示

这时就需要区别参数列表,获取函数签名的命令是javap

具体使用方法大家百度一下就知道了

 

最后如果有附加当前线程到虚拟机的话

需要调用DetachCurrentThread方法来释放线程

否则线程不能正常结束

 

温馨提示:

理论上通过上面几个步骤就可以反射到java层了

但实际操作起来会发现在native线程里执行FindClass的时候会找不到该自定义类(系统类可以)

具体原因不详,解决方案如下:

JNI_OnLoad里获取该类对象并保存一个全局引用

[java] view plaincopy
  1. JavaVM *g_vm = NULL;  
  2. jclass g_inflectClass = NULL;  
  3. jmethodID g_methodID = NULL;  
[java] view plaincopy
  1. void InitInflectClass(JavaVM* vm)  
  2. {  
  3.   g_vm = vm;  
  4.   
  5.     JNIEnv *env = NULL;  
  6.     int status = g_vm->GetEnv((void **) &env, JNI_VERSION_1_4);  
  7.     if(status != JNI_OK)   
  8.     {  
  9.         return ;  
  10.     }  
  11.   
  12.   
  13.     jclass inflectClass = env->FindClass("com/genius/test/InflectClass");  
  14.     if (inflectClass == NULL)  
  15.     {  
  16.         return ;  
  17.     }  
  18.   
  19.     g_inflectClass = inflectClass;  
  20.     g_methodID = env->GetStaticMethodID(inflectClass, "test""(ILjava/lang/String;Ljava/lang/String;)V");  
  21.     if (g_methodID == NULL)  
  22.     {  
  23.         return ;  
[java] view plaincopy
  1.     }  
  2. }  
  3.   
  4. 直接把函数方法保存下来也可以  

然后在C++回调的时候直接使用该全局类或函数方法即可

0 0