参考:《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")
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", "..."); }
以下是完整的测试程序和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(); } } }
/** * @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"); } }
/** * @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); }
访问静态域
访问静态域和访问非静态域类似,只不过要使用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);
访问域相关函数:
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之一