栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java jni(jni开发)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java jni(jni开发)

从Java到C++系列目录

前言 概念

本文中:

JNI方法:指JNI提供的一系列API。

native方法:跨native层调用的方法(Java->C/C++)。

C/C++方法:除native方法外,普通的C/C++方法。

native层:C/C++代码。

代码

示例代码:JNIInterface

测试代码:JNIInterfaceTest

摘要

本文主要内容如下:

加载so

native方法声明、定义

native与static native

静态注册与动态注册

Java元素定位

局部引用与全局引用

传递Java基本类型

传递Java对象

调用Java方法

处理Java方法调用异常

传递枚举

传递字符串

加载so

在Java层调用native方法,首先要加载so。加载so,主要通过下面两个方法,二者效果是一致的:

void load(String filename)
void loadLibrary(String libname)

以加载libjava2cpp.so为例:

load的参数是so文件的全路径名。APK安装后,在设备的/data/data/{packageName}/lib下可以看见APK里的so。
所以:

System.load("/data/data/com.example.java2cpp/lib/libjava2cpp.so");

loadLibrary的参数,是so文件的部分名称。如libjava2cpp.so,就是去掉前缀"lib",去掉后缀".so":

System.loadLibrary("java2cpp");

注意事项:

    加载so的代码,通常写在静态代码块里。好处是加载类时,静态代码块会优先执行。确保了在用户调用类的native方法之前,so已加载。
class JNIInterface {
    static {
        System.loadLibrary("java2cpp");
    }
}
native方法声明、定义

在C/C++中,声明和定义通常是分离的。声明一般在头文件(.h)中,定义一般在源文件(.cpp/.cc)中。

native方法的声明和定义也是如此。但是特别的是,定义是在Java源文件中的。

class JNIInterface {
    public native static String stringFromJNI();
}

native的定义则是在C/C++的源文件中。

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_java2cpp_JNIInterface_stringFromJNI(JNIEnv *env, jclass clazz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

注意事项:

    声明是在Java源文件中,需要遵守Java语法。没有方法体,并且需要用native修饰符来声明。定义是在C/C++的源文件中。需要遵守C/C++的语法。native方法声明和定义的参数个数不一样。定义总会比声明多两个参数:JNIEnv和jclass/jobject。native方法是不支持重载的。如,再声明一个stringFromJNI的重载方法是不可行的。
public native static String stringFromJNI(int x);
    推测:extern "C"导致了native方法无法重载。extern "C"会让编译器按照C语言的编译方式,为native方法生成符号表。而C语言是不支持重载的。
native与static native

native方法,可以声明为成员方法,也可以声明为静态方法

声明:

class JNIInterface {
    public native void jniMethod();
    public native static void staticJniMethod();
}

对应的实现:

extern "C" JNIEXPORT void JNICALL
Java_com_example_java2cpp_JNIInterface_jniMethod(JNIEnv *env, jobject thiz) {}

extern "C" JNIEXPORT void JNICALL
Java_com_example_java2cpp_JNIInterface_staticJniMethod(JNIEnv *env, jclass clazz) {}

两者的差异在于:

    成员方法对应的实现,第二个参数是jobject,代表JNIInterface的一个对象静态方法对应的实现,第二个参数是jclass,代表JNIInterface这个类
静态注册与动态注册

native方法,可以选择静态注册、动态注册两种方式。

下面将对如下两个native方法分别采用静态注册、动态注册:

public native void nativeJniMethod();

public native int nativeDynamicRegisterMethod();
静态注册

静态注册的native方法名,必须遵循一定的规则:

    extern "C" JNIEXPORT + 返回值 + JNICALLJava_ + 包名(_代替.) + _ + 类名 + _ + 方法名
extern "C" JNIEXPORT void JNICALL
Java_com_example_java2cpp_JNIInterface_nativeJniMethod(JNIEnv *env, jobject thiz) {}
动态注册

动态注册一般在JNI_OnLoad方法上进行。JNI_OnLoad方法会在System.loadLibrary加载so成功后,被虚拟机调用。

动态注册主要分为四步:

    实现native方法:dynamic_register_method。命名和普通的c++方法一样即可。

    在gMethods数组中,添加JNINativeMethod结构体(代表native方法声明与native方法实现映射关系),如:

{"nativeDynamicRegisterMethod", "()I", (void *) dynamic_register_method}
    通过FindClass来加载对应的Java类。通过RegisterNatives来进行动态注册。

后续需要新增native方法时,只需要重复前两个步骤即可。

#define JNIInterface_CLASS "com/example/java2cpp/JNIInterface"

static jint dynamic_register_method(JNIEnv *env, jobject thiz) {
    return 0;
}


static const JNINativeMethod gMethods[] = {
        {"nativeDynamicRegisterMethod", "()I", (void *) dynamic_register_method},
};

jint JNI_onLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = nullptr;
    jint result = JNI_ERR;
    if (vm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
        return result;
    }

    jclass c = env->FindClass(JNIInterface_CLASS);
    if (c == nullptr) {
        const char *msg = "Native registration unable to find class; aborting...";
        env->FatalError(msg);
    }

    int numMethods = sizeof(gMethods) / sizeof(gMethods[0]);
    //采用RegisterNatives进行动态注册
    if (env->RegisterNatives(c, gMethods, numMethods) < 0) {
        const char *msg = "RegisterNatives failed; aborting...";
        env->FatalError(msg);
    }
    return JNI_VERSION_1_6;
}

