• Java Native Interface 二 JNI中对Java基本类型和引用类型的处理



    本文是《The Java Native Interface Programmer’s Guide and Specification》读书笔记

    Java编程里会使用到两种类型:基本类型(如int,float等)和引用类型(如class,instance,arrays),JNI编程里对这两种的类型的处理方法也是不一样的。

    JNI定义了与Java的基本类型和引用类型相对应的C/C++类型:

    Java类型 本地类型(JNI中使用)
    boolean jboolean
    int jint
    float jfloat
    byte jbyte
    char jchar
    short jshort
    long jlong
    double jdouble

    Java中所有对象都是Object类的子类,在JNI中与之对应的为jobject类,所有的类型都是jobject类的子类,直接继承自jobject 的引用类型:

    Java类型 本地类型
    java.lang.Class jclass
    java.lang.String jstring
    arrays jarray
    java.lang.Throwable jthrowable

    其中数组又会有其他的子类型,如Java中的int[] 对应到JNI中为jintArray,Object[]对应为jobjectArray[]等;

    在使用C++时,类的继承的语法为

    class _jobjct{};
    class _jclass:public_jobjct{};//公有继承
    

    同时在JNI中会使用类标识符来表示Java中的类或接口。数组用'['来标识,如int[]在JNI中用'[I'来标识,三维数组double[][][],用['[[[D'来标识;下面是JNI中常用基本类型的标识符与对应的Java类:

    成员域标识符 Java 类型
    Z boolean
    B byte
    C char
    S short
    I int
    J long
    F float
    D double

    JNI中引用类型的标识符以字符'L'开始,并以';'结束,但数组的标识符与前面的基本类型的标识符相同;

    标识符 Java类型
    "Ljava/lang/String;" String
    "[I" int[]
    "[Ljava/lang/Object;" Object[]

    在JNI中方法的标识符里,方法所含的参数之间是没有空格隔开的,并用字符"V"作为void方法的返回标识;构造函数一般使用"V"作为返回值,"init"作为名字;

    方法标识符 Java方法
    "()Ljava/lang/String; " String f()
    "(ILjava/lang/Class;)J" long f(int i,Class c)
    "([B)V" String(byte[]bytes)

    在了解完JNI中对应的类型与标识符后,接下来就是怎样使用它们了。

    String类型的使用

    在实现本地方法时,必须使用JNI的方法将对应的jstring对象类型转型为c/c++可以识别的string 对象,否则在调用这个本地方法时会导致Java虚拟机崩溃;
    假设有一个本地方法,在Java里声明为:

     public native String getLine(String promt);
    

    则你在实现这个方法时,不能这样写:

    JNIEXPORT jstring JNICALL
    Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
    {
        /* ERROR: incorrect use of jstring as a char* pointer */
        printf("%s", prompt);
        ...
    }
    

    你可以这样写:

    JNIEXPORT jstring JNICALL
    Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
    {
        char buf[128];
        const jbyte *str;
        str = (*env)->GetStringUTFChars(env, prompt, NULL);
        //必须要记得检查JNI方法的返回值,因为JVM在执行时需要分配内存,可能存在内存分配失败的情况
        if (str == NULL) {
            return NULL; /* OutOfMemoryError already thrown */
        }
        printf("%s", str);
        //当使用完JNI方法返回的对象后,调用相应的Release方法释放掉对应的内      存,忘记使用Release方法可能会导致内存泄漏
        (*env)->ReleaseStringUTFChars(env, prompt, str);
        /* We assume here that the user does not type more than
         * 127 characters */
        scanf("%s", buf);
        return (*env)->NewStringUTF(env, buf);
    }
    

    jni中的可以对String(jstring)对象使用的方法总结:

    方法名 方法描述
    GetStringChars,ReleaseStringChars 获取或释放一个指向Unicode编码的字符串内容的指针,可能会返回一个字符串的拷贝
    GetStringUTFChars,ReleaseStringUTFChars 获取或释放一个指向UTF-8编码的字符串内容的指针,可能会返回一个字符串的拷贝
    GetStrinLength 返回字符串里Unicode编码的字符的个数
    GetStringUTFLength 返回字符串里UTF-8编码的字符的个数
    NewString 创建一个包含给定的Unicode编码的C字符串的所有字符的java.lang.String实例
    NewStringUTF 创建一个包含给定的UTF-8编码的C字符串的所有字符的java.lang.String实例
    GetStringCritical,ReleaseStringCritical 获取一个指向Unicode编码的字符串内容的指针,可能返回一个字符串的拷贝。并且本地代码在调用Get/ReleaseStrinCritical这对方法时,不能阻塞。
    GetStringUTFRegion,SetStringUTFRegion 从一个预先分配的UTF-8格式的C的缓冲区拷贝字符串内容(或将字符串内容写入缓冲区中)

    Arrays类型的使用

    JNI对普通数组(基本类型的数组,如int[],float[])和对象数组的处理方法是不一样的。下面以简单的例子来说明:

    1 普通的数组的使用

    求一个数组的和的例子:Java Class为:

    class IntArray {
        private native int sumArray(int[] arr);//jni方法,求数组的和
        public static void main(String[] args) {
            IntArray p = new IntArray();
            int arr[] = new int[10];
            for (int i = 0; i < 10; i++) {
                arr[i] = i;
            }
            int sum = p.sumArray(arr);
            System.out.println("sum = " + sum);
        }
        static {
            System.loadLibrary("IntArray");
        }
    }
    

    Java的Arrays在JNI中用jarrays和它的子类,如jintArray来表示,就像jstring不是C字符串的类型,jarrays也不是C的数组类型,因此,你不能在实现JNI方法时,直接使用jarrays的引用,下面是错误的JNI方法的编写:

    JNIEXPORT jint JNICALL
    Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
    {
        int i, sum = 0;
        for (i = 0; i < 10; i++) {
            sum += arr[i];
        }
    }
    

    正确的JNI的方法应该为:

    JNIEXPORT jint JNICALL
    Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
    {
        jint buf[10];
        jint i, sum = 0;
        (*env)->GetIntArrayRegion(env, arr, 0, 10, buf);
        for (i = 0; i < 10; i++) {
            sum += buf[i];
        }
        return sum;
    }
    

    也可以这样写:

    JNIEXPORT jint JNICALL
    Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
    {
        jint *carr;
        jint i, sum = 0;
        carr = (*env)->GetIntArrayElements(env, arr, NULL);
        if (carr == NULL) {
            return 0; /* exception occurred */
        }
        for (i=0; i<10; i++) {
            sum += carr[i];
        }
        (*env)->ReleaseIntArrayElements(env, arr, carr, 0);
        return sum;
    }
    

    JNI中可以对基本类型数组使用的方法有:

    <类型>:表示需要用基本类型来替代,如Get<类型>ArrayRegion,就可以有GetIntArrayRegion,GetFloatArrayRegion等;

    方法名 方法描述
    Get<类型>ArrayRegion,Set<类型>ArrayRegion 得到基本类型的数组的内容,或将内容写入预先分配的C的缓冲区里
    Get<类型>ArrayElements,Release<类型>ArrayElements 获得一个指向基本类型数组的内容的指针,可能会返回一个数组的拷贝
    GetArrayLength 返回数组的大小
    New<类型>Array 创建一个给定长度的数组
    GetPrimitiveArrayCritical,ReleasePrimitiveArrayCritical 获得或释放指向基本类型数组的内容的指针

    2 对象数组的使用:

    JNI提供了许多对方法来得到对象数组。GetObjectElement方法会返回一个给定位置的元素,SetObjectElement方法更新给定位置的元素。但我们不能一次性就得到(复制)所有的对象数组里的元素。下面还是以简单的例子来说明:
    Java Class为:

    class ObjectArrayTest {
      //本地方法,用来生成一个给定大小的二维矩阵
        private static native int[][] initInt2DArray(int size);
        public static void main(String[] args) {
            int[][] i2arr = initInt2DArray(3);
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                     System.out.print(" " + i2arr[i][j]);
                }
                System.out.println();
            }
        }
        static {
            System.loadLibrary("ObjectArrayTest");
        }
    }
    

    本地方法的实现为:

    JNIEXPORT jobjectArray JNICALL
    Java_ObjectArrayTest_initInt2DArray(JNIEnv *env,
                                       jclass cls,
                                       int size)
    {
        jobjectArray result;
        int i;
        //获得一个元素类型(Int)的Class引用,类似于Java中得到某一类型的类加载器
        jclass intArrCls = (*env)->FindClass(env, "[I");
        if (intArrCls == NULL) {
            return NULL; /* exception thrown */
        }
        //生成一个元素类型为intArrCls的数组,这个方法只能生成一维数组
        result = (*env)->NewObjectArray(env, size, intArrCls,
    NULL);
        if (result == NULL) {
            return NULL; /* out of memory error thrown */
        }
        for (i = 0; i < size; i++) {
            jint tmp[256];  /* make sure it is large enough! */
            int j;
            //生成数组里存放的元素(仍然为一个数组)
            jintArray iarr = (*env)->NewIntArray(env, size);
            if (iarr == NULL) {
                return NULL; /* out of memory error thrown */
            }
            for (j = 0; j < size; j++) {
                tmp[j] = i + j;
            }
            //用tmp里的元素填充iarr数组,使用的是基本类型数组的方法
            (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);
            //将iarr作为数组元素填充到result数组相应位置中,使用的是对象数                组的方法
            (*env)->SetObjectArrayElement(env, result, i, iarr);
             //释放到对iarr对象的引用
            (*env)->DeleteLocalRef(env, iarr);
        }
        //二维数组就是数组里存放的元素仍然为数组,返回一个对象数组
        return result;
    }
    
  • 相关阅读:
    tp文件上传
    tp5与页面链接
    tp5语法查询
    tp5基本增删改查
    tp5基本登录
    数据修改
    文件上传。判断。一维二维数组
    数据库连接
    php针对于数据库的改变
    php数据库连接
  • 原文地址:https://www.cnblogs.com/WoodJim/p/4794772.html
Copyright © 2020-2023  润新知