Manipulating arrays in JNI

JNI exposes strings, classes, instance objects, and arrays as reference types. This recipe will discuss arrays in JNI.

Getting ready

You should make sure you've read the following recipes before going through this recipe:

  • Managing references in JNI
  • Manipulating classes in JNI

How to do it…

In this section, we will create a sample Android project that demonstrates how to manipulate arrays in JNI.

  1. Create a project named ArrayManipulation. Set the package name as cookbook.chapter2. Create an activity named ArrayManipulationActivity. 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 arraytest.c under the jni folder, then implement the GetArrayLengthDemo, NewObjectArrayDemo, NewIntArrayDemo, GetSetObjectArrayDemo, GetReleaseIntArrayDemo, GetSetIntArrayRegionDemo, and GetReleasePrimitiveArrayCriticalDemo native methods.
  3. Modify ArrayManipulationActivity.java by adding code to load the native library, declare the native methods, and invoke them.
  4. Create a Dummy class with a single integer field named value.
  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…

Arrays are represented by jarray or its subtypes such as jobjectArray and jbooleanArray. Similar to jstring, they cannot be accessed directly by native code like C arrays do. JNI provides various functions for accessing arrays:

  • Create new arrays: JNI provides NewObjectArray and New<Type>Array functions to create arrays for objects and primitive types. Their function prototypes are as follows:
    jarray NewObjectArray(JNIEnv *env, jsize length, jclass elementType, jobject initialElement);
    <ArrayType> New<Type>Array(JNIEnv *env, jsize length);

    We demonstrate the usage of NewObjectArray in the native method NewObjectArrayDemo, where we create 10 instances of the Dummy class. The length parameter of the function indicates the number of objects to create, elementType is a reference to the class, and initialElement is the initialization value that is going to be set for all the created object instances in the array. In the Java code, we implemented the callNewObjectArrayDemo method, which calls the NewObjectArrayDemo native method to create an array of 10 Dummy objects, all with the value field set to 5. The execution result should look similar to the following screenshot:

    How it works…

    As expected, the value field of all the objects created by NewObjectArray is 5.

    The usage of New<Type>Array is shown in the native method NewIntArrayDemo, where we create an array of 10 integers using the JNI function NewIntArray, and then assign a value to each of the integers. All eight primitive types (jboolean, jbyte, jchar, jshort, jint, jlong, jfloat, and jdouble) of JNI have a corresponding New<Type>Array function to create an array of its type. Note that NewIntArrayDemo calls the GetIntArrayElements and ReleaseIntArrayElements JNI functions, which we'll discuss later in this recipe. In the Java code, we implemented a callNewIntArrayDemo method to call NewIntArrayDemo and display the integer array elements on the screen. The execution of callNewIntArrayDemo gives the following result:

    How it works…

    As shown in the screenshot, the integer arrays are assigned with values from 0 to 9.

  • GetArrayLength: This native function has the following prototype:
    jsize GetArrayLength(JNIEnv *env, jarray array);

    It accepts a reference to jarray and returns its length. We demonstrated its usage in the native method GetArrayLengthDemo. In the Java code, we implemented the callGetArrayLengthDemo method, which creates three arrays, including a double array, a Dummy object array, and a two-dimensional array of integers. The method calls the GetArrayLengthDemo native method to find the lengths for the three arrays. We output the array length to logcat in the native method. The sample execution output should look similar to the following screenshot:

    How it works…
  • Access object arrays: JNI provides two functions to access object arrays, namely GetObjectArrayElement and SetObjectArrayElement. As its name suggests, the first one retrieves a reference to an object element of an array, while the second one sets the element of an object array. The two functions have the following prototype:
    jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index);
    void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);

    In the two functions, the argument array refers to the object array and index is the position of the element. While the get function returns a reference to the object element, the set function sets the element according to the value argument.

    We illustrate the usage of the two functions in native method GetSetObjectArrayDemo. The method accepts an object array and an object. It replaces the object at index one with the object received and then returns the original object at index one. In the Java code, we call the callGetSetObjectArrayDemo method to pass an array of three Dummy objects with values of 0, 1, 2, and another Dummy object of value 100 to the native method. The execution result should look similar to the following screenshot:

    How it works…

    As shown, the object at index 1 is replaced by the object with value 100, and the original object of value 1 is returned.

  • Access arrays of primitive types: JNI provides three sets of functions to access arrays of primitive types. We demonstrate them separately using three different native methods, all using jintarray as an example. Arrays of other primitive types are similar to integers.

    Firstly, if we want to create a separate copy of jintarray in a native buffer, or only access a small portion of a large array, GetIntArrayRegion/ SetIntArrayRegion functions are the proper choices. These two functions have the following prototype:

    void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint* buf);
    void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint* buf);

    The two functions accept the same set of input parameters. The argument array refers to the jintArray we operate on, start is the start element position, len indicates the number of elements to get or set, and buf is the native integer buffer. We show the usage of these two functions in a native method called GetSetIntArrayRegionDemo. The method accepts an input jintArray, copies three elements from index 1 to 3 of the array to a native buffer, multiplies their values by 2 at the native buffer, and copies the value back to index 0 to 2.

    In the Java code, we implement the callGetSetIntArrayRegionDemo method to initialize an integer array, pass the array to a native method GetSetIntArrayRegionDemo, and display the before and after values of all the elements. You should see an output similar to the following screenshot:

    How it works…

    The initial values for the five elements were 0, 1, 2, 3, and 4. We copied three elements from index one (1, 2, 3) to the native buffer buf. We then multiplied the values at the native buffer by 2, which made the first three elements at the native buffer 2, 4, and 6. We copied these three values from the native buffer back to the integer array, starting at index 0. The final values for the three elements were therefore 2, 4, and 6, and the last two elements remained unchanged as 3 and 4.

    Secondly, if we want to access a large array, then GetIntArrayElements and ReleaseIntArrayElements are the JNI functions for us. They have the following prototype:

    jint *GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy);
    void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode);

    GetIntArrayElements returns a pointer to the array elements, or NULL in case of a failure. The array input parameter refers to the array we want to access, and isCopy is set to true if a new copy is created after the function call finishes. The returned pointer is valid until ReleaseIntArrayElements is called.

    ReleaseIntArrayElements informs the VM that we don't need access to the array elements any more. The input parameter array refers to the array we operate on, elems is the pointer returned by GetIntArrayElements, and mode indicates the release mode. When isCopy at GetIntArrayElements is set to JNI_TRUE, the changes we make through the returned pointer will be reflected on the jintArray, since we're operating on the same copy. When isCopy is set to JNI_FALSE, the mode parameter determines how the data release is done. Depending upon whether we want to copy values from the native buffer back to the original array, and whether we want to free the elems native buffer, the mode parameters can be 0, JNI_COMMIT, or JNI_ABORT, as follows:

    Copy values back

    Yes

    No

    Free native buffer

    Yes

    0

    JNI_ABORT

    No

    JNI_COMMIT

    -

    We illustrate the two JNI functions with the native method GetReleaseIntArrayDemo. The method accepts an input integer array, obtains a native pointer through GetIntArrayElements, multiplies each element by 2, and finally commits the changes back by ReleaseIntArrayElements with mode set to 0. In the Java code, we implement the callGetReleaseIntArrayDemo method to initialize the input array and invoke the GetReleaseIntArrayDemo native method. The following is a screenshot of the phone display after executing the callGetReleaseIntArrayDemo method:

    How it works…

    As expected, all integer elements in the original array are multiplied by 2.

    The third set of JNI functions are GetPrimitiveArrayCritical and ReleasePrimitiveArrayCritical. The usage of these two functions is similar to that of Get<Type>ArrayElements and Release<Type>ArrayElements, except for one important difference—the code block between the Get and Release methods is a critical region. No other JNI functions or function calls causing the current thread to wait for another thread in the same VM shall be made. These two methods essentially increase the possibility of obtaining an uncopied version of the original primitive array, and therefore improve the performance. We demonstrate the usage of these functions in a native method GetReleasePrimitiveArrayCriticalDemo along with the Java method callGetReleasePrimitiveArrayCriticalDemo. The implementations are similar to the second set of functions calls, and the display result is the same.

..................Content has been hidden....................

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