还可以对动态注册的方法进行反注册,一般在JNI_OnUnload进行,通过UnregisterNatives来反注册。

void JNI_onUnload(JavaVM *vm, void *reserved) {
    ...
    env->UnregisterNatives(c)
}
Java元素定位

在native方法中,无论是获取Java对象的变量、还是调用Java的方法等,首先需要通过JNI方法,定位对应的Java元素。

这里的Java元素,包括:类、成员变量、静态变量、成员方法、静态方法等。

Java代码:

class JNIInterface {
    public int num = 1;
    public static int staticNum = 2;

    private int getNum() {
        return num;
    }

    private static int getStaticNum() {
        return staticNum;
    }
}

C++代码:

struct JavaJNIInterface {
    jclass class_ref;
    jfieldID num;
    jfieldID static_num;
    jmethodID getNum;
    jmethodID getStaticNum;
};
static JavaJNIInterface javaJNIInterface;

jint JNI_onLoad(JavaVM *vm, void *reserved) {
    ...
    jclass c = env->FindClass(JNIInterface_CLASS);
    javaJNIInterface.class_ref = reinterpret_cast(env->NewGlobalRef(c));
    javaJNIInterface.num = env->GetFieldID(c, "num", "I");
    javaJNIInterface.static_num = env->GetStaticFieldID(c, "staticNum", "I");
    javaJNIInterface.getNum = env->GetMethodID(c, "getNum", "()I");
    javaJNIInterface.getStaticNum = env->GetStaticMethodID(c, "getStaticNum", "()I");
    env->DeleteLocalRef(c);
    ...
}

注意事项:

    jfieldID、jmethodID是可以反复使用的,除非虚拟机卸载了该类。所以,这里将jfieldID、jmethodID结果保存起来。jclass在保存之前,经过了NewGlobalRef转换。因为FindClass获得的jclass是局部引用,生命周期短。具体原因参考后面的局部引用与全局引用。变量/方法的是public,还是private都不影响JNI方法访问。字段、方法的类型签名不需要刻意去记忆。可以通过Studio的提示,快速实现。
局部引用与全局引用

JNI的引用类型具体有哪些,可以回顾一下《从Java到C+±JNI基本概念》。

JNI将引用类型分为两类:局部引用、全局引用。

当Java方法调用native方法时,Java VM会创建一个注册表。所有从Java层传递到native层的Java对象会被添加到注册表中。注册表会将不可移动的局部引用映射到Java对象,防止对象被垃圾回收。native方法调用结束并返回时,注册表会被删除,局部引用不再指向Java对象。这些Java对象如果没有其他GC Roots可达,就可正常被垃圾回收。

