• IntelliJ IDEA平台下JNI编程(五)—本地C代码创建Java对象及引用


    本文学习如何在C代码中创建Java对象和对象数组,前面我们学习了C代码中访问Java对象的属性和方法,其实在创建对象时本质上也就是调用构造函数,因此本文知识学习起来也很轻松。有了前面学习数组创建的方法后,C代码创建对象数组同样很容易,下面开始学习吧~

    1. C代码创建Java对象

    创建Java对象本质就是调用构造函数,这与上一篇文章中提到的调用方法使用方法一致。下面直接贴代码:

    package com.huachao.java;
    
    /**
     * Created by HuaChao on 2017/03/23.
     */
    
    public class HelloJNI {
    
    
        static {
            System.loadLibrary("HelloJNI");
        }
    
        private String name;
    
        private HelloJNI(String name) {
            this.name = name;
        }
    
    
        public static native HelloJNI getInstance();
    
    
        public static void main(String[] args) {
            HelloJNI obj = HelloJNI.getInstance();
            System.out.println(obj.name);
    
        }
    
    }
    
    

    接下来在c代码中完成对getInstance()的实现。

    #include<jni.h>
    #include <stdio.h>
    #include "com_huachao_java_HelloJNI.h"
    
    JNIEXPORT jobject JNICALL Java_com_huachao_java_HelloJNI_getInstance
      (JNIEnv * env,jobject thisObj){
    
            jclass cls = (*env)->FindClass(env, "com/huachao/java/HelloJNI");
    
            //获取构造函数的ID
            jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
            if (NULL == midInit) return NULL;
            // 调用构造函数
            jstring name=(*env)->NewStringUTF(env, "HuaChao");
            jobject newObj = (*env)->NewObject(env, cls, midInit, name);
    
            return newObj;
    }
    

    有一点需要注意,在调用HellJNI(String name)构造函数时,需要先将c的本地字符串转为jstring类型,即调用NewStringUTF函数,再作为参数传入。运行结果如下:

    HuaChao
    
    

    2. C代码创建对象数组

    创建对象数组其实就是结合对象的创建和数组的创建来实现,都是前面学过的知识。在HelloJNI.java中加入getArray函数,并且修改main函数如下:

     public static native HelloJNI[] getArray(String[] names);
     
     public static void main(String[] args) {
          String[] names = {"HuaChao", "Lianjin"};
          HelloJNI[] arr = HelloJNI.getArray(names);
          for (HelloJNI obj : arr) {
              System.out.println("name:" + obj.name);
          }
    
      }
    

    在本地C代码中,对应getArray函数的实现如下:

    JNIEXPORT jobjectArray JNICALL Java_com_huachao_java_HelloJNI_getArray
     (JNIEnv * env,jobject thisObj,jobjectArray names){
    
          // 获取HelloJNI类的引用
           jclass clazz = (*env)->FindClass(env, "com/huachao/java/HelloJNI");
           //获取构造函数的ID
           jmethodID midInit = (*env)->GetMethodID(env, clazz, "<init>", "(Ljava/lang/String;)V");
           // 获取数组长度
           jsize length = (*env)->GetArrayLength(env, names);
           jobjectArray outJNIArray = (*env)->NewObjectArray(env, length, clazz, NULL);
           //遍历names数组
           int i;
           for (i = 0; i < length; i++) {
              jstring name = (*env)->GetObjectArrayElement(env, names, i);
              if (NULL == name)
                    return NULL;
               jobject newObj = (*env)->NewObject(env, clazz, midInit, name);
              (*env)->SetObjectArrayElement(env, outJNIArray, i, newObj);
           }
    
           return outJNIArray;
     }
    

    最后,运行如下:

    name:HuaChao
    name:Lianjin
    
    

    3. C代码对Java类的局部和全局引用

    与Java代码类似,在C代码函数里面创建的对象时,对对象的引用为局部引用,当函数执行结束时,引用无效。但是如果在函数外对对象进行引用,引用会一直有效,直到程序结束。前面我们频繁地用到了jclass和jmethodID以及jfieldID,下面我们尝试将其作为c代码的全局引用。

    首先看看Java中代码。

    package com.huachao.java;
    
    /**
     * Created by HuaChao on 2017/03/23.
     */
    
    public class HelloJNI {
    
    
        static {
            System.loadLibrary("HelloJNI");
        }
    
        private String name;
    
        private HelloJNI(String name) {
            this.name = name;
        }
    
    
        public static native HelloJNI getInstance();
    
    
        public static void main(String[] args) {
            HelloJNI obj1 = HelloJNI.getInstance();
            HelloJNI obj2 = HelloJNI.getInstance();
            System.out.println("obj1:--->>name=" + obj1.name);
            System.out.println("obj2:--->>name=" + obj2.name);
        }
    
    }
    
    

    然后在C代码中,将jclass和jmethodID对象作为全局变量

    #include<jni.h>
    #include <stdio.h>
    #include "com_huachao_java_HelloJNI.h"
    static jclass cls;
    static jmethodID midInit;
    JNIEXPORT jobject JNICALL Java_com_huachao_java_HelloJNI_getInstance
      (JNIEnv * env,jobject thisObj){
    
            //如果cls为null,则调用FindClass赋值
            if(NULL==cls){
                cls = (*env)->FindClass(env, "com/huachao/java/HelloJNI");
            }
            if(NULL==cls) return NULL;
    
            //如果midInit为NULL,获取构造函数的ID
            if(NULL==midInit){
                midInit = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
            }
            if (NULL == midInit) return NULL;
            // 调用构造函数
            jstring name=(*env)->NewStringUTF(env, "HuaChao");
            jobject newObj = (*env)->NewObject(env, cls, midInit, name);
    
            return newObj;
    }
    
    

    点击运行,发现报错!!!!!

    为什么会出现这样的情况呢?主要是第一次调用FindClass()函数时,能正常获取到com.huachao.java.HelloJNI类对象,并作为全局静态变量存储。但是,在接下来调用中,引用不再有效,虽然此时cls变量不为NULL,主要原因是FindClass()返回的是一个局部引用,当函数执行结束时,引用将变为无效的引用。

    为了解决这个问题,可以通过调用NewGlobalRef函数将局部引用转为全局引用。完成转换后,记得将局部引用删除。修改c代码如下:

    #include<jni.h>
    #include <stdio.h>
    #include "com_huachao_java_HelloJNI.h"
    static jclass cls;
    static jmethodID midInit;
    JNIEXPORT jobject JNICALL Java_com_huachao_java_HelloJNI_getInstance
      (JNIEnv * env,jobject thisObj){
    
            //如果cls为null,则调用FindClass赋值
            if(NULL==cls){
               jclass clsLocal = (*env)->FindClass(env, "com/huachao/java/HelloJNI");
               //局部引用转为全局引用
               cls = (*env)->NewGlobalRef(env, clsLocal);
               //删除局部引用
               (*env)->DeleteLocalRef(env, clsLocal);
            }
            if(NULL==cls) return NULL;
    
            //如果midInit为NULL,获取构造函数的ID
            if(NULL==midInit){
                midInit = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
            }
            if (NULL == midInit) return NULL;
            // 调用构造函数
            jstring name=(*env)->NewStringUTF(env, "HuaChao");
            jobject newObj = (*env)->NewObject(env, cls, midInit, name);
    
            return newObj;
    }
    

    运行结果如下:

    obj1:--->>name=HuaChao
    obj2:--->>name=HuaChao
    

    细心的同学会发现,同为全局引用,为什么jclass全局引用需要将本地引用转为全局引用,而jmethodID对象却不需要?注意,jmethodIDjfieldID并不是jobject对象,无需转换。

  • 相关阅读:
    为什么puppeteer比selenium好?
    Puppeteer
    js跳出多层循环
    webpack loader- 图片处理
    webpack的loader的原理和实现
    Webpack中Loader的pitch方法
    url-loader和file-loader区别
    Vue中强制组件重新渲染的正确方法
    ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION :浏览器下载报错
    JSBridge的原理及使用
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/10608629.html
Copyright © 2020-2023  润新知