Android JNI编程指南

本文深入解析C++与Java之间的交互机制,详细阐述了如何在C++中注册JNI函数,如何在Java中调用这些函数,并且如何在Java类中访问并操作JNI中的变量。内容涵盖JNI函数的注册、Java函数的调用、变量的传递与访问等关键点,同时介绍了数组和对象的使用方法,以及如何在不同线程中访问Java函数和变量。
摘要由CSDN通过智能技术生成
1 JNI注册:
   C语言部分:  
   int register_android_Test(JNIEnv* env)
   {
          return jniRegisterNativeMethods(env, "XXX/XXX/XXX", sMethod, NELEM(sMethod));
   }   
   extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
   {
        JNIEnv* env = NULL;
        jint result = -1;
      
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) 
        {
                return result;
        }
        register_android_Test(env);
     
        return JNI_VERSION_1_4;
   }
   Java部分代码
    static 
    {
         try 
         {
               System.loadLibrary("XXX");           
         }
         catch (UnsatisfiedLinkError ule) 
         {
          
                     Log.e("Test JNI", "WARNING: Could not load JNI :" + ule.toString());
            
          }
    }
   在JAVA 中调用System.loadLibrary,系统会调用JNI库中定义的JNI_OnLoad,有JNI_OnLoad完成JNI函数的注册。
   System.loadLibrary函数的参数是JNI库的文件名,但是不包括前面的lib和扩展名,例如我们的JNI库的文件libabcd.so,那么就要调用System.loadLibrary("abcd");
   JNI库通常保存在目标机的system/lib下或者放在eclips工程下的libs\armeabi目录中。
   jniRegisterNativeMethods完成JNI函数在JAVA中的映射,JNI函数是通过一个JNINativeMethod类型的数组来描述的。
   jniRegisterNativeMethods函数的第二个参数是要映射的JAVA类的包名+类名。
   例如定义了下面的类
   package com.Android;
   public class T1   
   {
            .......
   }
   那么第二个参数就是"com/android/T1" ,这个名字不能搞错了,否者JAVA调用loadLibrary会报异常的。
    
