JNI訪問Java對象的成員

通過前面的幾篇文章的學習,咱們已經打通了Java層到Native層的通道。Java層調用Native層很簡單,只須要調用一個native方法,那麼Native層如何回調Java層呢,從這篇文章開始,咱們就來探討這個問題。html

Java類的成員(變量和方法)有靜態和非靜態之分,靜態成員一般經過類來直接調用(固然也能夠用對象調用),非靜態成員經過類的對象來調用。在JNI層也聽從一樣的規則。所以我將經過兩篇文章分別來講明,而今天這篇文章就是講述在JNI層如何經過Java對象來訪問成員(變量和方法)。java

準備工做

經過對前面文章的學習,咱們已經學會了如何以最快的速度實現函數的"動態註冊",那麼咱們來先作一個準備工做,實現"動態註冊"ios

首先準備一個帶有native方法的Java類,名爲Person.javac++

package com.bxll.jnidemo;

public class Person {

    static {
        System.loadLibrary("person_jni");
    }

    native void native_init();
    
    private int mAge = -1;
    private String mName;

    public Person() {
        native_init();
    }

    public void setName(String name) {
        mName = name;
    }

    public String toString() {
        return "Name: " + mName + ", Age: " + mAge;
    }
    
    public static void main(String[] args) {
        System.out.println(new Person());
    }    
}
複製代碼

而後在Native層進行實現,實現的文件爲person.cpp數組

#include <jni.h>
#include <iostream>

static void com_bxll_jnidemo_Person_init(JNIEnv * env, jobject obj) {

}

const JNINativeMethod methods[] = 
{
    {"native_init", "()V", (void *)com_bxll_jnidemo_Person_init}
};

jint JNI_OnLoad(JavaVM * vm, void * reserved) {
    int jni_version = -1;

    JNIEnv * env = NULL;
    if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) == JNI_OK)
    {
	jclass clazz_person = env->FindClass("com/bxll/jnidemo/Person");
	if (env->RegisterNatives(clazz_person, methods, sizeof methods / sizeof methods[0]) == JNI_OK)
	{
	    jni_version = JNI_VERSION_1_6;
	}
    }

    return jni_version;
}
複製代碼

那麼,com_bxll_jnidemo_Person_init函數就是Java層的native方法的實現,咱們將在這個函數中來說解如何訪問Java對象的變量和方法。bash

訪問對象的變量

com_bxll_jnidemo_Person_init函數中來實現獲取Java對象變量的值,以及設置變量的值。oracle

static void com_bxll_jnidemo_Person_init(JNIEnv * env, jobject obj) {
    // 獲取Java對象obj的Class對象
    jclass clazz_person = env->GetObjectClass(obj);
    // 從Class對象中獲取mAge變量
    jfieldID fieldID_mAge = env->GetFieldID(clazz_person, "mAge", "I");
    // 從Java對象obj中獲取變量mAge的值
    jint age = env->GetIntField(obj, fieldID_mAge);
    std::cout << "Age: " << age << std::endl;
    // 設置Java對象obj的變量mAge的值
    env->SetIntField(obj, fieldID_mAge, 18);
}
複製代碼

從註釋中能夠看出,JNI層獲取Java對象的某個變量值以及設置變量值的方式,是否是和使用Java反射的方式很是像。ide

若是你懂Java反射,那麼這裏的邏輯就瓜熟蒂落,固然,若是你不懂也不要緊,只要咱們記住獲取對象變量值最終調用的函數是什麼,在這裏就是GetIntField,而後經過參數一步一步推斷就能夠了。函數

那麼如今,咱們來看看這裏涉及的函數post

獲取jclass對象

jclass GetObjectClass(JNIEnv *env, jobject obj);
複製代碼

jobject obj表明一個Java對象,函數返回一個對象的Class對象。

經過JNI函數動態註冊之JNI類型和簽名這篇文章可知,jclassJavaClass對象在JNI中的類型。

Java反射同樣,獲取到了Class對象後就能夠從中獲取變量和方法。

獲取jfieldID

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
複製代碼

參數

  1. env: 指向JNIEnv的指針。
  2. clazz: Class對象,能夠經過GetObjectClass()或者FindClass()獲取。
  3. name: Class對象的某個變量的名字。
  4. sig: Class對象的變量的類型簽名。

若是不知道參數sig的類型簽名如何寫,能夠閱讀JNI函數動態註冊之JNI類型和簽名進行了解。

返回一個Class對象的變量,類型爲jfieldIDClass對象的變量由參數namesig惟一標識。

Java反射同樣,獲取到了變量後,就能夠經過這個變量獲取到變量的值,也能夠設置變量的值。

獲取變量的值

根據變量的類型,會有不一樣的函數來獲取變量的值。函數的基本形式以下

NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
複製代碼
基本類型函數名 返回類型
GetBooleanField() jboolean
GetByteField() jbyte
GetCharField() jchar
GetShortField() jshort
GetIntField() jint
GetLongField() jlong
GetFloatField() jfloat
GetDoubleField() jdouble
GetObjectField() jobject

前八項對應於Java的八中基本類型,第九項對應於Java的全部引用類型。

例如對於int類型,對應的函數原型你以下

jint GetIntField(JNIEnv * env, jobject obj, jfieldID fieldID);
複製代碼

參數

  • jobject obj表明一個Java對象
  • jfieldID fieldID表明Class對象的某個變量,經過GetFieldID()獲取。

設置變量的值

根據變量的類型,也會有不一樣的函數來設置變量的值。函數的基本形式以下

void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,
                    NativeType value);
