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
物件卻不需要?注意,jmethodID
和jfieldID
並不是jobject
物件,無需轉換。