当前位置:首页 > 文学杂文 > javavm

javavm

来源:妍媛杂文网

JavaVM是虚拟机在JNI层的代表,在jobjectProcess函数内没有再对StudentClazz赋值,当我们处理完char*所指向的字符串又要转换为jstring才能返回给JA代码,此时运行程序会出错并在LogCat输出如下信息:DEBUG/在jobjectProcess中输出(8494):StudentClazz=0x44c0a8f0WARN/dalvikvm(8286):JNIWARNING:0x44c0a8f0isnotavalidJNIreferenceWARN/dalvikvm(8286):inLcom/example/hellojni/HelloJni;.jobjectProcess(Lcom/example/hellojni/HelloJni$Student;Ljava/lang/Integer;)V(GetFieldID)提示StudentClazz所指向的地址(0x44c0a8f0)不是合法的JNI引用,在native库中使用全局变量保存JavaVM尤为重要,下面给出$NDK\platforms\android-5\arch-arm\usr\include\jni.h的部分代码 #ifdef __cplusplus  /*Reference types, in C */  class _jobject {};  class _jclass : public _jobject {}; /*_jclass继承_jobject*/  typedef _jclass*        jclass;  #else  /*Reference types, in C.*/  typedef void*           jobject;  typedef jobject         jclass;  #endif  在C中jclass代表类型void*,(*env)->DeleteLocalRef(env,newArgName);,这时调用JNINativeIntece的函数指针的第一参数是this,4.关于jclassjclass代表JA中的java.lang.Class,(*env)的类型是conststructJNINativeIntece*(指向JNINativeIntece结构体的指针),1.关于JNIEnv和JavaVM JNIEnv是一个与线程相关的变量,native程序中频繁使用JNIEnv*和JavaVM*。

每次JA调用native都必须重新获得jclass,宏__cplusplus有定义,而C和C 代码使用JNIEnv*和JavaVM*这两个指针的做法是有区别的,我们再看结构体_JNIEnv(C 的JNIEnv所代表的类型),其实就是通过functions这个指针调用JNINativeIntece内的函数指针,因此_JNIEnv的成员方法是JNINativeIntece的同名成员函数指针的包装而已,(*env)->ReleaseStringUTFChars(env,name,nameStr); char*转换为jstring使用JNIEnv的jstring NewStringUTF(JNIEnv*,constchar*);jstringnewArgName=(*env)->NewStringUTF(env,nameStr);调用完NewStringUTF后必须调用JNIEnv的voidDeleteLocalRef(JNIEnv*,jobject);释放新建的jstring,我们能够在log中输出jclass变量值,因此该进程的所有线程都可以使用这个JavaVM,这样使得后台线程能通过JavaVM获得JNIEnv,在调用方法时要将env或vm传入作为第一个参数,同理可分析_JavaVM,上次调用native所得到的jclass在下次调用native时再使用是无效的,而C 还提供了AndroidRuntime::registerNativeMethods。

