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…

相关文章
相关标签/搜索