局部引用
    在一个native方法调用期间都是有效的,在native方法完成调用返回时,会被自动释放。不能跨线程使用。Java对象作为参数,传递到native方法时,都是局部引用。通过JNI方法,获取到的Java对象,都是局部引用。如:FindClass、NewObject、GetObjectField等JNI方法。局部引用在下面两种情况要考虑通过env->DeleteLocalRef主动释放:
      native方法访问大型的Java对象时(比如一个大数组),会创建对Java对象的本地引用。native方法使用完大对象后,还进行较耗时的操作。这期间,由于本地引用的存在,会导致大对象无法及时被垃圾回收。native方法创建了大量的局部引用,可能导致本地引用表溢出,甚至系统内存不足。比如在循环遍历中,调用JNI方法,获取Java对象,不断创建新的本地引用。下列代码,将会导致JNI ERROR (app bug): local reference table overflow (max=8388608)。
static void create_local_ref_too_much(JNIEnv *env, jclass clazz) {
    for (int i = 0; i < 10000000; i++) {
        jclass c = env->FindClass(JNIInterface_CLASS);
    }
}

运行单测testCreateLocalRefTooMuch,在logcat里将会看到如下结果:

全局引用
    明确释放之前,都是有效的。可以跨方法、跨线程使用。全局引用是用局部引用来创建:
jclass local_ref_class = env->FindClass(JNIInterface_CLASS);
jclass global_ref_class = reinterpret_cast(env->NewGlobalRef(local_ref_class));
    全局引用创建后,不像局部引用,可以被自动释放,只能手动释放:
env->DeleteGlobalRef(global_ref_class);
    不及时释放不需要的全局引用,可能会导致全局引用表溢出。下列代码,将会导致JNI ERROR (app bug): global reference table overflow (max=51200)。
static void create_global_ref_too_much(JNIEnv *env, jclass clazz) {
    for (int i = 0; i < 10000000; i++) {
        jclass c = env->FindClass(JNIInterface_CLASS);
        jclass global_ref = reinterpret_cast(env->NewGlobalRef(c));
        env->DeleteLocalRef(c);
    }
}

运行单测testCreateGlobalRefTooMuch,在logcat里将会看到如下结果:

传递Java基本类型

Java的基本类型,都能在JNI里找到对应的类型。对应表详见《从Java到C++:JNI基本概念》。如:

Java TypeNative TypeDescription
intjintsigned 32 bits

int对应jint。而jint在头文件jni.h的定义为:

typedef int32_t  jint;     

int32_t其实只是C++中int的别名。

typedef __int32_t     int32_t;

typedef int __int32_t;

Java的基本类型与C++的基本类型的对应关系如下:

Java TypeC++ TypeDescription
booleanboolunsigned 8 bits
bytesigned charsigned 8 bits
charunsigned shortunsigned 16 bits
shortshortsigned 16 bits
intintsigned 32 bits
longlong longsigned 64 bits
floatfloat32 bits
doubledouble64 bits

注意事项:

    该表格仅在Android平台上有效。因为C++与Java不同,它是平台相关的。Java的基本数据类型是固定长度的。但C++不提供这种保证。C++提供了一种灵活的标准,它只确保最小长度(从C语言借鉴而来),如下所示:

    short至少16位;

    int至少与short一样长;

    long至少32位,且至少与int一样长;

    long long至少64位,且至少与long一样长

    Java的long对应C++的long long,不是longJava的char是16位的,采用的是Unicode编码,两个字节表示一个字符。对应C++的unsigned short。而C++的char是8位的。Java的byte对应C++的signed char。C++中的char与C++中的int、short(默认是有符号类型)不同,char默认是有符号和无符号,由C++实现决定。经测试,在Android平台,char是等同于signed char的。

测试代码如下:

Java代码:

public native static void nativeTransmitPrimitiveType(int i, long l, float f,
                                                byte _byte, double d, boolean b, short s, char c);

@Test
public void testTransmitPrimitiveType() {
    JNIInterface.nativeTransmitPrimitiveType(Integer.MAX_VALUE, Long.MAX_VALUE, Float.MAX_VALUE,
        Byte.MAX_VALUE, Double.MAX_VALUE, true, Short.MAX_VALUE, '中');
}

