JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量

转载请注明出处:http://blog.csdn.net/xyang81/article/details/42836783

在上一章中我们学习到了如何在本地代码中访问任意Java类中的静态方法和实例方法,本章我们也通过一个示例来学习Java中的实例变量和静态变量,在本地代码中如何来访问和修改。静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过【类名.变量名】来访问。实例变量也称为成员变量(属性),每个实例都拥有一份实例变量数据的拷贝,它们之间修改后的数据互不影响。下面看一个例子:

package com.study.jnilearn;

/**
 * C/C++访问类的实例变量和静态变量
 * @author yangxin
 */
public class AccessField {

	private native static void accessInstanceField(ClassField obj);

	private native static void accessStaticField();

	public static void main(String[] args) {
		ClassField obj = new ClassField();
		obj.setNum(10);
		obj.setStr("Hello");

		// 本地代码访问和修改ClassField为中的静态属性num
		accessStaticField();
		accessInstanceField(obj);

		// 输出本地代码修改过后的值
		System.out.println("In Java--->ClassField.num = " + obj.getNum());
		System.out.println("In Java--->ClassField.str = " + obj.getStr());
	}

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

}

AccessField是程序的入口类,定义了两个native方法:accessInstanceField和accessStaticField,分别用于演示在本地代码中访问Java类中的实例变量和静态变量。其中accessInstaceField方法访问的是类的实例变量,所以该方法需要一个ClassField实例作为形参,用于访问该对象中的实例变量。

package com.study.jnilearn;

/**
 * ClassField.java
 * 用于本地代码访问和修改该类的属性
 * @author yangxin
 *
 */
public class ClassField {

	private static int num;

	private String str;

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		ClassField.num = num;
	}

	public String getStr() {
		return str;
	}

	public void setStr(String str) {
		this.str = str;
	}
}

在本例中没有将实例变量和静态变量定义在程序入口类中,新建了一个ClassField的类来定义类的属性,目的是为了加深在C/C++代码中可以访问任意Java类中的属性。在这个类中定义了一个int类型的实例变量num,和一个java.lang.String类型的静态变量str。这两个变量会被本地代码访问和修改。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_AccessField */

#ifndef _Included_com_study_jnilearn_AccessField
#define _Included_com_study_jnilearn_AccessField
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_study_jnilearn_AccessField
 * Method:    accessInstanceField
 * Signature: (Lcom/study/jnilearn/ClassField;)V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessInstanceField
  (JNIEnv *, jclass, jobject);