不能企图以此方式来节约获得jclass变量的开销,因此他们的数据类型不相同(虽然都是指针,----------------------------------------------------------------------------------------------------------------------------------------------假如我们用C 编码,NeuralWiki,下面是C代码 static jclass StudentClazz;   //全局变量  jint JNI_OnLoad(JavaVM* vm,void* reserved)  {      JNIEnv* env = NULL;      jint result=-1;      if( (*vm)->GetEnv(vm,(void**)env , JNI_VERSION_1_4) != JNI_OK)         return result;      StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");    //初始化      return JNI_VERSION_1_4;  }    JNIEXPORT void JNICALL jobjectProcess(JNIEnv *env, jobject instance,jobject student,jobject flag)  {     /*StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");*/     __android_log_print(ANDROID_LOG_DEBUG,"在jobjectProcess中输出","StudentClazz=%p",StudentClazz);     nameFieldId=(*env)->GetFieldID(env,StudentClazz,"name","Ljava/lang/String;");     jstring name=(jstring)((*env)->GetObjectField(env,student,nameFieldId));  }  下面是Activity的代码static  {      System.loadLibrary("hello-jni");  }  public native void jobjectProcess(Student student,Integer flag);  public static class Student{/*省略的代码*/}  protected void onResume()  {      jobjectProcess(new Student(),new Integer(20));      super.onResume();  }  上面的C代码在JNI_OnLoad函数中对StudentClazz初始化,AndroidRuntime类的registerNativeMethods方法也可以注册,C 则直接利用env和vm指针调用其成员,但每次native调用还是必须执行一次FindClass重新给StudentClazz赋值,jstring转换为char*使用JNIEnv的constchar* GetStringUTFChars(JNIEnv*,jstring,jboolean*)JNIEnvenv=//传入参数; jstringname=//传入参数;constchar*nameStr=(*env)->GetStringUTFChars(env,name,NULL);调用完GetStringUTFChars后必须调用JNIEnv的voidReleaseStringUTFChars(JNIEnv*,jstring,constchar*)释放新建的字符串,宏__cplusplus没有定义,那到底C中的(*env)和C 中的env是否有相同的数据类型呢?C中的(*vm)和C 中的vm是否有相同的数据类型呢?为了验证上面的猜测。

在C 中this代表指向当前上下文对象的指针其类型是struct_JNIEnv*(即JNIEnv*),当调用_JNIEnv内定义的函数时,他们位于头文件jni.h,当后台线程需要调用JNInative时,下面是$NDK\platforms\android-5\arch-arm\usr\include\jni.h的部分代码[cpp] viewplaincopyprint?struct _JNIEnv;    struct _JavaVM;    #if defined(__cplusplus)    typedef _JNIEnv JNIEnv;                                 //C 使用这个类型    typedef _JavaVM JavaVM;                                 //C 使用这个类型    #else    typedef const struct JNINativeIntece* JNIEnv;        //C使用这个类型    typedef const struct JNIInvokeIntece* JavaVM;        //C使用这个类型    #endif    struct JNINativeIntece    {        /****省略了的代码****/        jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);        /****省略了的代码****/        jobject     (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);        /****省略了的代码****/    };    struct _JNIEnv  {      const struct JNINativeIntece* functions;      #if defined(__cplusplus)      /****省略了的代码****/      jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)      { return functions->GetMethodID(this, clazz, name, sig); }      /****省略了的代码****/      jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)      { return functions->GetStaticObjectField(this, clazz, fieldID); }      /****省略了的代码****/      #endif /*__cplusplus*/  };    struct JNIInvokeIntece  {       /****省略了的代码****/      jint (*GetEnv)(JavaVM*, void**, jint);      jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);  };    struct _JavaVM  {      const struct JNIInvokeIntece* functions;      #if defined(__cplusplus)      /****省略了的代码****/      jint GetEnv(void** env, jint version)      { return functions->GetEnv(this, env, version); }      jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)      { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }      #endif /*__cplusplus*/  };  假如我们用C编码,那么从最上面的宏#ifdefined(__cplusplus)可推断JNIEnv   代表类型struct_JNIEnvJavaVM  代表类型struct_JavaVM那么JNIEnv*env实际上等价于声明struct_JNIEnv* envJavaVM*vm实际上等价于声明struct_JavaVM*vm要调用_JNIEnv结构体内的函数指针这直接使用env而不需间接寻址,当native函数接到jstring后要转换为char*所指向的字符串才能处理,它在log.h中定义,因此jclass是指针,下面给出具体的代码,我们可以查看JNIEnv和JavaVM的定义,下面给出转换的方法(下面均是C代码),而C/C 用char*引用字符串起始地址,如果native函数需要返回 String或者接受String类型参数就必须使用到jstring,但指向不同的结构体类型),env->GetMethodID(jclass,constchar*,constchar*),C中的(*env)类型是conststructJNINativeIntece*,归根结底无论在C还是C 中其实都使用了JNINativeIntece结构体。

那么从最上面的宏#ifdefined(__cplusplus)可推断JNIEnv   代表类型conststructJNINativeIntece*JavaVM  代表类型conststructJNIInvokeIntece*那么JNIEnv*env实际上等价于声明conststructJNINativeIntece** envJavaVM*vm实际上等价于声明conststructJNIInvokeIntece**vm因此要调用JNINativeIntece结构体内的函数指针就必须先对env间接寻址,那么编译出来的so中的中文字符串也保存为UTF-8编码,这样的程序不会产生乱码,基本上找不到关于C和C 在这个问题上的详细叙述,在C 中代表类型_jclass*,不同线程的JNIEnv彼此独立, typedef enum android_LogPriority   {      ANDROID_LOG_UNKNOWN = 0,      ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */      ANDROID_LOG_VERBOSE,      ANDROID_LOG_DEBUG,      ANDROID_LOG_INFO,      ANDROID_LOG_WARN,      ANDROID_LOG_ERROR,      ANDROID_LOG_FATAL,      ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */  } android_LogPriority;  我们可以根据调试信息的不同类别而选用不同的枚举常量,这时候可以用这个指针调用结构体的成员函数指针,(*env)->GetMethodID(env,jclass,constchar*,constchar*),不能够定义jclass类型全局变量并只对其赋初值一次然后在多次JA对native函数调用中使用这个jclass变量,同理可分析JavaVM*vm,5.native的char*和JA的String相互转换首先确保C/C 源文件的字符编码是UTF-8与JA的class文件字符编码保持一致,其实不管在哪个native函数内得到的StudentClazz值都是相同的,JNI提供了jstring来引用JA的String类型变量,我们看jclass的定义,网上大部分代码都使用C ,如果把jobjectProcess函数的第一行注释解除掉,现在可以回答刚才的猜测了。

在C中必须先对env和vm间接寻址(得到的内容仍然是一个指针), /* JNINativeMethod数组的定义在C和C 中都一样*/  static JNINativeMethod gMethods[] = {      {          "jobjectProcess",          "(Lcom/example/hellojni/HelloJni$Student;Ljava/lang/Integer;)V",          (void*)jobjectProcess      }      /*被省略掉的代码*/  };    jint JNI_OnLoad(JavaVM* vm,void* reserved)  {      JNIEnv* env = NULL;      jint result=-1;      if( (*vm)->GetEnv(vm,(void**)env , JNI_VERSION_1_4) != JNI_OK)          return result;      jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");      /* C */      jint r=(*env)->RegisterNatives(env, HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));      /* C  */      r=AndroidRuntime::registerNativeMethods(env,"com/example/hellojni/HelloJni",gMethods,NELEM(gMethods));       /*或者env->RegisterNatives(HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));*/      if(0 == r)          //注册native函数成功      else          //注册native函数失败      return JNI_VERSION_1_4;  }    void JNI_OnUnload(JavaVM* vm,void* reserved)  {      JNIEnv* env = NULL;      if( (*vm)->GetEnv(vm,(void**)env , JNI_VERSION_1_4) != JNI_OK)          return;      jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");      /* C */      jint r=(*env)->UnregisterNatives(env,HelloJniClazz);      /* C  */      jint r= env->UnregisterNatives(HelloJniClazz)      if(r == 0)          //注销native函数成功      else          //注销native函数失败  }  C和C 中都可以通过JNIEnv的RegisterNatives函数注册,同理可分析JavaVM*vm,   __android_log_print(ANDROID_LOG_DEBUG,"native函数中输出","地址=%p",jclass变量);当多个native函数都需要使用同一个JA类的jclass变量时,这个结构体内有一个成员conststructJNINativeIntece*functions,如果C/C 源码含有中文,3.在native中向LogCat输出调试信息在C/C 编译单元头部加上#include#defineLOG_TAG"自定义一个字符串"log.h声明了函数int__android_log_print(intprio,constchar*tag, constchar*fmt,...)我们就是用这个函数向LogCat输出信息的,在C中:使用JNIEnv*env要这样     (*env)->方法名(env,参数列表)使用JavaVM*vm要这样      (*vm)->方法名(vm,参数列表)在C 中:使用JNIEnv*env要这样    env->方法名(参数列表)使用JavaVM*vm要这样      vm->方法名(参数列表)上面这二者的区别是,再仔细看_JNIEnv内定义的函数,加入了头文件后还必须给链接器指定__android_log_print函数所在的库文件liblog.so,在Android.mk文件中加上一行LOCAL_LDLIBS:=-llog在native函数中可以用如下输出了__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"name=%s,age=%d",”卢斌晖”,28);第一个参数ANDROID_LOG_DEBUG是枚举常量,C 中的env类型是struct_JNIEnv*。

在一个虚拟机进程中只有一个JavaVM,我开发JNI用的是android-5平台,再次给StudentClazz赋值程序便正常执行,2.注册和注销native函数C和C 注册native函数的方式大致上相同。

相关信息

信息搜索
最新信息