C++代码:

static void transmit_primitive_type(JNIEnv *env, jclass clazz, jint i, jlong l, jfloat f,
                                    jbyte byte, jdouble d, jboolean b, jshort s, jchar c) {
    int c_i = numeric_limits::max();
    assert(i == c_i);
    //long等同于long int
    assert(i == numeric_limits::max());

    long long c_l = numeric_limits::max();
    assert(l == c_l);
    assert(l != numeric_limits::max());
    assert(l == numeric_limits::max());

    float c_f = numeric_limits::max();
    assert(f == c_f);

    signed char c_byte = numeric_limits::max();
    assert(byte == c_byte);
    assert(byte == numeric_limits::max());
    assert(255 == numeric_limits::max());

    double c_d = numeric_limits::max();
    assert(d == c_d);

    bool c_b = numeric_limits::max();
    assert(b == c_b);

    short c_s = numeric_limits::max();
    assert(s == c_s);

    unsigned short c_c = 0x4e2d;//'中'的Unicode编码
    assert(2 == sizeof(unsigned short));
    assert(c == c_c);
}
传递Java对象

Java的引用类型与JNI引用类型的对应表详见《从Java到C+±JNI基本概念》。

在native方法中,可以实现:

获取Java对象的各种变量的值修改Java对象的各种变量创建Java对象

Java对象定义如下:

public class Position {
    public float longitude;
    public float latitude;
}

public class Image {
    public long id;
    public int width;
    public int height;
    public Position pos = new Position();
    public byte[] data = new byte[10];
}

C++代码:

    定位Java类Image、Position的相关元素:类、变量
struct JavaPosition {
    jclass class_ref;
    jfieldID longitude;
    jfieldID latitude;
};

jint JNI_onLoad(JavaVM *vm, void *reserved) {
    ...
    jclass c = env->FindClass("com/example/java2cpp/bean/Image");
    javaImage.class_ref = reinterpret_cast(env->NewGlobalRef(c));
    javaImage.id = env->GetFieldID(c, "id", "J");
    javaImage.width = env->GetFieldID(c, "width", "I");
    javaImage.height = env->GetFieldID(c, "height", "I");
    javaImage.position = env->GetFieldID(c, "pos", "Lcom/example/java2cpp/bean/Position;");
    javaImage.data = env->GetFieldID(c, "data", "[B");
    env->DeleteLocalRef(c);

    c = env->FindClass("com/example/java2cpp/bean/Position");
    javaPosition.class_ref = reinterpret_cast(env->NewGlobalRef(c));
    javaPosition.latitude = env->GetFieldID(c, "latitude", "F");
    javaPosition.longitude = env->GetFieldID(c, "longitude", "F");
    env->DeleteLocalRef(c);
    ...
}
    在native方法中,通过JNI的一系列方法,获取Java对象的变量的值
    //获取对象的基本类型变量:通过GetField系列方法
    jlong id = env->GetLongField(image, javaImage.id);
    jint width = env->GetIntField(image, javaImage.width);
    jint height = env->GetIntField(image, javaImage.height);

    assert(id == 0);
    assert(width == 0);
    assert(height == 0);

    //获取对象的普通引用类型变量:通过GetObjectField
    jobject position = env->GetObjectField(image, javaImage.position);
    if (position != nullptr) {
        jfloat longitude = env->GetFloatField(position, javaPosition.longitude);
        jfloat latitude = env->GetFloatField(position, javaPosition.latitude);
        assert(longitude == 0.0f);
        assert(latitude == 0.0f);
    }

    //获取对象的数组变量:也是通过GetObjectField,需要强转为对应的JNI引用类型,如:
    //byte[]<->jbyteArray
    //int[]<->jintArray
    jbyteArray data = reinterpret_cast( env->GetObjectField(image, javaImage.data));
    //获取Java数组内容
    if (data != nullptr) {
        jsize size = env->GetArrayLength(data);
        jboolean isCopy;
        //基本类型数组:使用GetArrayElements系列方法。
        //引用类型数组:使用GetObjectArrayElement
        jbyte *byte_array_native = env->GetByteArrayElements(data, &isCopy);
        for (int i = 0; i < size; ++i) {
            jbyte b = byte_array_native[i];
            assert(b == 0);
        }
        //第三个参数mode通常为0即可
        env->ReleaseByteArrayElements(data, byte_array_native, 0);
    }
    修改Java对象的变量的值
    //修改对象的基本类型变量:通过SetField系列方法
    env->SetLongField(image, javaImage.id, 999);
    env->SetIntField(image, javaImage.width, 1920);
    env->SetIntField(image, javaImage.height, 1080);

    //修改对象的普通引用类型变量:通过SetObjectField
    //创建一个Java对象:通过AllocObject
    jobject newPosition = env->AllocObject(javaPosition.class_ref);
    env->SetFloatField(newPosition, javaPosition.longitude, 9.9f);
    env->SetFloatField(newPosition, javaPosition.latitude, 99.9f);
    env->SetObjectField(image, javaImage.position, newPosition);

    //修改对象的数组类型变量:也是通过SetObjectField
    //创建Java基本类型的数组:通过NewArray系列方法
    //创建Java引用类型的数组:通过NewObjectArray
    jbyteArray newData = env->NewByteArray(10);
    int len = 1;
    for (int i = 0; i < 10; ++i) {
        jbyte b = i;
        //基本类型数组通过SetArrayRegion系列方法设置数组的元素
        //SetArrayRegion其实可以直接设置整个C++数组给newData;
        //如果是引用类型数组,则要通过SetObjectArrayElement一个个设置数组的元素
        env->SetByteArrayRegion(newData, i, len, &b);
    }
    env->SetObjectField(image, javaImage.data, newData);


    //直接修改Java数组的内容:大体流程与获取Java数组内容一致
    jbyteArray extraData = reinterpret_cast(env->GetObjectField(image,
                                                                            javaImage.extra_data));
    if (data != nullptr) {
        jsize size = env->GetArrayLength(extraData);
        jboolean isCopy;
        jbyte *byte_array_native = env->GetByteArrayElements(extraData, &isCopy);
        for (int i = 0; i < size; i++) {
            //更改数组的内容
            byte_array_native[i] = i;
        }
        //将缓冲数组的内容拷贝回原数组,释放缓冲数组。
        env->ReleaseByteArrayElements(extraData, byte_array_native, 0);
    }

