• Java之JNI的介绍与应用20170622


    /******************************************************************************************************************/

    JNI(Java Native Interface(Java本地(c语言写的)接口))

    一、JAVA调用C

    1.Java如何调用c库的函数

    1)加载C库(找到C库)

    static {        /* 1. load */

        System.loadLibrary("native");//加载C库导致c文件中的JNI_OnLoad被调用

    }

    2)找到函数(JAVA和C库之间建立映射)

    (1)显示建立(一般选择这个,更灵活)

    /* System.loadLibrary */

    JNIEXPORT jint JNICALL

    JNI_OnLoad(JavaVM *jvm, void *reserved)

    {

        JNIEnv *env;

        jclass cls;

        if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {

        //先获得java程序的运行环境

            return JNI_ERR; /* JNI version not supported */

        }

        //从而找到java程序的那个类

        cls = (*env)->FindClass(env, "JNIDemo");

        if (cls == NULL) {

            return JNI_ERR;

        }

        /* 2. map java hello <-->c c_hello 最后建立对应关系,将类和C函数注册到Native中*/

        if ((*env)->RegisterNatives(env, cls, methods, 1) < 0)

        return JNI_ERR;

        return JNI_VERSION_1_4;

    }

    其中methods数组定义如下,

    static const JNINativeMethod methods[] = {

        {"hello", "()V", (void *)c_hello},

    };

    关于数组成员类型中的含义如下:

    typedef struct {

        char *name;/* Java里调用的函数名 */

        char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型。 */

    //即JAVA程序中要调用的java方法(*name指向的函数)所对应的参数和返回值,用一些符号表示,具体见图表。

    (获取JNI字段描述符除了对应图标找之外,还可以先编译(java -d .JNIDemo.java)再用javah命令来生成头文件(javah -jni a.b.c.d.JNIDemo),头文件中就会含有那些描述符)

        void *fnPtr; /* C语言实现的本地函数 */

    } JNINativeMethod;

    JNI字段描述符图表:

     

    (2)隐式建立(自然用不到显示建立中需要做的事情,但是不灵活)

    I、类a.b.c.d.JNIDemo要调用hello函数

    II、C语言中要实现Java_a_b_c_d_JNIDemo_hello

    //函数名固定,所以不灵活

    //可以使用javah命令来获得这个复杂的函数名(头文件中已经声明好了这个函数)

    III、可以用工具生成头文件

    javac -d . JNIDemo.java

    javah -jni a.b.c.d.JNIDemo

    (3)注意,c程序中被java调用的函数,永远比对应java程序中的方法多两个参数(见javah生成的头文件,两个参数(JNIEnv *env, jobject cls))

    3)调用函数

    public native void hello();//使用前先使用native 关键字声明这是本地的方法(c实现的)

    public static void main (String args[]) {

        JNIDemo d = new JNIDemo();     

        /* 3. call */

        d.hello();//调用

    }

    4)Java和C库传递数据

    (1)传递基本类型数据

    直接使用、直接返回

    JAVA://传入123,打印返回(修改了java本地方法,自然要修改JNINativeMethod数组内容为相对应)

    System.out.println(d.hello(123));

    C://打印接收,返回100(可以从javah生成的头文件中获取到对应的声明来书写,去掉声明中的JNIEXPORT关键字(因为我们不需要导出,我们是注册进去的))

    jint c_hello(JNIEnv *env, jobject cls, jint m)

    {//使用jni.h中的类型

        printf("Hello, world! val = %d ", m);

        return 100;

    }

    (2)传递字符串 (jni.pdf P39)

    需要借用运行环境中的某些函数       

    JAVA:(修改了java本地方法,自然要修改JNINativeMethod数组内容为相对应)

    System.out.println(d.hello("this is java"));

    C:(可以从javah生成的头文件中获取到对应的声明来书写,去掉声明中的JNIEXPORT关键字)

    jstring JNICALL c_hello(JNIEnv *env, jobject cls, jstring str)

    {//JNICALL 可以不要,是个空的宏

        //printf("this is c : %s ", str);

        //return "return from C";

        const jbyte *cstr;

        cstr = (*env)->GetStringUTFChars(env, str, NULL);//将传进来的String类转换为字符串

        if (cstr == NULL) {//里面分配了内存,这里要判断

            return NULL; /* OutOfMemoryError already thrown */

        }

        printf("Get string from java :%s ", cstr);

        (*env)->ReleaseStringUTFChars(env, str, cstr);//使用完了要释放

        return (*env)->NewStringUTF(env, "return from c");//返回字符串

    }

    (3)传递数组 (jni.pdf P48)

    JAVA:(修改了java本地方法,自然要修改JNINativeMethod数组内容为相对应)

    int [] a = {1, 2, 3};

    int [] b = null;

    int i;

    /* 3. call */

    b = d.hello(a);

    for (i = 0; i < b.length; i++)     

        System.out.println(b[i]);

    C:(可以从javah生成的头文件中获取到对应的声明来书写,去掉声明中的JNIEXPORT关键字)

    jintArray c_hello(JNIEnv *env, jobject cls, jintArray arr)

    {

        jint *carr;

        jint *oarr;

        jintArray rarr;

        jint i, n = 0;

        carr = (*env)->GetIntArrayElements(env, arr, NULL);//获取所有元素

        if (carr == NULL) {

            return 0; /* exception occurred */

        }

        n = (*env)->GetArrayLength(env, arr);//获取数组长度

        oarr = malloc(sizeof(jint) * n);

        if (oarr == NULL)

        {

            (*env)->ReleaseIntArrayElements(env, arr, carr, 0);//使用完了要释放

            return 0;

        }

        for (i = 0; i < n; i++)

        {

            oarr[i] = carr[n-1-i];

        }

        (*env)->ReleaseIntArrayElements(env, arr, carr, 0);//使用完了要释放

        /* create jintArray */

        rarr = (*env)->NewIntArray(env, n);//构造jintArray (New<Type>Array)

        if (rarr == NULL)//NewIntArray使用的参数可以搜索jni.h

        {//创建失败

            return 0;

        }

        //把oarr中所有的内容拷贝到raar中

        //要设置的内容rarr,从0位开始设置,长度n,数据来源oarr。

        (*env)->SetIntArrayRegion(env, rarr, 0, n,oarr);//SetIntArrayRegion使用的参数可以搜索jni.h

        free(oarr);

       

        return rarr;返回构造的jintArray类型的rarr

    }

    /******************************************************************************************************************/

    二、C调用JAVA

    1. 创建虚拟机

    JavaVM* jvm;

    JNIEnv* env;

    /* 1. create java virtual machine */

    if (create_vm(&jvm, &env)) {

        printf("can not create jvm ");

        return -1;

    }

    jint create_vm(JavaVM** jvm, JNIEnv** env)

        JavaVMInitArgs args; 

        JavaVMOption options[1]; 

        args.version = JNI_VERSION_1_6;//表明虚拟机是哪个版本 

        args.nOptions = 1; 

        options[0].optionString = "-Djava.class.path=./"; //虚拟机查找类的路径./表示当前目录

        args.options = options; 

        args.ignoreUnrecognized = JNI_FALSE; 

        return JNI_CreateJavaVM(jvm, (void **)env, &args); 

    }

    2. 获得class

    jclass cls;

    /* 2. get class */

    cls = (*env)->FindClass(env, "Hello");//字符串内为要找的类(Hello是要调用的类)

    if (cls == NULL) {

        printf("can not find hello class ");

        ret = -1;

        goto destroy;

    }

    3. 实例化对象(非静态方法才需要实例化) : 获得构造方法(方法名为"<init>"), 构造参数, 创建对象

    1)获得构造方法(方法名为"<init>")

    jmethodID cid;

    /* Get the method ID for the String constructor */

    cid = (*env)->GetMethodID(env, cls,"<init>", "()V");//构造方法固定名为"<init>"

        if (cid == NULL) {

            ret = -1;

            printf("can not get constructor method");

            goto destroy;

        }

    2)构造参数(要调用的构造方法没有参数则不需要进行构造参数)

    3)创建对象

    jobject jobj;

    jobj = (*env)->NewObject(env, cls, cid);

    if (jobj == NULL) {

        ret = -1;

        printf("can not create object");

        goto destroy;

    }

    4. 调用方法   : 又分为获得方法, 构造参数, 调用方法

    1)获得方法

    jmethodID mid;//方法ID

    mid = (*env)->GetMethodID(env, cls, "sayhello_to","(Ljava/lang/String;)I");

    if (mid == NULL) {//"sayhello_to"方法名,"(Ljava/lang/String;)I"sayhello_to方法参数和返回值对应的signature(用于分辨同名方法)

        ret = -1;

        printf("can not get method ");

        goto destroy;

    }

    signature(JNI字段描述符)获取可使用:

    javac Hello.java

    javap -p -s Hello.class(Hello.class为编译好的Hello.java)

    2)构造参数(要调用的方法没有参数则不需要进行构造)

    int r;//存放返回值

    jstring jstr;

    jstr = (*env)->NewStringUTF(env, "abcdefg");//构造传入参数,传入字符串"abcdefg"

    3)调用方法

    (1)静态方法:

    (*env)->CallStaticVoidMethod(env, cls, mid, NULL);//NULL最后一项为要调用的方法的参数

    (2)非静态方法:

    int r;//存放返回值

    jstring jstr;

    jstr = (*env)->NewStringUTF(env, "abcdefg");//构造传入参数,传入字符串"abcdefg"

    r = (*env)->CallIntMethod(env, jobj, mid, jstr);//非静态传入的是jobject jobj,传入参数jstr

        printf("ret = %d ", r);

    5.读取/设置类中的属性(数据成员):

    1). 获得属性ID

    jfieldID nameID;

    jfieldID ageID;

    //get field id

    nameID = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");

    if (nameID == NULL) {//"name"java类中的数据成员名称

        ret = -1;//"Ljava/lang/String;"signature获取见上

        printf("can not get field name");

        goto destroy;

    }

    2). 读取/设置

    //get/set field

    jstr = (*env)->NewStringUTF(env, "Bill");

    (*env)->SetObjectField(env, jobj, nameID, jstr);//设置类中的数据成员name

    //name数据成员必然属于实例化对象的,先用创建类的实例化对象jobj

    获取数据成员直接把SetObjectField的Set改为Get即可

    /* Read the instance field s */

    jstr = (*env)->GetObjectField(env, obj, fid);

    3)读取/设置类中的Int型属性

    ageID = (*env)->GetFieldID(env, cls, "age", "I");

    if (ageID == NULL) {

        ret = -1;

        printf("can not get field age");

        goto destroy;

    }

    (*env)->SetIntField(env, jobj, ageID, 10);//int类型数据成员设置

    获取int数据成员直接把SetIntField中的Set改为Get即可

    jint value = env->GetIntField(env, obj, fid);

    6.使用结束后

    destroy:

        (*jvm)->DestroyJavaVM(jvm);

        return ret;

  • 相关阅读:
    集RTMP, HLS, FLV, WebSocket 于一身的网页直播/点播播放器EasyPlayer.js引用videojs无法自动播放问题解决
    HLS播放器RTSP播放器支持8K播放且低延时高并发全功能流媒体播放器EasyPlayer搭建之HTML中 px,em,rem该如何区别?
    网页全终端安防视频流媒体播放器EasyPlayer.js如何实现在web浏览器播放H.265编码视频?
    网页全终端视频流媒体视频直播/点播播放器EasyPlayer.js实现WEB播放H265/HEVC视频方案介绍
    网页全终端视频流媒体播放器EasyPlayer之使用 nginx 和 rtmp 插件搭建视频直播和点播服务器
    车辆实时监控项目中数字摄像头和模拟摄像头的运用、区别及优势分析
    音视频流媒体服务器直播点播平台在车辆实时监控系统中如何做用户观看限制?
    如何借助CDN解决在线教育带宽小、访问大、网点分布不均等问题
    关于区块链,看看诺奖得主是如何评说的!
    区块链也有随机性!
  • 原文地址:https://www.cnblogs.com/yuweifeng/p/7065596.html
Copyright © 2020-2023  润新知