java jni 入门4 - 访问实例域,静态域

参考:《Java核心技术 卷II:高级特性》第12章 本地方法


######################################################################


之前使用的本地方法都是带有数字或字符串参数的静态方法。下面考虑操作对象的本地方法。


访问实例域:

一个java函数:

	
	public void raiseSalary(double byPercent) {
		salary += 1+ byPercent / 100;
	}
变量salary为全局变量


重写代码,使其成为一个本地方法。与此前的本地方法不同,它并不是一个静态方法。

运行javah给出以下原型:

JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv *, jobject, jdouble)
注意,第二个参数不再是jclass类型而是jobject类型。实际上,它和this等价。 静态方法得到的是类的引用,而非静态方法的得到的是隐含的this参数对象的引用


在Java1.0中“原始的”Java到C的绑定中,可以直接访问对象数据域。然而,直接访问要求虚拟机暴露它们的内部数据布局。基于这个原因,JNI要求程序员通过调用特殊的JNI函数来获取和设置数据的值


本地示例中,使用GetDoubleField和SetDoubleField函数,因为salary是double类型的。对于其他类型,可以使用的函数有:GetIntField / SetIntField , GetObjectField / SetObjectField等等。其一般语法是:

x=(*env)->GetXxxField(env, this_obj, fieldID)
(*env)->SetXxxField(env, this_obj, fieldID, x)
这里,class代表Class类型Java对象的一个值,fieldID是一个特殊类型的值,jfield标识结构中的一个域,Xxx代表Java数据类型(Object, Boolean, Byte或其他)。

有两种方法得到class对象。

GetObjectClass函数返回任意对象的类。例如

jclass class_Empolyee = (*env)->GetObjectClass(env, this_obj)
FindClass函数可以以字符串形式来指定类名(要以反斜线/代替句号作为包名之间的分隔符)。例如

jclass class_String = (*env)->FindClass(env, "java/lang/String")


使用GetFieldID函数来获得fieldID。必须提供域的名字,它的签名以及它的类型的编码。例如,从salary域得到域ID的代码:

jfieldID id_salary=(*env)->GetFieldID(env, class_Employee, "salary", "D")
字符串"D"表示类型是double


JNI的设计者不想把数据域直接暴露在外,所以不得不提供获取和设置数据域值的函数。为了使这些函数的开销最小化,从域名计算域ID(代价最大的一个步骤)被分解出来作为单独的一步操作。也就是说,如果反复地获取和设置一个特定的域,计算域标识符的开销只有一次。

下面代码以本地方法形式重新实现了函数raiseSalary方法

JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv *env, jobject this_obj, jdouble byPercent) {
	/* get the class */
	jclass class_Employee = (*env)->GetObjectClass(env, this_obj);
	
	/* get the field ID */
	jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D");	
	
	/* get the field value */
	jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary);
	
	salary *= 1 + byPercent / 100;
		
	/* set the field value */
	(*env)->SetDoubleField(env, this_obj, id_salary, salary);
}


注意,类引用只在本地方法返回之前有效。因此,不能在代码中缓存GetObjectClass的返回值。不要将类引用保存下来以供以后的方法调用重复使用。在每次执行本地方法时必须都调用GetObjectClass。如果无法忍受这一点,必须调用NewGlobalRef来锁定该引用:

static jclass class_X = 0;
static jfieldID id_a;
......
if (class_x == 0) 
{
     jclass cx = (*env)->GetObjectClass(env, obj);
     class_X = (*env)->NewGlobalRef(env, cx);
     id_a = (*env)->GetFieldID(env, cls, "a", "...");
}

这样,就可以在后面的调用中使用类引用和域ID。当你结束对类的使用时,务必调用:(*env)->DeleteGlobalRef(env, Class_X);


以下是完整的测试程序和Employee类的java代码

EmployeeTest.java

/**
 * @time 15-11-11
 * @author zj
*/
public class EmployeeTest {
	public static void main(String[] args) {
		Employee[] staff = new Employee[3];
		staff[0] = new Employee("Harry Hacker", 35000);
		staff[1] = new Employee("Carl Cracker", 75000);
		staff[2] = new Employee("Tony Tester", 38000);

		for (Employee e : staff) {
			e.raiseSalary(5);
		}
		for (Employee e : staff) {
			e.print();
		}
	}
}

Employee.java

/**
 * @time 15-11-11
 * @author zj
*/
class Employee {
	public Employee(String n, double s) {
		name = n;
		salary = s;
	}

	public native void raiseSalary(double byPercent);

	public void print() {
		System.out.println(name+" "+salary);
	}

	private String name;
	private double salary;
	
	static {
		System.loadLibrary("Employee");
	}
}

Employee.c

/**
 * @time 15-11-11
 * @author zj
*/
#include "Employee.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_Employee_raiseSalary
  (JNIEnv *env, jobject this_obj, jdouble byPercent) {
	/* get the class */
	jclass class_Employee = (*env)->GetObjectClass(env, this_obj);
	
	/* get the field ID */
	jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D");

	/* get the field value */
	jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary);

	salary *= 1 + byPercent/100;

	/* set the field value */
	(*env)->SetDoubleField(env, this_obj, id_salary, salary);
}

java jni 入门4 - 访问实例域,静态域_第1张图片




######################################################3


访问静态域


访问静态域和访问非静态域类似,只不过要使用GetStaticFieldID和GetStaticXxxField/SetStaticXxxFieldID函数。

只有两个区别:

1.由于没有对象,必须使用FindClass代替GetObjectClass来获得类引用。

2.访问域时,要提供类而非实例对象。

例如,下面给出如何得到System.out的引用的代码:

/* get the class */
jclass class_System = (*env)->FindClass(env, "java/lang/System");

/* get the field ID */
jfieldID id_out = (*env)->GetStaticFieldID(env, class_System, "out", "Ljava/io/PrintStream;");

/* get the field vlaue */
jobject obj_out = (*env)->GetStaticObjectField(env, class_System, id_out);


#######################################################################3


访问域相关函数:

jfieldID GetFieldID(JNIEnv *env, jclass cl, const char name[], const char fieldSignature[])
返回类中一个域的标识符


Xxx GetXxxField(JNIEnv *env, jobject obj, jfieldID id)
返回域的值。 域类型Xxx是Object, Boolean, Byte, Char, Short, Int, Long, Float 或 Double之一


void setXxxField(JNIEnv *env, jobject obj, jfieldID id, Xxx value)
把某个域设置为一个新值。域类型Xxx是Object, Boolean, Byte, Char, Short, Int, Long, Float或Double之一


jfieldID GetStaticFieldID(JNIEnv *env, jclass cl, const char name[], const char fieldsignature[])
返回某类型的一个静态域的标识符


Xxx GetStaticXxxField(JNIEnv *env, jclass cl, jfieldID id)
返回某静态的值。域类型Xxx是Object, Boolean, Byte, Char, Short, Int, Long, Float或Double之一。


void SetStaticXxxField(JNIEnv *env, jclass cl, jfieldID id, Xxx value)
把某个静态域设置为一个新值。域类型Xxx是Object, Boolean, Byte, Char, Short, Int, Long, Float或Double之一


你可能感兴趣的:(java,jni)