注意事项:

    获取Java对象的各种变量的值

    获取对象的基本类型变量:通过GetField系列方法获取对象的普通引用类型变量:通过GetObjectField方法获取对象的数组类型变量:也是通过GetObjectField方法,还需要强转为对应的JNI引用类型。比如:byte[]<->jbyteArray、int[]<->jintArray获取静态变量:和获取上述的成员变量类似,只是方法名的Get后面多了一个Static。方法的第一个参数也由jobject,变成了jclass。

    基本类型:通过GetStaticField系列方法普通引用类型、数组类型:通过GetStaticObjectField方法 获取Java数组内容:

    获取数组大小:通过GetArrayLength方法获取数组内容:

    基本类型数组:通过GetArrayElements系列方法引用类型数组:通过GetObjectArrayElement方法 获取Java数组后,需要释放缓冲数组:通过ReleaseByteArrayElements方法

    修改Java对象的各种变量

    修改对象的基本类型变量:通过SetField系列方法修改对象的普通引用类型变量:通过SetObjectField方法修改对象的数组类型变量:也是通过SetObjectField方法修改Java数组的元素:

    基本类型数组:通过SetArrayRegion系列方法设置数组的元素。SetArrayRegion其实可以直接设置整个C++数组给Java数组引用类型数组:通过SetObjectArrayElement方法一个个设置数组的元素

    创建Java对象

    创建普通Java对象:通过AllocObject方法创建数组:

    基本类型的数组:通过NewArray系列方法引用类型的数组:通过NewObjectArray

测试代码:testFillImage

调用Java方法

在native方法中,可以通过JNI方法调用各种Java方法。无论Java方法是public,还是private,都不会影响JNI的调用。

Java代码:

public class Image {
    private static final String TAG = "Image";
    ...
    public Position getPos() {
        return pos;
    }

