|
All the native methods that you saw so far were static methods with number and string parameters. We next consider native methods that operate on objects. As an exercise, we implement a method of the Employee class that was introduced in Volume 1, Chapter 4, using a native method. Again, this is not something you would normally want to do, but it does illustrate how to access fields from a native method when you need to do so. Accessing Instance FieldsIn order to see how to access instance fields from a native method, we will reimplement the raiseSalary method. In the Java programming language, the code was simple. public void raiseSalary(double byPercent) { salary *= 1 + byPercent / 100; } Let us rewrite this as a native method. Unlike the previous examples of native methods, this is not a static method. Running javah gives the following prototype: JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv *, jobject, jdouble); Note the second argument. It is no longer of type jclass but of type jobject. In fact, it is the equivalent of the this reference. Static methods obtain a reference to the class, whereas non-static methods obtain a reference to the implicit this argument object. Now we access the salary field of the implicit argument. In the "raw" Java-to-C binding of Java 1.0, this was easya programmer could directly access object data fields. However, direct access requires all virtual machines to expose their internal data layout. For that reason, the JNI requires programmers to get and set the values of data fields by calling special JNI functions. In our case, we need to use the GetdoubleField and SetDoubleField functions because the type of salary is a double. There are other functionsGetIntField/SetIntField, GetObjectField/SetObjectField, and so onfor other field types. The general syntax is:
Here, class is a value that represents a Java object of type Class, fieldID is a value of a special type, jfieldID, that identifies a field in a structure, and Xxx represents a Java data type (Object, Boolean, Byte, and so on). There are two ways to obtain the class object. The GetObjectClass function returns the class of any object. For example: jclass class_Employee = (*env)->GetObjectClass(env, this_obj); The FindClass function lets you specify the class name as a string (curiously, with / instead of periods as package name separators). jclass class_String = (*env)->FindClass(env, "java/lang/String"); Use the GetFieldID function to obtain the fieldID. You must supply the name of the field and its signature, an encoding of its type. For example, here is the code to obtain the field ID of the salary field. jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D"); The string "D" denotes the type double. You learn the complete rules for encoding signatures in the next section. You may be thinking that accessing a data field seems quite convoluted. The designers of the JNI did not want to expose the data fields directly, so they had to supply functions for getting and setting field values. To minimize the cost of these functions, computing the field ID from the field namewhich is the most expensive stepis factored out into a separate step. That is, if you repeatedly get and set the value of a particular field, you incur only once the cost of computing the field identifier. Let us put all the pieces together. The following code reimplements the raiseSalary method as a native method.
CAUTION
Examples 11-11 and 11-12 show the Java code for a test program and the Employee class. Example 11-13 contains the C code for the native raiseSalary method. Example 11-11. EmployeeTest.java1. public class EmployeeTest 2. { 3. public static void main(String[] args) 4. { 5. Employee[] staff = new Employee[3]; 6. 7. staff[0] = new Employee("Harry Hacker", 35000); 8. staff[1] = new Employee("Carl Cracker", 75000); 9. staff[2] = new Employee("Tony Tester", 38000); 10. 11. int i; 12. for (Employee e : staff) e.raiseSalary(5); 13. for (Employee e : staff) e.print(); 14. } 15. } Example 11-12. Employee.java1. public class Employee 2. { 3. public Employee(String n, double s) 4. { 5. name = n; 6. salary = s; 7. } 8. 9. public native void raiseSalary(double byPercent); 10. 11. public void print() 12. { 13. System.out.println(name + " " + salary); 14. } 15. 16. private String name; 17. private double salary; 18. 19. static 20. { 21. System.loadLibrary("Employee"); 22. } 23. } Example 11-13. Employee.c[View full width] 1. #include "Employee.h" 2. 3. #include <stdio.h> 4. 5. JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv* env, jobject this_obj, jdouble byPercent) 6. { 7. /* get the class */ 8. jclass class_Employee = (*env)->GetObjectClass(env, this_obj); 9. 10. /* get the field ID */ 11. jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D"); 12. 13. /* get the field value */ 14. jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary); 15. 16. salary *= 1 + byPercent / 100; 17. 18. /* set the field value */ 19. (*env)->SetDoubleField(env, this_obj, id_salary, salary); 20. } Accessing Instance Fields
Accessing Static FieldsAccessing static fields is similar to accessing non-static fields. You use the GetStaticFieldID and GetStaticXxxField/SetStaticXxxField functions. They work almost identically to their non-static counterpart, with two differences:
For example, here is how you can get a reference to 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 value */ jobject obj_out = (*env)->GetStaticObjectField(env, class_System, id_out); |
|