2   JAVA类中访问JNI函数。
    Andorid使用了JNINativeMethod描述了函数的参数和返回值,实现C到JAVA的映射
    JNINativeMethod结构描述如下:
    typedef struct {
           const char* name;
           const char* signature;
           void* fnPtr;
    } JNINativeMethod;
    第一个变量name是Java中函数的名字。
    第二个变量signature,用字符串是描述了函数的参数和返回值
    第三个变量fnPtr是函数指针,指向C函数。
 
 其中比较难以理解的是第二个参数,例如
 "()V"
 "(II)V"
 "(Ljava/lang/String;Ljava/lang/String;)V"
 实际上这些字符是与函数的参数类型和返回值一一对应的。
    "()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();
 "(II)V" 表示 void Func(int, int);
  具体的每一个字符的对应关系如下
       字符    Java类型      C类型
          V        void               void
          Z       jboolean        boolean
           I        jint                  int
          J       jlong               long
          D       jdouble          double
          F       jfloat                float
          B       jbyte                byte
          C       jchar               char
          S       jshort              short


 数组则以"["开始,用两个字符表示
         字符     Java类型            C类型
             [I       jintArray                int[]
             [F       jfloatArray           float[]
            [B       jbyteArray           byte[]
             [C       jcharArray         char[]
             [S       jshortArray        short[]
            [D       jdoubleArray     double[]
            [J       jlongArray           long[]
            [Z       jbooleanArray   boolean[]


    上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。
    而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring


 Ljava/lang/String; String 类 
 Ljava/net/Socket; Socket 类


 如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。


 例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z" 
 FileStatus就是在FileUtils类的内部定义的一个类


    下面举个例子
    在C代码中
   static jint android_init(JNIEnv* env, jobject obj) 
   {
              return 0;
   }
   static jboolean android_close(JNIEnv* env, jobject obj) 
   {
            return true;
   }
   static jboolean android_Test(JNIEnv* env, jobject obj,jbyte para1,jbyte para2 , jbyte para3 )
   {
             return true;
   }
   static JNINativeMethod sMethod[] = 
   {
              /* name,       signature, funcPtr */
             {"native_init", "()I", (void*)android_init},
             {"native_close", "()Z", (void*)android_close},
            {"native_Test", "(BBB)Z", (void*)android_Test},
   };
   int register_android_Test(JNIEnv* env)
   {
              return jniRegisterNativeMethods(env, "com/android/test/mytest", sMethod, NELEM(sMethod));
   }
   register_android_Test函数在 JNI_OnLoad里面调用。
 
 JAVA代码中
        package com.android.test; 
        public class mytest       
        {
                static {
                        try { 
                                System.loadLibrary("test_jni"); //加载JNI库“libtest_jni.so”,这个库就是上面C代码编译出来的库
                        }
                        catch (UnsatisfiedLinkError ule) {
                                Log.e("JNI", "WARNING: Could not load libtest_jni.so :" + ule.toString());
                        }
                }
                public  native int native_init( ); //注意JNI函数一定要加上native修饰
                public  native boolean native_close( );
                public  native boolean native_Test(byte group,byte bit , byte data );
        }
    在JAVA代码中要使用这些JNI函数,就直接new一个类调用就可以了,代码如下:
    mytest myjniclass =new mytest;
    myjniclass.native_init();
    myjniclass.native_Test(1,2,3);
    myjniclass.native_close();
    
3   JNI中回调JAVA函数
    3.1 JNI中回调JAVA类的静态函数
    例如 前面的JAVA类中定义了一个静态函数
    public static void TestStaitc(   )
    {
      
    }
    在C代码中首先定义jmethodID和jclass类型的全局变量
    static jclass gClazz;
    static jmethodID method_TestStaitc;
    在register_android_Test函数中增加下面的代码
    jclass clazz = env->FindClass("com/android/test/mytest");
    gClazz = jclass(env->NewGlobalRef(clazz));
     method_TestStaitc = env->GetStaticMethodID(clazz,"TestStaitc","()V");
 
 在回调的地方
 env->CallStaticVoidMethod(gClazz,method_TestStaitc);
 就大功告成了!
 
 当然了上面对gClazz和method_TestStaitc初始化的代码也可以放在回调的地方调用,但是因为JAVA函数可能会多次调用,放在注册的时候调用可以减少系统的开销。
 env->FindClass("com/android/test/mytest");返回的是一个局部引用,局部引用在本地方法返回时会自动释放,所以我们要调动gClazz = jclass(env->NewGlobalRef(clazz));创建一个全局引用,这个引用不会自动释放,我们在不需要这个引用的时候要显示调用env->DeleteGlobalRef(gClazz)来释放。
 (注:除了FindClass,还有下面的函数也可以获取类的引用
       jclass GetObjectClass(jobject obj)   根据一个对象,获取该对象的类
              jclass GetSuperClass(jclass obj)     获取一个类的父类)


 GetStaticMethodID 是返回静态方法的jmethodID,注意这个函数的第一参数一定是类的引用(jclass ),而不是对象的引用。第二个参数是JAVA中定义的函数名,第三个参数是JAVA函数的参数和返回值
        同样道理CallStaticVoidMethod的第一个参数也必须是jclass类型,否者会报异常。 
    CallStaticVoidMethod 是调用无返回值的静态方法,JNI中还有许多其他返回类型的方法,如下:
    CallStaticObjectMethod //返回一个JAVA的对象
    CallStaticBooleanMethod
    CallStaticByteMethod
    CallStaticCharMethod
    CallStaticShortMethod
    CallStaticIntMethod
    CallStaticLongMethod
    CallStaticFloatMethod
    CallStaticDoubleMethod
    CallStaticVoidMethod
    
    例如:如果JAVA函数原型为public static int TestStaitc(  int para );
    那么就调用
    jint ret = env->CallStaticIntMethod(gClazz,method_TestStaitc,1);
    这里注意一个参数一定要是类的引用,而不是对象的引用。
    
    3.2 JNI中回调JAVA类的非静态函数
    例如 前面的JAVA类中定义了一个静态函数
    public   void Test(   )
    {
      
    }
    在C代码中首先定义jmethodID 的全局变量
    
    static jmethodID method_Test ;
    在register_android_Test函数中增加下面的代码
    jclass clazz = env->FindClass("com/android/test/mytest"); 
    method_Test = env->GetMethodID(clazz,"Test","()V");
 
 在回调的地方
 env->CallVoidMethod(obj,method_Test);
 就大功告成了!
 这里要注意,obj的对象的引用(jobject),而不是类的引用(jclass);
 jobject是通过JNI函数的第二个参数传递进来的,如果要创建一个全局的对象,可以调用gObject = env->NewGlobalRef(obj);
 同样的除了CallVoidMethod ,JNI中还有许多其他返回类型的方法
 CallObjectMethod //返回一个JAVA的对象
    CallBooleanMethod
    CallByteMethod
    CallCharMethod
    CallShortMethod
    CallIntMethod
    CallLongMethod
    CallFloatMethod
    CallDoubleMethod
    CallVoidMethod
    3.3 JNI调用Java方法不在一个类中。
    前面介绍的都是JNI和JAVA在同一个类中,如果JNI和JAVA不在一个类中,如何访问呢?
    例如我们要访问 adc/def包里面的test类的函数,的 void test(void) 函数 可以用下面的代码
    jclass clazz = env->FindClass("adc/def/test");
    jmethodID method1 = env->GetMethodID(clazz,"test","()V");
    jobject obj= env->AllocObject(clazz);//这里创建一个对象实例,如果是静态函数,就不需要创建对象的示例了
    env->CallVoidMethod(obj,method1); //调用函数,如果是静态函数,就应该是env->CallStaticVoidMethod(clazz,method1);前面的method1 = env->GetStaticMethodID(clazz,"test","()V");
    AllocObject 仅仅是分配了一个test的对象,但是不会调用这个类的构造函数,那么对于需要构造函数的类该怎么处理呢?
    首先需要获得构造函数的jmethodID
    jmethodID methodInit = env->GetMethodID(clazz,"<init>","()V"); 注意这里的第二个参数不是构造函数的名字,而是<init>
    然后创建对象
    jobject objtest = env->NewObject( clazz,methodInit);
    如果构造函数需要参数,就按参数的顺序依次加载methodInit的后面,就好了。
    注意AllocObject和NewObject创建的都是局部引用,在方法结束后可能会被回收。如果要在其他的方法中使用,需要创建全局引用gObject = env->NewGlobalRef(objtest);
    对于全局引用,在不需要再使用的时候,要显示的调用env->DeleteGlobalRef(gObject);
    对于局部引用,可以不用显示的释放。也可以在函数结束时调用env->DeleteLocalRef(Object);释放
  
4   JNI中访问JAVA的变量
    对于JNI中访问JAVA变量的方法和函数类似。
    首先需要定义一个jfieldID。
    jfieldID   field; 
    JNI提供了两个函数 GetFieldID和GetStaticMethodID分别获取非静态变量和静态变量的jfieldID
    然后用上面两个函数初始化的jfieldID变量。
    
    获取或者设置这些变量的值可以用下面的函数
    void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
    void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
    void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
    void SetCharField(jobject obj, jfieldID fieldID, jchar value)
    void SetShortField(jobject obj, jfieldID fieldID, jshort value)
    void SetIntField(jobject obj, jfieldID fieldID, jint value)
    void SetLongField(jobject obj, jfieldID fieldID, jlong value)
    void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
    void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
    
    void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
    void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)
    void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value)
    void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value)
    void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value)
    void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
    void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value)
    void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value)
    void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value)
    
    jobject GetObjectField(jobject obj, jfieldID fieldID)
    jboolean GetBooleanField(jobject obj, jfieldID fieldID)
    jbyte GetByteField(jobject obj, jfieldID fieldID)
    jchar GetCharField(jobject obj, jfieldID fieldID)
    jshort GetShortField(jobject obj, jfieldID fieldID)
    jint GetIntField(jobject obj, jfieldID fieldID)
    jlong GetLongField(jobject obj, jfieldID fieldID)
    jfloat GetFloatField(jobject obj, jfieldID fieldID)
    jdouble GetDoubleField(jobject obj, jfieldID fieldID)
    
    jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
    jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
    jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
    jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
    jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
    jint GetStaticIntField(jclass clazz, jfieldID fieldID)
    jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
    jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
    jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)
    
    下面举例说明如何使用,例如前面的mytest类里面定义了两个变量
    int i;
    String Message;
    JNI C代码
    定义两个全局变量
    jfieldID mStringMessageId;
    jfieldID nIntiId;
    在register_android_Test函数中增加下面的代码
    jclass clazz = env->FindClass("com/android/test/mytest");
    nIntiId = env->GetFieldID(clazz, "i", "I");
 mStringMessageId = env->GetFieldID(clazz, "Message", "Ljava/lang/String;");
 
 获取变量的值
 int i = GetIntField(obj,nIntiId);
 jstring Messagestr = jstring(env->GetObjectField(obj, mStringMessageId));
 设置变量
 GetIntField(obj,nIntiId,5);
 char tempbuf[10];
 sprintf(tempbuf,"test" );
 env->SetObjectField(obj, mStringMessageId, env->NewStringUTF(tempbuf));
 对于访问JNI和JAVA不在同一个类的变量的方法,和3.3介绍的类似,这里就不详细描述了。
 
