Java枚举和C枚举的转换——JNI笔记

前言

       最近在做中间件相关,遇到一个JAVA和C之间枚举值通过JNI传递的问题。我们知道C语言的枚举型是一个集合,其每个枚举成员是一个整型常量;而JAVA的枚举其本质是一个类,继承自Enum类,每个枚举成员是枚举类型常量。他们之间是不同的两种数据类型,不能直接进行转换。Java枚举和C枚举的转换本质是Java枚举类型和int整型的转换。

 

 两种枚举的特点

 

新创建的枚举类型继承自Enum类。

Enum类有两个成员变量,name和ordinal。name为枚举常量的名字,ordinal为枚举常量声明时的顺序(从0开始)。

Enum类有一个ordinal()方法,用来获取变量ordinal的值;还有一个values()方法用来获取所有的枚举常量,返回值是一个枚举的数组。

JAVA的枚举类型构造函数一定是私有的,因为要保证枚举是单例的。因此我们不能在JNI里使用AllocObject、NewObject这种需要用到构造函数的方法构建一个枚举型。

JAVA枚举类型的成员都是static的,JNI中可以直接访问

C的枚举型中第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。

 

 一个例子

       知道了两种枚举的特点,我们就可以开始进行转换了。下面我们通过一个例子说明如何进行转换:

       我的老板有个三岁的女儿,她很喜欢吃水果,她知道苹果是红色的,香蕉是黄色的。可是别的水果呢?于是她希望有个万能的小精灵告诉她水果的颜色。于是老板找到了我……

       啥也不说了,开始吧。先定义两个C枚举,用来描述水果和颜色,它们是底层沟通信息用的。

typedef enum {
    FRUIT_APPLE,
    FRUIT_GRAPE,
    FRUIT_BANANA,
    FRUIT_MAX
}FRUIT;

typedef enum {
    COLOR_RED,
    COLOR_PURPLE,
    COLOR_YELLOW,
    COLOR_MAX
}COLOR;

然后定义两个JAVA枚举,也用来表示水果和颜色,应用间聊天的时候会用到它们进行描述。


public enum EnFruit {
    APPLE,
    GRAPE,
    BANANA,
    FRUIT_MAX;
}

public enum EnColor {
    RED,
    PURPLE,
    YELLOW,
    COLOR_MAX;
}

很好,我们离成功又进了一步。

现在我们老板的女儿问出了第一个问题:葡萄什么颜色的?

水果颜色识别器:什么?什么水果??什么是葡萄??

应用:就是这个!!这个就是!!!

两位大佬语言不通,鸡同鸭讲,吵成一团。

于是JNI大哥来帮忙。

extern "C" jobject JNICALL Java_com_example_jnitest_MainActivity_getFruitColor(JNIEnv *env, jobject object, jobject objFruit){

    jclass clazzFruit = env->FindClass("com/example/jnitest/EnFruit"); 
    jmethodID j_method_fruitOrdinal = env->GetMethodID(clazzFruit,"ordinal","()I");
    int fruit = env->CallIntMethod(objFruit,j_method_fruitOrdinal);
    COLOR color = get_fruit_color((FRUIT)fruit);//自定义水果颜色识别器,识别水果颜色
    
}
  • 他首先通过FindClass查找Java中代表水果的类,这个方法的参数是类的全名。使用UTF-8编码,其中分隔符使用/表示。
  •  然后通过GetMethodID方法获取水果类的一个实例方法ID。该方法的第一个参数是一个Java类;第二个参数是UTF-8编码的方法名;第三参数是UTF-8编码的方法签名。
  • 这里需要注意,GetMethodID获取的方法是非静态的,需要类的实例才能调用它。接下来的CallIntMethod方法就是通过一个类的实例调用了ordinal方法。Call<type>Method这一系列方法的返回值是type;第一个参数就是类的实例(这里类的实例是由Java直接传给JNI的objFruit);第二个参数就是上一步中获取的实例方法ID;后面是可变参数列表。这里的ordinal方法没有参数,故不填写。

现在终于把应用说的水果翻译成水果颜色识别器能看懂的话,识别器很高兴。

水果颜色识别器:这个水果是紫色啦!

应用:紫色?眼前的黑不是黑,你说的紫是什么紫?

语言不通真是烦恼,于是JNI大哥又忙活起来。当然最后不要忘记DeleteLocalRef释放资源,把翻译用的草稿本还回去留给别人继续用。