    private byte[] getData() {
        return data;
    }
}

class JNIInterface {
    public native static Position nativeGetImagePos(Image image);

    public native static byte[] nativeGetImageData(Image image);

    public native static int nativeAdd(int i, int j);
}

C++代码:

    元素定位:略在native中调用对应的Java方法
static jobject get_image_pos(JNIEnv *env, jclass clazz, jobject image) {
    jobject pos = env->CallObjectMethod(image, javaImage.getPos);
    return pos;
}

static jbyteArray get_image_data(JNIEnv *env, jclass clazz, jobject image) {
    jbyteArray data = reinterpret_cast(env->CallObjectMethod(image, javaImage.getData));
    return data;
}

static jint add(JNIEnv *env, jclass clazz, jint i, jint j) {
    jint result = env->CallStaticIntMethod(javaCalculator.class_ref, javaCalculator.add, i, j);
    return result;
}

注意事项:

    Java成员方法通过CallMethod系列的方法调用,如CallObjectMethod、CallIntMethod、CallVoidMethod等。

    参数1固定是Java对象,参数2固定是方法Id。后续参数是Java方法参数,没有则不传。 Java静态方法通过CallStaticMethod系列的方法调用,如CallStaticObjectMethod、CallStaticIntMethod、CallStaticVoidMethod等。

    参数1固定是Java类,参数2固定是方法Id。后续参数是Java方法参数,没有则不传。

测试代码:testCallJavaMethodInNative

处理Java调用异常

C++调用Java方法时,如果发生了异常,需要通过JNI的相关方法进行处理。

Java代码:

class JNIInterface {
    private static void javaThrowException() {
        throw new NullPointerException();
    }
    
    public native static int nativeHandleJavaException();
}

C++代码:

    元素定位:略

    处理调用Java方法时发生的异常

jint handle_java_exception(JNIEnv *env, jclass clazz) {
    //产生了一个Java异常
    env->CallStaticVoidMethod(javaJNIInterface.class_ref, javaJNIInterface.javaThrowExceptMethod);
    //在Clear之前,不能调用除ExceptionXxx系列之外的JNI方法
    //env->FindClass("com/example/java2cpp/JNIInterface");
    if (env->ExceptionCheck()) {
        env->ExceptionClear();
        return -1;
    } else {
        return 0;
    }
}

注意事项:

    先通过ExceptionCheck检查方法调用是否产生异常,然后通过ExceptionClear处理。ExceptionClear的作用相当于try catch。在Clear之前,不能调用除ExceptionXxx系列之外的JNI方法。还可以通过ExceptionDescribe获取异常描述,通过ExceptionOccurred获取异常对象。暂不提供代码示例。

测试代码:testHandleJavaException、testUnHandleJavaException

传递枚举

Java枚举实际上是个对象。而C++的枚举,可以自动转换为int值,或者由int强转为枚举。

传递Java枚举到C++层,或者转换C++枚举到Java层,需要做一些额外的处理。

Java代码:

public enum ImageFormat {
    RGB_888, NV21, NV12
}

class JNIInterface {
    public native static ImageFormat nativeTransmitEnum(int iFormat, ImageFormat format);
}

C++代码:

    C++层对应枚举:
enum image_format {
    RGB_888, NV21, NV12
};
    元素定位:
    jclass c = env->FindClass("com/example/java2cpp/bean/ImageFormat");
    javaImageFormat.class_ref = reinterpret_cast(env->NewGlobalRef(c));
    javaImageFormat.name = env->GetMethodID(c, "name", "()Ljava/lang/String;");
    javaImageFormat.ordinal = env->GetMethodID(c, "ordinal", "()I");
    javaImageFormat.values = env->GetStaticMethodID(c, "values",
                                                "()[Lcom/example/java2cpp/bean/ImageFormat;");
    env->DeleteLocalRef(c);
    转换Java枚举成C++枚举、转换C++枚举成Java枚举:
