Accessing Java static and instance fields in the native code

We have demonstrated how to pass parameters of different types to native methods and return data back to Java. This is not the only way of sharing data between the native code and Java code. This recipe covers another method—accessing Java fields from the native code.

Getting ready

We're going to cover how to access Java fields of different types, including primitive types, strings, instance objects, and arrays. The following recipes should be read first before reading this recipe:

  • Passing parameters and receiving returns in primitive types
  • Manipulating strings in JNI
  • Manipulating classes in JNI
  • Manipulating objects in JNI
  • Manipulating arrays in JNI

Readers are also expected to be familiar with Java reflection API.

How to do it…

Follow these steps to create a sample Android project that demonstrates how to access Java static and instance fields from the native code:

  1. Create a project named AccessingFields. Set the package name as cookbook.chapter2. Create an activity named AccessingFieldsActivity. Under the project, create a folder named jni. Refer to the Loading native libraries and registering native methods recipe of this chapter for more detailed instructions.
  2. Create a file named accessfield.c under the jni folder, then implement the AccessStaticFieldDemo, AccessInstanceFieldDemo, and FieldReflectionDemo native methods.
  3. Modify AccessingFieldsActivity.java by adding code to load the native library, declare native methods, and invoke them. In addition, add four instance fields and four static fields.
  4. Create a Dummy class with an integer instance field named value and an integer static field named value2.
  5. Modify the layout XML file, add the Android.mk build file, and build the native library. Refer to steps 8 to 10 of the Loading native libraries and registering native methods recipe of this chapter for more details.
  6. We're now ready to run the project. We'll present the output while discussing each native method, in the following section.

How it works…

This recipe discusses the access of fields (both static and instance fields) in Java from native code:

  • jfieldID data type: jfieldID is a regular C pointer pointing to a data structure with details hidden from developers. We should not confuse it with jobject or its subtypes. jobject is a reference type corresponding to Object in Java, while jfieldID doesn't have such a corresponding type in Java. However, JNI provides functions to convert the java.lang.reflect.Field instance to jfieldID and vice versa.
  • Field descriptor: It refers to the modified UTF-8 string used to represent the field data type. The following table summarizes the Java field types and its corresponding field descriptors:

    Java field type

    Field descriptor

    boolean

    Z

    byte

    B

    char

    C

    short

    S

    int

    I

    long

    J

    float

    F

    double

    D

    String

    Ljava/lang/String;

    Object

    Ljava/lang/Object;

    int[]

    [I

    Dummy[]

    [Lcookbook/chapter2/Dummy;

    Dummy[][]

    [[Lcookbook/chapter2/Dummy;

    As shown in the table, each of the eight primitive types has a single character string as its field descriptor. For objects, the field descriptor starts with "L", followed by the class descriptor (refer to the Manipulating classes in JNI recipe for detailed information) and ends with ";". For arrays, the field descriptor starts with "[", followed by the descriptor for the element type.

  • Accessing static fields: JNI provides three functions to access static fields of a Java class. They have the following prototypes:
    jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    <NativeType> GetStatic<Type>Field(JNIEnv *env,jclass clazz, jfieldID fieldID);
    void SetStatic<Type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID,<NativeType> value);

    To access a static field, the first step is to obtain the field ID, which is done by the first function listed here. In the method prototype, the clazz argument refers to the Java class at which the static field is defined, name indicates the field name, and sig is the field descriptor.

    Once we have the method ID, we can either get or set the field value by calling function two or three. In the function prototype, <Type> can refer to any of the eight Java primitive types or Object, and fieldID is jfieldID returned by calling the first method. For set functions, value is the new value that we want to assign to the field.

    The usage of the preceding three JNI functions are demonstrated in the native method AccessStaticFieldDemo, where we set and get values for an integer field, a string field, an array field, and a Dummy object field. These four fields are defined in the Java class AccessingFieldsActivity. In native code, we output the get values to Android logcat, while in the Java code we display the value set by the native code to the phone screen. The following screenshot shows the logcat output:

    How it works…

    The phone display will look similar to the following screenshot:

    How it works…

    As shown, the values we set at the Java code for the fields can be obtained by the native code; and the values set by the native method are reflected in the Java code.

  • Accessing instance field: Accessing instance fields is similar to accessing static fields. JNI also provides the following three functions for us:
    jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    <NativeType> Get<Type>Field(JNIEnv *env,jobject obj, jfieldID fieldID);
    void Set<Type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, <NativeType> value);

    Again, we need to obtain the field ID first, before we can get and set the values for the field. Instead of passing the class reference to the get and set functions, we should pass the object reference.

    The usage is shown in native method AccessInstanceFieldDemo. Again, we print the values of get in the native code to the logcat and display the modified field values on the phone screen. The following screenshot shows the logcat output:

    How it works…

    The phone display will look similar to the following screenshot:

    How it works…

    A similar interpretation to accessing static fields can be made on the results.

  • Reflection support for field: JNI provides two functions to support the interoperation with the Java Reflection API for Field. They have the following prototypes:
    jfieldID FromReflectedField(JNIEnv *env, jobject field);
    jobject ToReflectedField(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);

    The first function converts java.lang.reflect.Field to jfieldID, and then we can use the set and get JNI functions described previously. The argument field is an instance of java.lang.reflect.Field.

    The second function does the reverse. It accepts a class reference, a jfieldID, and a jboolean variable indicating whether it is a static or an instance field. The function returns a reference to an object of java.lang.reflect.Field.

    The usage of these two functions is demonstrated in the native method FieldReflectionDemo. We used the Field instance passed from the caller to access the field value, and then returned a Field instance for another field. In the Java method callFieldReflectionDemo, we pass the Field instance to the native code and use the returned Field instance to obtain the field value. The native code outputs the field value to logcat as follows:

    How it works…

    The Java code displays the value for another field on the phone screen as follows:

    How it works…
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset