美文网首页Android音视频系列Android与FFmpeg
FFmpeg4Android:jni中c/c++调用java

FFmpeg4Android:jni中c/c++调用java

作者: walker不抽烟 | 来源:发表于2018-07-02 14:19 被阅读10次

    7 FFmpeg4Android:jni中c/c++调用java

    7.1 c/c++访问java属性

    先来看一个函数定义:

    JNIEXPORT jstring JNICALL Java_com_test_jni_TestNative_stringFromJNI
      (JNIEnv * env, jobject jobj) {
        return (*env)->NewStringUTF(env, "jni development.");
    }
    

    参数说明:
    1)env:是一个结构体指针的指针,主要用来在C/C++中使用虚拟机的功能,比如说:访问Java方法、属性、创建Java对象、处理字符串等等。
    2)jobj:是代表对象或类的结构体
    如果native方法不是静态方法,jobj代表该方法所属的java对象
    如果native方法是静态方法,jobj代表该方法所属Java类的class对象 //TestNative.class

    c与java数据类型对应关系
    Java基本数据类型与JNI数据类型的映射关系(在C/C++中用特定的类型来表示Java的数据类型),在反射访问java中属性时需要指定属性的签名,三者的对应关系如下:

    java类型 c/c++类型 签名
    boolean jboolean Z
    byte jbyte B
    char jchar C
    short jshort S
    int jint I
    long jlong L
    float jfloat F
    double jdouble D
    void void V

    引用类型(对象)

    java类型 c/c++类型 签名
    String jstring Ljava/lang/String;
    Object jobject Ljava/lang/Object;
    byte[] jByteArray [B
    Class jclass

    对象类型以L开头,然后/分隔包的完整类型,后面再加";"。
    数组类型以[开头,在加上数据元素类型的签名,如int[],签名就是[I;再比如int[][],签名就是[[I,object数组签名就是[Ljva/lang/Object;。

    7.1.1 访问一般属性

    如有java类JniTest:

    package com.lzp.jni;
    
    import java.util.Random;
    import java.util.UUID;
    
    public class JniTest {
    
        public String key = "walker";
        
        public static int count = 9;
        
        public native static String getStringFromC();
        
        public native String getString2FromC(int i);
        // 访问属性,返回修改之后的属性内容
        public native String accessField();
        
        public native void accessStaticField();
        
        public native void accessMethod();
        
        public native void accessStaticMethod();
        
        public static void main(String[] args) {
            String text = getStringFromC();
            System.out.println(text);
            JniTest t = new JniTest();
            text = t.getString2FromC(6);
            System.out.println(text);
            
            System.out.println("key修改前:"+t.key);
            t.accessField();
            System.out.println("key修改后:"+t.key);
            
            System.out.println("count修改前:"+count);
            t.accessStaticField();
            System.out.println("count修改后:"+count);
            
            t.accessMethod();
            t.accessStaticMethod();
        }
        
        // 产生指定范围的随机数
        public int genRandomInt(int max){
            System.out.println("genRandomInt 执行了...");
            return new Random().nextInt(max); 
        }
        
        // 产生UUID字符串
        public static String getUUID(){
            return UUID.randomUUID().toString();
        }
        
        // 加载动态库
        static{ 
            System.loadLibrary("jni_study");
        }
    }
    
    

    我们想在jni中访问该类中的属性key,并修改。

    // 修改属性key的字符串
    JNIEXPORT jstring JNICALL Java_com_tz_jni_TestNative_accessField(JNIEnv * env, jobject obj){
        //得到class
        jclass cls = (*env)->GetObjectClass(env, obj);
        //jfieldID
        //签名:类型的简称
        //属性,方法
        jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
    
        //获取key属性的值
        //注意:key为基本数据类型,规则如下
        //(*env)->GetIntField(); (*env)->Get<Type>Field();
        jstring jstr = (*env)->GetObjectField(env, obj, fid);
    
        //jstring转为 C/C++字符串
        char *str = (*env)->GetStringUTFChars(env, jstr, NULL);
        //拼接字符串
        char text[50] = "super ";
        strcat(text,str);
    
        //拼接完成之后,从C字符串转为jstring
        jstr = (*env)->NewStringUTF(env, text);
    
        //修改key的属性
        //注意规则:Set<Type>Field
        (*env)->SetObjectField(env, obj, fid, jstr);
    
        return jstr;
    }
    

    7.1.2访问静态属性

    //count属性+10
    JNIEXPORT void JNICALL Java_com_tz_jni_TestNative_accessStaticField(JNIEnv * env, jobject obj) {
        //jclass
        jclass cls = (*env)->GetObjectClass(env, obj);
        //jfieldID
        jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");
        //获取静态属性的值
        //规则:GetStatic<Type>Field
        jint count = (*env)->GetStaticIntField(env, cls, fid);
        count += 10;
        //修改属性的值
        //规则:SetStatic<Type>Field
        (*env)->SetStaticIntField(env, cls, fid, count);
    }
    

    7.2 c/c++访问java方法

    回顾一下java反射,一般分为3个步骤:
    1)加载calss(字节码),获取class的对象;
    2)获取对应的方法或属性;
    3)修改属性,或执行方法。
    例如:
    有java类Reflect:

    public class Reflect {
        public void print(String s) {
            System. out.println(s);
        }
    }
    

    另一个Test类来反射此类,执行print(String)方法:

    public class Test {
        public static void main(String[] args) {
            try {
                Class clazz = Test.class.getClassLoader().loadClass("Reflect");
                Method method = clazz.getDeclaredMethod("print", new Class[] {String.class});
                method.invoke(clazz.newInstance(), new String[] {"java反射"});
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    控制台输出:java反射

    接下来看c/c++中的反射。
    而对一个方法,其签名就是其参数类型签名和返回值类型签名的字符串,其形式如下:
    **(类型签名1类型签名2...)返回值类型签名 **

    下面看看两个例子:
    方法 1):public string addTail(String tail, int index)
    方法签名为:(Ljava/util/String;I)Ljava/util/String;
    方法 2):public int addValue(int index, String value,int[] arr)
    方法 签名为:(ILjava/util/String;[I)I

    也可以通过命令,获取属性与方法签名。在class文件夹中,输入:javap -s -p 类名
    1)访问java方法

    // 借用java的api产生指定大小的随机数
    JNIEXPORT void JNICALL Java_com_tz_jni_TestNative_accessMethod(JNIEnv * env, jobject obj) {
        //jclass
        jclass cls = (*env)->GetObjectClass(env, obj);
        //jmethodID
        jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
        //调用方法,产生了一个随机数
        //规则:Call<Type>Method 返回值类型
        jint random = (*env)->CallIntMethod(env, obj, mid, 200);
    
        //打印出来看看
        //FILE *fp = fopen("D://log.txt", "w");
        //int转为字符串
        char str[50];
        sprintf(str, "%d", random);
        fputs(str, fp);
        fclose(fp);
    }
    

    2)访问静态方法

    //借用java api 产生一个UUID字符串,作为文件的名称
    JNIEXPORT void JNICALL Java_com_tz_jni_TestNative_accessStaticMethod(JNIEnv * env, jobject cls){    
        //如果native方法为static,jobject为子类jclass的实例,也就是native方法所属的类的Class实例
        //jclass
        //jclass cls = (*env)->GetObjectClass(env, obj);
    
        //jmethodID
        jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;"); 
    
        //调用
        //规则:CallStatic<Type>Method
        jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid);
        //jstring转为C字符串
        char *uuid_str = (*env)->GetStringUTFChars(env, uuid, NULL);
        char filename[100] = {0};
        sprintf(filename, "D://%s.txt", uuid_str);
        FILE *fp = fopen(filename, "w");
        fputs(uuid_str, fp);
        fclose(fp);
    }
    

    3)访问构造方法

    //使用java.util.Date产生一个当前时间时间戳
    JNIEXPORT jobject JNICALL Java_com_tz_jni_TestNative_accessConstructor(JNIEnv * env, jobject obj){
        //Date jclass
        jclass cls = (*env)->FindClass(env, "java/util/Date");
        //构造方法jmethodID
        jmethodID contructor_mid = (*env)->GetMethodID(env, cls, "<init>","()V");
        //实例化一个Date对象
        jobject date_obj = (*env)->NewObject(env, cls, contructor_mid);
    
        //调用getTime方法
        jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");
        jlong time = (*env)->CallLongMethod(env, date_obj, mid);
        //jlong -> 字符串
        FILE *fp = fopen("D://log.txt", "w");
        char str[50];
        sprintf(str, "%lld", time);
        fputs(str, fp);
        fclose(fp);
    
        return date_obj;
    }
    

    4)调用父类的方法

    JNIEXPORT void JNICALL Java_com_tz_jni_TestNative_callNonvirtualMethod(JNIEnv * env, jobject obj) {
        //获取一个Man对象
        jclass cls = (*env)->GetObjectClass(env, obj);
        jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/tz/jni/Human;");
        jobject human_obj = (*env)->GetObjectField(env, obj, fid);
    
        //执行sayHi方法
        jclass human_cls = (*env)->FindClass(env, "com/tz/jni/Human");//注意:传父类的类名
        jmethodID mid = (*env)->GetMethodID(env, human_cls, "sayHi", "()Ljava/lang/String;");
        
        //执行子类覆盖的方法
        //jstring jstr = (*env)->CallObjectMethod(env, human_obj, mid);
        //执行父类的方法
        jstring jstr = (*env)->CallNonvirtualObjectMethod(env, human_obj, human_cls, mid);
    
        //jstring->char*
        char * str = (*env)->GetStringUTFChars(env, jstr, NULL);
        FILE *fp = fopen("D://log.txt", "w");
        fputs(str, fp);
        fclose(fp);
    }
    

    7.3 实战:中文字符串乱码

    JNIEXPORT jstring JNICALL Java_com_tz_jni_TestNative_chineseChars(JNIEnv * env, jobject obj,jstring jstr) {
        //java中传入的中文->C字符串
        /*char * str = (*env)->GetStringUTFChars(env, jstr, NULL);
        FILE *fp = fopen("D://log.txt", "w");
        fputs(str, fp);
        fclose(fp);*/
    
        //C字符串->jstring
        char *cstr = "我说中文";
        //jstring j_str = (*env)->NewStringUTF(env, cstr);
        //借用Java的转码API,返回GB2312中文编码字符串
        //使用这个构造方法,完成转码
        //public String(byte bytes[], String charsetName)
        //执行这个构造方法需要三个东西
        //1.jmethodID
        //2.byte数组(jbyteArray)参数
        //3.charsetName参数(jstring)
    
        //String类的jclass
        jclass str_cls = (*env)->FindClass(env, "java/lang/String");
        jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V");
        
        //char * -> char[] ->jbyteArray -> jbyte * 
        jbyteArray bytes = (*env)->NewByteArray(env, strlen(cstr));
        //bytes数组赋值
        (*env)->SetByteArrayRegion(env, bytes, 0, strlen(cstr), cstr);
    
        //charsetName="GB2312"
        jstring charsetName = (*env)->NewStringUTF(env, "GB2312");
    
        //返回GB2312中文编码jstring
        return (*env)->NewObject(env, str_cls, constructor_mid, bytes, charsetName);
    }
    

    相关文章

      网友评论

        本文标题:FFmpeg4Android:jni中c/c++调用java

        本文链接:https://www.haomeiwen.com/subject/ogqluftx.html