5 JNI参数的传递
  5.1 JNI类型描述
    Java类型        JNI本地类型        描述  
    boolean         jboolean           C/C++8位整型 
    byte                jbyte                   C/C++带符号的8位整型 
    char                jchar                 C/C++无符号的16位整型 
    short               jshort               C/C++带符号的16位整型 
    int                    jint                   C/C++带符号的32位整型 
    long                jlong                C/C++带符号的64位整型e 
    float                 jfloat             C/C++32位浮点型 
    double           jdouble         C/C++64位浮点型 
    Object            jobject            任何Java对象,或者没有对应java类型的对象 
    Class             jclass             Class对象 
    String             jstring            字符串对象 
    Object[]          jobjectArray       任何对象的数组 
    boolean[]       jbooleanArray      布尔型数组 
    byte[]              jbyteArray         带符号的8位整型数组 
    char[]             jcharArray         字符型数组 
    short[]           jshortArray        短整型数组 
    int[]                jintArray          整型数组 
    long[]             jlongArray         长整型数组 
    float[]             jfloatArray        浮点型数组 
    double[]        jdoubleArray       双浮点型数组 
    对于简单数据类型例如byte char int boolean等等只来的,直接使用就可以了
     
    5.2数组的使用:
    为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数,XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
    我们可以在C语言中操作这些指针了。操作完成功后ReleaseXXXArrayElements
    函数                                              Java 数组类型       本地类型  
 GetBooleanArrayElements        jbooleanArray       jboolean 
 GetByteArrayElements               jbyteArray                 jbyte 
 GetCharArrayElements           jcharArray                 jchar 
 GetShortArrayElements          jshortArray         jshort 
 GetIntArrayElements                jintArray           jint 
 GetLongArrayElements           jlongArray          jlong 
 GetFloatArrayElements          jfloatArray         jfloat 
 GetDoubleArrayElements         jdoubleArray        jdouble 
 
 ReleaseBooleanArrayElements      jbooleanArray       jboolean 
 ReleaseByteArrayElements         jbyteArray          jbyte 
 ReleaseCharArrayElements         jcharArray          jchar 
 ReleaseShortArrayElements        jshortArray         jshort 
 ReleaseIntArrayElements          jintArray           jint 
 ReleaseLongArrayElements         jlongArray          jlong 
 ReleaseFloatArrayElements        jfloatArray         jfloat 
 ReleaseDoubleArrayElements       jdoubleArray        jdouble 
 具体可以看下面的例子
        static jint Test(JNIEnv* env, jobject obj ,  jintArray buf )
        {
                jint* ptr= env->GetIntArrayElements(buf, 0 );
                jsize  len = env->GetArrayLength(buf);
                int i;
                for(i=0;i<len;i++)
                {
                        ALOGD("buf[%d]=%d",i,ptr[i] );
                        ptr[i]=9;
                }
                env->ReleaseIntArrayElements(buf, ptr, 0 );
                return 0;
        }
 执行完这个JNI函数后,JAVA传进来的数组的值就全部变为9了。
 上面看到的是数组座位参数传递进来,那么如何返回一个数组呢?
 我们可以调用NewXXXArray 创建一个数组,这个函数的参数就是要生成的数组的长度。然后return这个数组就可以了。如果需要给数组赋值,参考上面的代码GetIntArrayElements就可以了
 下面是例子:
        jintArray newbuf = env->NewIntArray(5);
        ptr= env->GetIntArrayElements(newbuf, 0 );
        for(i=0;i<5;i++)
        {
   
                ptr[i]=6;
        }
        env->ReleaseIntArrayElements(newbuf, ptr, 0 );
        return newbuf;
 对于其他数据类型的数组,调用相应的NewXXXArray就好了,把XXX换为相应的数据类型。
 
 5.3对象的使用
 通过使用之前介绍的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。
        FindClass 
        GetMethodID 
        AllocObject 
        CallXXXMethod (XXX为相应的数据类型)
        CallStaticXXXMethod(XXX为相应的数据类型)
        GetStaticMethodID 
        NewObject 
        GetFieldID
        GetStaticFieldID
        GetXXXField(XXX为相应的数据类型)
        SetXXXField(XXX为相应的数据类型)
    
 5.4 String类的使用
 其实String(jstring)也是数以一种对象类型,不过由于使用的频率非常高,所以JNI专门提供了一系列的函数进行操作。
 在JAVA中String类都是16为的unicode,而C\C++中都是8位的char型,两者之间如何转换呢?
 String到char型的转换:
 const char *ptr = env->GetStringUTFChars(stringname, NULL);
 jsize len =  GetStringUTFLength(stringname)  ;
 这里就可以对ptr进行处理了,注意,ptr的内容不能改变。
 处理完调用env->ReleaseStringUTFChars(stringname, ptr); 
 char型到String类型的转换
 jstring value = NULL;
 value = env->NewStringUTF(buf); //buf是char型的指针
 
