Calling static and instance methods from the native code

The previous recipe covers how to access Java fields in NDK. Besides fields, a Java class also has methods. This recipe focuses on calling static and instance methods from JNI.

Getting ready

The code examples require a basic understanding of the JNI primitive types, strings, classes, and instance objects. It is better to make sure you have read the following recipes before going through this recipe:

  • Passing parameters and receiving returns in primitive types
  • Manipulating strings in JNI
  • Manipulating classes in JNI
  • Manipulating objects in JNI
  • Accessing Java static and instance fields in native code

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

How to do it…

The following steps can be followed to create a sample Android project that illustrates how to call static and instance methods from the native code:

  1. Create a project named CallingMethods. Set the package name as cookbook.chapter2. Create an activity named CallingMethodsActivity. 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 callmethod.c under the jni folder, then implement the native methods AccessStaticMethodDemo, AccessInstanceMethodDemo, and MethodReflectionDemo.
  3. Modify CallingMethodsActivity.java by adding code to load the native library, declare the native methods, and invoke them.
  4. Create a Dummy class with an integer instance field named value and an integer static field named value2. In addition, create a DummySub class that extends Dummy with an additional String field called name.
  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 illustrates how to call the Java static and instance methods from the native code:

  • jmethodID data type: Similar to jfieldID, jmethodID is a regular C pointer pointing to a data structure with details hidden from the developers. JNI provides functions to convert the java.lang.reflect.Method instance to jmethodID and vice versa.
  • Method descriptor: This is a modified UTF-8 string used to represent the input (input arguments) data types and output (return type) data type of the method. Method descriptors are formed by grouping all field descriptors of its input arguments inside a "()", and appending the field descriptor of the return type. If the return type is void, we should use "V". If there's no input arguments, we should simply use "()", followed by the field descriptor of the return type. For constructors, "V" should be used to represent the return type. The following table lists a few Java methods and their corresponding method descriptors:

    Java method

    Method descriptor

    Dummy(int pValue)

    (I)V

    String getName()

    ()Ljava/lang/String;

    void setName(String pName)

    (Ljava/lang/String;)V

    lont f(byte[] bytes, Dummy dummy)

    ([BLcookbook/chapter2/Dummy;)J

  • Calling static methods: JNI provides four sets of functions for native code to call Java methods. Their prototypes are as follows:
    jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    
    <NativeType> CallStatic<Type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
    
    <NativeType> CallStatic<Type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
    
    <NativeType> CallStatic<Type>MethodV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args);

    The first function gets the method ID. It accepts a reference clazz to the Java class, a method name in a modified UTF-8 string format, and a method descriptor sig. The other three sets of functions are used to call the static methods. <Type> can be any of the eight primitive types, Void, or Object. It indicates the return type of the method invoked. The methodID argument is the jmethodID returned by the GetStaticMethodID function. The arguments to the Java method are passed one by one in CallStatic<Type>Method, or put into an array of jvalue as CallStatic<Type>MethodA, or put into the va_list structure as CallStatic<Type>MethodV.

    We illustrate the usage of all the four sets of JNI functions in a native method AccessStaticMethodDemo. This method gets the method IDs for the getValue2 and setValue2 static methods of the Dummy class, and invokes these two methods using three different ways to pass the arguments to the called Java method. In CallingMethodsActivity.java, we implement callAccessStaticMethodDemo, which initializes the value2 static field to 100, invokes the native method AccessStaticMethodDemo, and prints the final value2 value on phone screen. The following screenshot shows the logcat output:

    How it works…

    The output of screen looks similar to the following screenshot:

    How it works…

    As shown, the native method firstly got value2 as 100, and it then used three different JNI functions to call the set method to modify the value. Finally, the phone display indicated that the final modified value is reflected in Java code.

  • Calling instance methods: Calling instance methods from the native code is similar to calling static methods. JNI also provides four sets of functions as follows:
    jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    
    <NativeType> Call<Type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
    
    <NativeType> Call<Type>MethodA(JNIEnv *env,jobject obj, jmethodID methodID, jvalue *args);
    
    <NativeType> Call<Type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);

    The usage of these four sets of functions is similar to that of the JNI functions for calling static methods, except that we need to pass a reference to the instance object instead of the class. In addition, JNI provides another three sets of functions for calling instance methods, as follows:

    <NativeType> CallNonvirtual<Type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
    
    <NativeType> CallNonvirtual<Type>MethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, jvalue *args);
    
    <NativeType> CallNonvirtual<Type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args);

    These three sets of methods accept an extra argument clazz as compared to the three sets of functions earlier. The clazz argument can be a reference to the class that obj is instantiated from, or a superclass of obj. A typical use case is to call GetMethodID on a class to obtain a jmethodID. We have a reference to an object of the class's subclass, and then we can use the preceding functions to call the Java method associated by jmethodID with the object reference.

    The usage of all the seven sets of functions is illustrated in a native method AccessInstanceMethodDemo. We used the first four sets of functions to call getName and setName methods of the DummySub class with an object of it. We then used CallNonvirtual<Type>Method to call the getValue and setValue methods, which are defined in the Dummy superclass. In CallingMethodsActivity.java, we implemented the callAccessInstanceMethodDemo method to invoke the AccessInstanceMethodDemo native method. The following screenshot shows the logcat output:

    How it works…

    As the results show, the getName, setName, getValue, and setValue methods are executed successfully.

  • Reflection support for method: Similar to fields, JNI also provides the following two functions to support reflection:
    jmethodID FromReflectedMethod(JNIEnv *env, jobject method);
    
    jobject ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);

    The first function accepts a reference to the java.lang.reflect.Method instance, and returns its corresponding jmethodID. The returned jmethodID value can then be used to call the associated Java method. The second function does the reverse. It accepts a reference to the Java class, jmethodID, and jboolean indicating whether it's a static method or not, and returns a reference to java.lang.reflect.Method. The return value can be used in the Java code to access the corresponding method.

    We illustrate these two JNI functions in native method MethodReflectionDemo. In CallingMethodsActivity.java, we implement the callMethodReflectionDemo method to pass the java.lang.reflect.Method object of getValue to the native code, get the returned setValue java.lang.reflect.Method object, and invoke the setValue method with the returned object.

    The native method outputs the return value of getValue method to logcat as follows:

    How it works…

    The Java code displays the getValue method return values before and after invoking setValue on the phone screen as follows:

    How it works…

    As expected, the native code can access the getValue method with the Method object passed from the Java code, and the Java code can call the setValue method with the Method object returned from the native method.

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

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