/*
 * Class:     com_study_jnilearn_AccessField
 * Method:    accessStaticField
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessStaticField
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

以上代码是程序入口类AccessField.class为native方法生成的本地代码函数原型头文件

// AccessField.c

#include "com_study_jnilearn_AccessField.h"

/*
 * Class:     com_study_jnilearn_AccessField
 * Method:    accessInstanceField
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessInstanceField
(JNIEnv *env, jclass cls, jobject obj)
{
    jclass clazz;
    jfieldID fid;
    jstring j_str;
    jstring j_newStr;
    const char *c_str = NULL;

    // 1.获取AccessField类的Class引用
    clazz = (*env)->GetObjectClass(env,obj);
    if (clazz == NULL) {
        return;
    }

    // 2. 获取AccessField类实例变量str的属性ID
    fid = (*env)->GetFieldID(env,clazz,"str", "Ljava/lang/String;");
    if (clazz == NULL) {
        return;
    }

    // 3. 获取实例变量str的值
    j_str = (jstring)(*env)->GetObjectField(env,obj,fid);

    // 4. 将unicode编码的java字符串转换成C风格字符串
    c_str = (*env)->GetStringUTFChars(env,j_str,NULL);
    if (c_str == NULL) {
        return;
    }
    printf("In C--->ClassField.str = %s\n", c_str);
    (*env)->ReleaseStringUTFChars(env, j_str, c_str);

    // 5. 修改实例变量str的值
    j_newStr = (*env)->NewStringUTF(env, "This is C String");
    if (j_newStr == NULL) {
        return;
    }

    (*env)->SetObjectField(env, obj, fid, j_newStr);

    // 6.删除局部引用
    (*env)->DeleteLocalRef(env, clazz);
    (*env)->DeleteLocalRef(env, j_str);
    (*env)->DeleteLocalRef(env, j_newStr);
}

/*
 * Class:     com_study_jnilearn_AccessField
 * Method:    accessStaticField
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessField_accessStaticField
(JNIEnv *env, jclass cls)
{
    jclass clazz;
    jfieldID fid;
    jint num;

    //1.获取ClassField类的Class引用
    clazz = (*env)->FindClass(env,"com/study/jnilearn/ClassField");
    if (clazz == NULL) {    // 错误处理
        return;
    }

    //2.获取ClassField类静态变量num的属性ID
    fid = (*env)->GetStaticFieldID(env, clazz, "num", "I");
    if (fid == NULL) {
        return;
    }

    // 3.获取静态变量num的值
    num = (*env)->GetStaticIntField(env,clazz,fid);
    printf("In C--->ClassField.num = %d\n", num);

    // 4.修改静态变量num的值
    (*env)->SetStaticIntField(env, clazz, fid, 80);

    // 删除属部引用
    (*env)->DeleteLocalRef(env,clazz);
}

以上代码是对头文件中函数原型的实现。


运行程序,输出结果如下:

代码解析:


一、访问实例变量

在main方法中,通过调用accessInstanceField()方法来调用本地函数Java_com_study_jnilearn_AccessField_accessInstanceField,定位到函数32行:

j_str = (jstring)(*env)->GetObjectField(env,obj,fid); 

该函数就是用于获取ClassField对象中num的值。下面是函数的原型:

jobject (JNICALL *GetObjectField) (JNIEnv *env, jobject obj, jfieldID fieldID);

因为实例变量str是String类型,属于引用类型。在JNI中获取引用类型字段的值,调用GetObjectField函数获取。同样的,获取其它类型字段值的函数还有GetIntField,GetFloatField,GetDoubleField,GetBooleanField等。这些函数有一个共同点,函数参数都是一样的,只是函数名不同,我们只需学习其中一个函数如何调用即可,依次类推,就自然知道其它函数的使用方法。

GetObjectField函数接受3个参数,env是JNI函数表指针,obj是实例变量所属的对象,fieldID是变量的ID(也称为属性描述符或签名),和上一章中方法描述符是同一个意思。env和obj参数从Java_com_study_jnilearn_AccessField_accessInstanceField函数形参列表中可以得到,那fieldID怎么获取呢?了解Java反射的童鞋应该知道,在Java中任何一个类的.class字节码文件被加载到内存中之后,该class子节码文件统一使用Class类来表示该类的一个引用(相当于Java中所有类的基类是Object一样)。然后就可以从该类的Class引用中动态的获取类中的任意方法和属性。注意:Class类在Java SDK继承体系中是一个独立的类,没有继承自Object。请看下面的例子,通过Java反射机制,动态的获取一个类的私有实例变量的值:

public static void main(String[] args) throws Exception {
	ClassField obj = new ClassField();
	obj.setStr("YangXin");
	// 获取ClassField字节码对象的Class引用
	Class<?> clazz = obj.getClass();
	// 获取str属性
	Field field = clazz.getDeclaredField("str");
	// 取消权限检查,因为Java语法规定,非public属性是无法在外部访问的
	field.setAccessible(true);
	// 获取obj对象中的str属性的值
	String str = (String)field.get(obj);
	System.out.println("str = " + str);
}

运行程序后,输出结果当然是打印出str属性的值“YangXin”。所以我们在本地代码中调用JNI函数访问Java对象中某一个属性的时候,首先第一步就是要获取该对象的Class引用,然后在Class中查找需要访问的字段ID,最后调用JNI函数的GetXXXField系列函数,获取字段(属性)的值。上例中,首先调用GetObjectClass函数获取ClassField的Class引用:

clazz = (*env)->GetObjectClass(env,obj);

然后调用GetFieldID函数从Class引用中获取字段的ID

fid = (*env)->GetFieldID(env,clazz,"str", "Ljava/lang/String;");

最后调用GetObjectField函数,传入实例对象和字段ID,获取属性的值

j_str = (jstring)(*env)->GetObjectField(env,obj,fid);

调用SetXXXField系列函数,可以修改实例属性的值,最后一个参数为属性的值。引用类型全部调用SetObjectField函数,基本类型调用SetIntField、SetDoubleField、SetBooleanField等

(*env)->SetObjectField(env, obj, fid, j_newStr);

二、访问静态变量

访问静态变量和实例变量不同的是,获取字段ID使用GetStaticFieldID,获取和修改字段的值使用Get/SetStaticXXXField系列函数,比如上例中获取和修改静态变量num:

// 3.获取静态变量num的值
num = (*env)->GetStaticIntField(env,clazz,fid);
// 4.修改静态变量num的值
(*env)->SetStaticIntField(env, clazz, fid, 80);

总结:

1、由于JNI函数是直接操作JVM中的数据结构,不受Java访问修饰符的限制。即,在本地代码中可以调用JNI函数可以访问Java对象中的非public属性和方法

2、访问和修改实例变量操作步聚:

1>、调用GetObjectClass函数获取实例对象的Class引用

2>、调用GetFieldID函数获取Class引用中某个实例变量的ID

3>、调用GetXXXField函数获取变量的值,需要传入实例变量所属对象和变量ID

4>、调用SetXXXField函数修改变量的值,需要传入实例变量所属对象、变量ID和变量的值

3、访问和修改静态变量操作步聚:
   1>、调用FindClass函数获取类的Class引用

2>、调用GetStaticFieldID函数获取Class引用中某个静态变量ID

3>、调用GetStaticXXXField函数获取静态变量的值,需要传入变量所属Class的引用和变量ID

4>、调用SetStaticXXXField函数设置静态变量的值,需要传入变量所属Class的引用、变量ID和变量的值

示例代码下载地址:https://code.csdn.net/xyang81/jnilearn

时间: 2024-10-26 09:08:45

JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量的相关文章

JNI/NDK开发指南(五)——访问数组(基本类型数组与对象数组)

转载请注明出处:http://blog.csdn.net/xyang81/article/details/42346165 JNI中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是JNI的基本数据类型,可以直接访问.而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作一样,不能直接访问Java传递给JNI层的数组,必须选择合适的JNI函数来访问和设置Java层的数组对象.阅读此文假设你已经了解了JNI与Java数据类型的映射关系,如果还不了解

JNI/NDK开发指南(八)——调用构造方法和父类实例方法

转载请注明出处:http://blog.csdn.net/xyang81/article/details/44002089 在第6章我们学习到了在Native层如何调用Java静态方法和实例方法,其中调用实例方法的示例代码中也提到了调用构造函数来实始化一个对象,但没有详细介绍,一带而过了.还没有阅读过的同学请移步<JNI/NDK开发指南(六)--C/C++访问Java实例方法和静态方法>阅读.这章详细来介绍下初始一个对象的两种方式,以及如何调用子类对象重写的父类实例方法. 我们先回过一下,在J

JNI/NDK开发指南(开山篇)

转载请注明出处:http://blog.csdn.net/xyang81/article/details/41759643 相信很多做过Java或Android开发的朋友经常会接触到JNI方面的技术,由其做过Android的朋友,为了应用的安全性,会将一些复杂的逻辑和算法通过本地代码(C或C++)来实现,然后打包成so动态库文件,并提供Java接口供应用层调用,这么做的目的主要就是为了提供应用的安全性,防止被反编译后被不法分子分析应用的逻辑.当然打包成so也不能说完全安全了,只是相对反编译Jav

JNI/NDK开发指南(十)——JNI局部引用、全局引用和弱全局引用

转载请注明出处:http://blog.csdn.net/xyang81/article/details/44657385 ????这篇文章比较偏理论,详细介绍了在编写本地代码时三种引用的使用场景和注意事项.可能看起来有点枯燥,但引用是在JNI中最容易出错的一个点,如果使用不当,容易使程序造成内存溢出,程序崩溃等现象.所以讲得比较细,有些地方看起来可能比较啰嗦,还请轻啪!下一篇文章会写一个在Android由于JNI引用使用不当,造成局部引用表溢出而导致程序闪退的案例,请关注! ????做Java

JNI/NDK开发指南(六)——C/C++访问Java实例方法和静态方法

转载请注明出处:http://blog.csdn.net/xyang81/article/details/42582213 通过前面5章的学习,我们知道了如何通过JNI函数来访问JVM中的基本数据类型.字符串和数组这些数据类型.下一步我们来学习本地代码如何与JVM中任意对象的属性和方法进行交互.比如本地代码调用Java层某个对象的方法或属性,也就是通常我们所说的来自C/C++层本地函数的callback(回调).这个知识点分2篇文章分别介绍,本篇先介绍方法回调,在第七章中介绍本地代码访问Java

JNI/NDK开发指南(四)——字符串处理

转载请注明出处:http://blog.csdn.net/xyang81/article/details/42066665 从第三章中可以看出JNI中的基本类型和Java中的基本类型都是一一对应的,接下来先看一下JNI的基本类型定义: typedef unsigned char jboolean; typedef unsigned short jchar; typedef short jshort; typedef float jfloat; typedef double jdouble; ty

JNI/NDK开发指南(九)——JNI调用性能测试及优化

转载请注明出处:http://blog.csdn.net/xyang81/article/details/44279725 在前面几章我们学习到了,在Java中声明一个native方法,然后生成本地接口的函数原型声明,再用C/C++实现这些函数,并生成对应平台的动态共享库放到Java程序的类路径下,最后在Java程序中调用声明的native方法就间接的调用到了C/C++编写的函数了,在C/C++中写的程序可以避开JVM的内存开销过大的限制.处理高性能的计算.调用系统服务等功能.同时也学习到了在本

JNI/NDK开发指南(一)—— JNI开发流程及HelloWorld

转载请注明出处:http://blog.csdn.net/xyang81/article/details/41777471 JNI全称是Java Native Interface(Java本地接口)单词首字母的缩写,本地接口就是指用C和C++开发的接口.由于JNI是JVM规范中的一部份,因此可以将我们写的JNI程序在任何实现了JNI规范的Java虚拟机中运行.同时,这个特性使我们可以复用以前用C/C++写的大量代码. 开发JNI程序会受到系统环境的限制,因为用C/C++语言写出来的代码或模块,编

JNI/NDK开发指南(九)——JNI调用性能測试及优化

转载请注明出处:http://blog.csdn.net/xyang81/article/details/44279725 在前面几章我们学习到了.在Java中声明一个native方法,然后生成本地接口的函数原型声明.再用C/C++实现这些函数,并生成相应平台的动态共享库放到Java程序的类路径下.最后在Java程序中调用声明的native方法就间接的调用到了C/C++编写的函数了.在C/C++中写的程序能够避开JVM的内存开销过大的限制.处理高性能的计算.调用系统服务等功能. 同一时候也学习到