extern "C" jobject JNICALL Java_com_example_jnitest_MainActivity_getFruitColor(JNIEnv *env, jobject object, jobject objFruit){

    jclass clazzFruit = env->FindClass("com/example/jnitest/EnFruit");
    jmethodID j_method_fruitOrdinal = env->GetMethodID(clazzFruit,"ordinal","()I");
    int fruit = env->CallIntMethod(objFruit,j_method_fruitOrdinal);
    COLOR color = get_fruit_color((FRUIT)fruit);//自定义水果颜色识别器,识别水果颜色

    jclass clazzColor = env->FindClass("com/example/jnitest/EnColor");
    jobject objColor;
    jfieldID j_fieldID_RED = env->GetStaticFieldID(clazzColor,"RED","Lcom/example/jnitest/EnColor;");
    jfieldID j_fieldID_PURPLE = env->GetStaticFieldID(clazzColor,"PURPLE","Lcom/example/jnitest/EnColor;");
    jfieldID j_fieldID_YELLOW = env->GetStaticFieldID(clazzColor,"YELLOW","Lcom/example/jnitest/EnColor;");

    switch (color){
        case COLOR_RED:
            objColor = env->GetStaticObjectField(clazzColor,j_fieldID_RED);
            break;
        case COLOR_PURPLE:
            objColor = env->GetStaticObjectField(clazzColor,j_fieldID_PURPLE);
            break;
        case COLOR_YELLOW:
            objColor = env->GetStaticObjectField(clazzColor,j_fieldID_YELLOW);
            break;
        default:
            objColor = nullptr;
    }

    env->DeleteLocalRef(clazzFruit);
    env->DeleteLocalRef(clazzColor);
    return objColor;
}
  • 还是通过 FindClass查找Java中代表颜色的类。
  • 由于Java的enum成员都是静态的,我们通过GetStaticFieldID获取颜色类的静态成员ID。它的第一个参数是一个Java类;第二个参数是使用UTF-8编码的属性名;第三个参数是使用UTF-8编码的属性类型签名。
  • 最后我们就可以通过类的GetStaticObjectField方法获取一个静态的成员,这个方法可以获得一个类的实例。他的第一个参数是一个Java类;第二个参数是这个静态成员的ID。

上面的方法虽然可行,但是在旁边的JNI二哥看不下去了,还没等大哥写完演算,就一把抢过来,在EnColor的枚举里自定义了一个static方法,获取指定顺序的枚举。改成了下面的样子。

public enum EnColor {
    RED,
    PURPLE,
    YELLOW,
    COLOR_MAX;

public static EnColor getColor(int ordinal){
        EnColor colors[] = values();
        if(ordinal < colors.length && ordinal >= 0)
            return colors[ordinal];
        return null;
    }
}

 对应的JNI如下修改,顿时清爽了很多。

extern "C" jobject JNICALL Java_com_example_jnitest_MainActivity_getFruitColor(JNIEnv *env, jobject object, jobject objFruit){

    jclass clazzFruit = env->FindClass("com/example/jnitest/EnFruit");
    jmethodID j_method_fruitOrdinal = env->GetMethodID(clazzFruit,"ordinal","()I");
    int fruit = env->CallIntMethod(objFruit,j_method_fruitOrdinal);
    COLOR color = get_fruit_color((FRUIT)fruit);//自定义水果颜色识别器,识别水果颜色

    jclass clazzColor = env->FindClass("com/example/jnitest/EnColor");
    jmethodID j_method_getColor = env->GetStaticMethodID(clazzColor,"getColor","(I)Lcom/example/jnitest/EnColor;");
    jobject objColor = env->CallStaticObjectMethod(clazzColor,j_method_getColor,(int)color);

    env->DeleteLocalRef(clazzFruit);
    env->DeleteLocalRef(clazzColor);
    return objColor;
}
  • 修改之后我们通过GetStaticMethodID获得一个静态方法ID。该方法与GetMethodID方法的参数都是一样的。
  • 然后我们通过CallStaticObjectMethod调用了一个我们自定义的静态方法,返回值是一个类的实例。CallStatic<type>Method这一类方法第一个参数是一个Java类,它不像Call<type>Method的方法那样需要类的实例;其余的参数和Call<type>Method方法是一样的。

这里有个小问题,我们在JNI里拿到的objColor并没有释放,不会造成内存泄漏的问题吗?首先,我们当然不能释放他,因为JAVA里还需要用到它。其次,Java是个好孩子,他最后会把从JNI里拿到的对象释放掉的,我们完全不用担心。

好了,现在应用可算知道识别器说的是哪个紫色了,赶紧把这个结果告诉我们老板的女儿。大功告成!

老板女儿:可是昨天我们吃的葡萄不是紫色的呀?爸爸你骗我,你骗我。(大哭)

老板(怒):你明天不用来上班了!

                                            

摔!!!

 


参考文献:

枚举的本质

java enum(枚举)使用详解 + 总结

C语言中的enum(枚举)用法

JNI完全指南(二)——类与异常

JNI完全指南(四)——对象操作

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页