複製代碼
函數名 參數類型
SetBooleanField() jboolean
SetByteField() jbyte
SetCharField() jchar
SetShortField() jshort
SetIntField() jint
SetLongField() jlong
SetFloatField() jfloat
SetDoubleField() jdouble
SetObjectField() jobject

前八項對應於Java的八中基本類型,第九項對應於Java的全部引用類型。

例如,對與int類型,函數原型以下

void setIntField(JNIEnv * env, jobject obj, jfieldID fieldID, jint value);
複製代碼

參數

  • jobject obj表明Java對象
  • jfieldID fieldID表明Class對象的某個方法,經過GetFieldID()獲取
  • jint value表明要設置的int

訪問對象的方法

咱們仍然在com_bxll_jnidemo_Person_init函數中來調用Java對象的方法

static void com_uni_ndkdemo_Person_init(JNIEnv *env, jobject obj) {
    // 獲取Class對象
    jclass clazz_person = env->GetObjectClass(obj);
    // 從Class對象中獲取setName方法
    jmethodID methodID_setName = env->GetMethodID(clazz_person, "setName", "(Ljava/lang/String;)V");
    // 建立調用setName()須要的參數值
    jstring name = env->NewStringUTF("David");
    // 調用Java對象obj的setName()方法
    env->CallVoidMethod(obj, methodID_setName, name);
}
複製代碼

咱們一樣能夠發現,在JNI中調用Java對象的方法,和Java反射使用的方式幾乎同樣。咱們如今來看看涉及到的JNI函數的做用。

獲取jmethodID

jmethodID GetMethodID(JNIEnv *env, jclass clazz,
                        const char *name, const char *sig);
複製代碼

參數

  • jclass clazz: Java類的Class對象,須要經過GetObjectClass()或者FindClass()獲取。
  • const char * name: 方法名,須要經過GetMethodID()獲取。
  • const char * sig: 方法簽名。

若是不知道參數sig的方法簽名如何寫,能夠閱讀JNI函數動態註冊之JNI類型和簽名進行了解。

返回值

  • 若是找到了對應的方法,就會返回一個jmethodID對象,不然返回NULL

注意: 若是要獲取Java類的構造方法,參數const char *name的值爲<init>,參數const char *sig的值爲void (V)。這個是比較特殊的,須要注意。

調用對象的方法

根據Java方法返回的類型的不一樣,JNI有不一樣的函數來調用Java對象的方法。不過基本形式有三種

NativeType Call<type>Method(JNIEnv *env, jobject obj,
jmethodID methodID, ...);

NativeType Call<type>MethodA(JNIEnv *env, jobject obj,
jmethodID methodID, const jvalue *args);

NativeType Call<type>MethodV(JNIEnv *env, jobject obj,
jmethodID methodID, va_list args);
複製代碼

能夠看到這三類函數的區別在於傳參的方式不一樣。

第一個函數和第三個函數其實原理都是同樣的,不過最經常使用的應該就是第一個。

那麼第二個函數就有點意思了,參數使用的是jvalue類型的數組做爲參數。

typedef union jvalue {
    jboolean    z;
    jbyte       b;
    jchar       c;
    jshort      s;
    jint        i;
    jlong       j;
    jfloat      f;
    jdouble     d;
    jobject     l;
} jvalue;
複製代碼

jvalue是一個聯合體,聯合體中定義Java基本類型和引用類型的變量,所以就能夠以數組的行爲來傳遞參數,這下就明白了吧。

咱們以第一個函數爲例來講明下具體的函數原型。

若是一個Java方法返回的是基本類型,例如int,那麼函數原型以下

jint CallIntMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
複製代碼

而若是一個Java方法的返回類型是引用類型,例如String,那麼函數原型以下

jobject CallObjectMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
複製代碼

固然,若是一個Java方法的返回類型爲void,那麼函數原型以下

void CallVoidMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
複製代碼

參數

  • jobject obj: 表明Java對象
  • jmethodID methodID: 對象的方法,經過GetMethodID()獲取
  • ...: 可變參數,表明須要傳入給Java方法的參數。

完整的實現

static void com_bxll_jnidemo_Person_init(JNIEnv * env, jobject obj) {
    jclass clazz_person = env->GetObjectClass(obj);
    jfieldID fieldID_mAge = env->GetFieldID(clazz_person, "mAge", "I");
    // 判斷是否能獲取到變量
    if (fieldID_mAge == NULL)
    {
        std::cout << "Can't find Person.mAge.'" << std::endl;
        return;
    }
    jint age = env->GetIntField(obj, fieldID_mAge);
    std::cout << "Age: " << age << std::endl;
    env->SetIntField(obj, fieldID_mAge, 18);


    jmethodID methodID_setName = env->GetMethodID(clazz_person, "setName", "(Ljava/lang/String;)V");
    // 判斷是否能獲取到方法
    if (methodID_setName == NULL)
    {
        std::cout << "Can't find setName method." << std::endl;
        return;
    }
    jstring name = env->NewStringUTF("David");
    env->CallVoidMethod(obj, methodID_setName, name);

    // 手動釋放局部引用
    env->DeleteLocalRef(name);
    env->DeleteLocalRef(clazz_person);
}
複製代碼

在這個完整的實現中,咱們增長了判空,以及手動釋放局部引用,這是一個比較好的JNI開發習慣。

總結

經過調用Java對象的native方法,虛擬機會調用相應的本地函數,而本地函數的第二個參數必定爲jobject object,表明的就是Java對象。因而能夠經過JNI技術,訪問這個對象的成員(變量和方法)。

在下一篇文章,咱們就來說解如何在JNI層訪問類的靜態成員(變量和方法)。若是你理解了本文的內容,那麼下一篇文章就只是一個拓展。

參考

docs.oracle.com/javase/7/do…

相關文章
相關標籤/搜索