02 jni基本变量与转换

    xiaoxiao2024-11-15  52

     

     

    基本类型的 java 与 c 对照图 Java 类型本地 C 类型实际表示的 C 类型 (Win32)说明booleanjbooleanunsigned char无符号,8 位bytejbytesigned char有符号,8 位charjcharunsigned short无符号,16 位shortjshortshort有符号,16 位intjintlong有符号,32 位longjlong__int64有符号,64 位floatjfloatfloat32 位doublejdoubledouble64 位voidvoidN/AN/A

    在jni.h 和 jni_md.h中声明

    typedef unsigned char jboolean;

    typedef unsigned short jchar;

    typedef short jshort;

    typedef float jfloat;

    typedef double jdouble;

    typedef long jint;

    typedef __int64 jlong;

    typedef signed char jbyte;

     

    Java对象类型

     

    Java对象在C\C++代码中的形式如下:

    class _jclass : public _jobject {};

    class _jthrowable : public _jobject {};

    class _jstring : public _jobject {};

    class _jarray : public _jobject {};

    class _jbooleanArray : public _jarray {};

    class _jbyteArray : public _jarray {};

    class _jcharArray : public _jarray {};

    class _jshortArray : public _jarray {};

    class _jintArray : public _jarray {};

    class _jlongArray : public _jarray {};

    class _jfloatArray : public _jarray {};

    class _jdoubleArray : public _jarray {};

    class _jobjectArray : public _jarray {};

     

    Java数组在本地代码中的处理

     

    我们可以使用GetFieldID获取一个Java数组变量的ID,然后用GetObjectFiled取得该数组变量到本地方法,返回值为jobject,然后我们可以强制转换为j<Type>Array类型。

     

    typedef jarray jbooleanArray;

    typedef jarray jbyteArray;

    typedef jarray jcharArray;

    typedef jarray jshortArray;

    typedef jarray jintArray;

    typedef jarray jlongArray;

    typedef jarray jfloatArray;

    typedef jarray jdoubleArray;

    typedef jarray jobjectArray;

     

    j<Type>Array类型是JNI定义的一个对象类型,它并不是C/C++的数组,如int[]数组,double[]数组等等。所以我们要把j<Type>Array类型转换为C/C++中的数组来操作。

     

    JNIEnv定义了一系列的方法来把一个j<Type>Array类型转换为C/C++数组或把C/C++数组转换为j<Type>Array。

     

    // 获取&释放primitive数组

    void * GetPrimitiveArrayCritical(jarray array, jboolean*isCopy)

    void ReleasePrimitiveArrayCritical(jarray array, void*carray, jint mode)

     

    上面是JNIEnv提供给本地代码调用的数组操作函数,大致可以分为下面几类:

     

    1) 获取数组的长度

     

    jsize GetArrayLength(jarray array);

     

    2) 对象类型数组的操作

     

    jsize GetArrayLength(jarray array) // 获得数组的长度

    jobjectArray NewObjectArray(jsize len, jclass clazz, jobjectinit) // 创建对象数组,指定其大小

    jobject GetObjectArrayElement(jobjectArray array, jsizeindex) // 获得数组的指定元素

    void SetObjectArrayElement(jobjectArray array, jsizeindex,jobject val) // 设置数组元素

     

    JNI没有提供直接把Java的对象类型数组(Object[ ])直接转到C++中的jobject[ ]数组的函数。而是直接通过Get/SetObjectArrayElement这样的函数来对Java的Object[ ]数组进行操作

     

    3) 对基本数据类型数组的操作

     

    基本数据类型数组的操作方法比较多,大致可以分为如下几类:

    // 创建数组

    jbooleanArray NewBooleanArray(jsize len) // 创建Boolean数组,指定其大小

    // 获得指定类型数组的元素

    jboolean * GetBooleanArrayElements(jbooleanArray array,jboolean *isCopy)

    // 释放指定数组

    void ReleaseBooleanArrayElements(jbooleanArrayarray,jboolean *elems,jint mode)

    // 获取某区域的数据

    void GetBooleanArrayRegion(jbooleanArray array,jsizestart, jsize len, jboolean *buf)

    // 设置某区域的数据

    void SetBooleanArrayRegion(jbooleanArray array, jsizestart, jsize len,const jboolean *buf)

     

    这类函数可以把Java基本类型的数组转换到C/C++中的数组。有两种处理方式,一是拷贝一份传回本地代码,另一种是把指向Java数组的指针直接传回到本地代码,处理完本地化的数组后,通过Realease<Type>ArrayElements来释放数组。处理方式由Get方法的第二个参数isCopied来决定。

    Realease<Type>ArrayElements(<Type>Arrayarr,<Type>* array, jint mode)用这个函数可以选择将如何处理Java和C/C++本地数组:

    其第三个参数mode可以取下面的值:

    l 0:对Java的数组进行更新并释放C/C++的数组

    l JNI_COMMIT:对Java的数组进行更新但是不释放C/C++的数组

    l JNI_ABORT:对Java的数组不进行更新,释放C/C++的数组

     

     

    局部引用与全局引用

     

    1) JNI中的引用变量

     

    Java代码与本地代码里在进行参数传递与返回值复制的时候,要注意数据类型的匹配。对于int, char等基本类型直接进行拷贝即可,对于Java中的对象类型,通过传递引用实现。VM保证所有的Java对象正确的传递给了本地代码,并且维持这些引用,因此这些对象不会被Java的gc(垃圾收集器)回收。因此,本地代码必须有一种方式来通知VM本地代码不再使用这些Java对象,让gc来回收这些对象。

     

    JNI将传递给本地代码的对象分为两种:局部引用和全局引用。

     

    l 局部引用:只在上层Java调用本地代码的函数内有效,当本地方法返回时,局部引用自动回收。

     

    l 全局引用:只有显示通知VM时,全局引用才会被回收,否则一直有效,Java的gc不会释放该引用的对象。

     

    默认的话,传递给本地代码的引用是局部引用。所有的JNI函数的返回值都是局部引用。

     

     

    2) 手动释放局部引用情况

     

    虽然局部引用会在本地代码执行之后自动释放,但是有下列情况时,要手动释放:

     

    l 本地代码访问一个很大的Java对象时,在使用完该对象后,本地代码要去执行比较复杂耗时的运算时,由于本地代码还没有返回,Java收集器无法释放该本地引用的对象,这时,应该手动释放掉该引用对象。

     

    (*env)->DeleteLocalRef(env, elemArr);

     

    这个情形的实质,就是允许程序在native方法执行期间,java的垃圾回收机制有机会回收native代码不在访问的对象。

     

    l 创建的工具函数,它会被未知的代码调用,在工具函数里使用完的引用要及时释放。

     

    l 不返回的本地函数。例如,一个可能进入无限事件分发的循环中的方法。此时在循环中释放局部引用,是至关重要的,这样才能不会无限期地累积,进而导致内存泄露。

     

    局部引用只在创建它们的线程里有效,本地代码不能将局部引用在多线程间传递。一个线程想要调用另一个线程创建的局部引用是不被允许的。将一个局部引用保存到全局变量中,然后在其它线程中使用它,这是一种错误的编程。

     

    3) 全局引用

     

    在一个本地方法被多次调用时,可以使用一个全局引用跨越它们。一个全局引用可以跨越多个线程,并且在被程序员手动释放之前,一直有效。和局部引用一样,全局引用保证了所引用的对象不会被垃圾回收。

     

    JNI允许程序员通过局部引用来创建全局引用, 全局引用只能由NewGlobalRef函数创建。下面是一个使用全局引用例子:

    stringClass = (*env)->NewGlobalRef(env, localRefCls);

    (*env)->DeleteLocalRef(env, localRefCls);

     

    4) 释放全局引用

     

    在native代码不再需要访问一个全局引用的时候,应该调用DeleteGlobalRef来释放它。如果调用这个函数失败,Java VM将不会回收对应的对象。

     

    最新回复(0)