本文學習如何在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物件,無需轉換。