static jobject transmit_enum(JNIEnv *env, jclass clazz, jint i_format, jobject format) {
    //如果传的是java枚举的下标
    //c++的枚举,可以自动转为int
    assert(i_format == image_format::NV21);

    //int无法自动转换为c++枚举,需要强转
    image_format nv21 = static_cast(i_format);
    assert(nv21 == image_format::NV21);

    //调用Java枚举的ordinal方法,获取对应枚举的下标
    image_format c_format = static_cast (env->CallIntMethod(format,
                                                                          javaImageFormat.ordinal));
    assert(c_format == image_format::NV21);


    image_format c_rgb = image_format::RGB_888;
    //调用Java枚举的静态方法values,获取枚举集合
    jobjectArray values = reinterpret_cast(env->CallStaticObjectMethod(
            javaImageFormat.class_ref, javaImageFormat.values));
    //获取对应的Java枚举
    jobject java_format = env->GetObjectArrayElement(values, c_rgb);
    return java_format;
}

注意事项:

    转换过程中,需要用到Java枚举的ordinal、values方法。C++的枚举,可以自动转换为int值。但反之则不行,需要强转。如代码中所示,直接传递Java枚举进入native,还得通过Java枚举的ordinal方法来获取枚举的下标。所以建议直接传递枚举的下标给native层。

测试代码:testTransmitEnum

传递字符串

在Java、C++之间传递字符串,需要注意字符串编码的格式。

传递UTF-8编码
static jstring transmit_string(JNIEnv *env, jclass clazz, jstring s) {
    jboolean isCopy;
    const char *chars = env->GetStringUTFChars(s, &isCopy);
    std::string c_s = "中国";
    assert(strcmp(chars, c_s.c_str()) == 0);

    char c_chars[7];
    c_chars[0] = 0xE4;//E4B8AD即"中"的UTF-8编码
    c_chars[1] = 0xB8;
    c_chars[2] = 0xAD;
    c_chars[3] = 0xE5;//E59BBD即"国"的UTF-8编码
    c_chars[4] = 0x9B;
    c_chars[5] = 0xBD;
    c_chars[6] = '';//'',C语言里用来表示字符串的结尾

    assert(strcmp(chars, c_chars) == 0);

    //使用完,要及时释放
    env->ReleaseStringUTFChars(s, chars);

    return env->NewStringUTF(c_chars);
}

注意事项:

    native方法获取到Java字符串,使用GetStringUTFChars。使用完后,必须调用ReleaseStringUTFChars释放获取到的字符数组指针。因为GetStringUTFChars调用了C++的关键字new,创建对应的字符数组。native方法返回字符串给Java层,需要先调用NewStringUTF创建Java字符串。

测试代码:testTransmitString

传递其他编码

比如从C++层获取的字符数组是GB2312编码的,想要回传到Java层。

C++代码:

    元素定位
    jclass c = env->FindClass("java/lang/String");
    javaString.class_ref = reinterpret_cast(env->NewGlobalRef(c));
    //java.lang.String的一个构造方法
    javaString.constructor = env->GetMethodID(c, "", "([BLjava/lang/String;)V");
    env->DeleteLocalRef(c);

2.使用GB2312编码的字符数组,构造Java String

static jstring native_get_GB2312String(JNIEnv *env, jclass clazz) {
    char c_str[5];
    c_str[0] = 0xD6;//D6D0即"中"
    c_str[1] = 0xD0;
    c_str[2] = 0xB9;//B9FA即"国"
    c_str[3] = 0xFA;
    c_str[4] = 0x00;//即'',C语言里用来表示字符串的结尾

    jbyteArray bytes = env->NewByteArray((jsize) strlen(c_str));
    env->SetByteArrayRegion(bytes, 0, (jsize) strlen(c_str), (jbyte *) c_str);
    jstring encoding = env->NewStringUTF("gb2312");

    return reinterpret_cast(env->NewObject(javaString.class_ref, javaString.constructor,
                                                    bytes, encoding));
}

注意事项:

    这里使用了String的一个构造方法,构造方法的类型签名是。这里不是通过AllocObject方法创建String对象。而是通过JNI的NewObject来调用String的特定构造方法。

测试代码:testGetGB2312

参考资料

Oracle的Java Native Interface Specification

JNI Functions

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/776630.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号