6   JNI在线程中访问JAVA的函数和变量
    JNIEnv 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ; 而线程和应用的主线程不在同一个线程中,那么其他的线程中如何访问JAVA的函数和类呢?
    首先我们要在创建线程的JNI函数中初始化两个全局变量
    static jobject     gObject; 
    static JavaVM      *gs_jvm;
    gObject = env->NewGlobalRef(obj);
    env->GetJavaVM(&gs_jvm); 
    在线程的开始处
    JNIEnv *env1=NULL ;
    gs_jvm->AttachCurrentThread(&env1, NULL); 
    之后就可以参考前面的方法访问JAVA的函数和变量了
    在退出线程的地方调用
    gs_jvm->DetachCurrentThread(); 
    下面是示例代码:
        static jobject     gObject=NULL; 
        static JavaVM      *gs_jvm=NULL;
        static void * Test_thread( void*  arg )
        {
                JNIEnv *env1=NULL ;
                gs_jvm->AttachCurrentThread(&env1, NULL); 
                while(1)
                {
                        //访问JAVA中的函数和变量
                }
                gs_jvm->DetachCurrentThread(); 
        }
        static jint StartInterrupt(JNIEnv* env, jobject obj  )
        {
                pthread_t  threadId;
                if(gObject==NULL)
                {
                        gObject = env->NewGlobalRef(obj);     
                }
                if(gs_jvm==NULL)
                {
                        env->GetJavaVM(&gs_jvm); 
                }
                pthread_create( &threadId, NULL, Test_thread, NULL );
        }
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值