Loading native libraries and registering native methods

Native code is usually compiled into a shared library and loaded before the native methods can be called. This recipe covers how to load native libraries and register native methods.

Getting ready

Please read the recipes in Chapter 1, Hello NDK, to set up the Android NDK development environment if you haven't done so already.

How to do it…

The following steps will show you how to build an Android application that demonstrates loading native libraries and registering native methods:

  1. Start Eclipse, select File | New | Android Project. Enter the value for Project Name as NativeMethodsRegister. Select Create new project in workspace. Then, click on Next.
  2. In the next window, select the latest version of Android SDK, then click on Next to go to the next window.
  3. Specify the package name as cookbook.chapter2. Select the Create Activity checkbox, and specify the name as NativeMethodsRegisterActivity. Set the value for Minimum SDK as 5 (Android 2.0). Then, click on Finish.
  4. In Eclipse Package Explorer, right-click on the NativeMethodsRegister project, then select New | Folder. Enter the name jni in the pop-up window, then click on Finish.
  5. Right-click on the newly created jni folder under the NativeMethodsRegister project, then select New | File. Enter nativetest.c as the value for File name, then click on Finish.
  6. Add the following code to nativetest.c:
    #include <android/log.h>
    #include <stdio.h>
    
    jint NativeAddition(JNIEnv *pEnv, jobject pObj, jint pa, jint pb) {
      return pa+pb;
    }
    
    jint NativeMultiplication(JNIEnv *pEnv, jobject pObj, jint pa, jint pb) {
      return pa*pb;
    }
    
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pVm, void* reserved)
    {
        JNIEnv* env;
        if ((*pVm)->GetEnv(pVm, (void **)&env, JNI_VERSION_1_6)) {
         return -1;
      }
        JNINativeMethod nm[2];
        nm[0].name = "NativeAddition";
        nm[0].signature = "(II)I";
        nm[0].fnPtr = NativeAddition;
        nm[1].name = "NativeMultiplication";
        nm[1].signature = "(II)I";
        nm[1].fnPtr = NativeMultiplication;
        jclass cls = (*env)->FindClass(env, "cookbook/chapter2/NativeMethodRegisterActivity");
        // Register methods with env->RegisterNatives.
        (*env)->RegisterNatives(env, cls, nm, 2);
        return JNI_VERSION_1_6;
    }
  7. Add the following code to load the native shared library and define native methods to NativeMethodRegisterActivity.java:
    public class NativeMethodRegisterActivity extends Activity {
        … …
          private void callNativeMethods() {
            int a = 10, b = 100;
              int c = NativeAddition(a, b);
              tv.setText(a + "+" + b + "=" + c);
              c = NativeMultiplication(a, b);
              tv.append("
    " + a + "x" + b + "=" + c);
          }
          private native int NativeAddition(int a, int b);
          private native int NativeMultiplication(int a, int b);
          static {
            //use either of the two methods below
    //System.loadLibrary("NativeRegister");
              System.load("/data/data/cookbook.chapter2/lib/libNativeRegister.so");
          }
    }
  8. Change TextView in the res/layout/activity_native_method_register.xml file as follows:
    <TextView
            android:id="@+id/display_res"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:padding="@dimen/padding_medium"
            android:text="@string/hello_world"
            tools:context=".NativeMethodRegisterActivity" />
  9. Create a file named Android.mk under the jni folder with the following content:
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := NativeRegister
    LOCAL_SRC_FILES := nativetest.c
    LOCAL_LDLIBS := -llog
    include $(BUILD_SHARED_LIBRARY)
  10. Start a terminal, go to the jni folder under our project, and type ndk-build to build the native library.
  11. Run the project on an Android device or emulator. You should see something similar to the following screenshot:
    How to do it…

How it works…

This recipe describes how to load a native library and register native methods:

  • Loading Native Library: The java.lang.System class provides two methods to load native libraries, namely loadLibrary and load. loadLibrary accepts a library name without the prefix and file extension. For example, if we want to load the Android native library compiled as libNativeRegister.so in our sample project, we use System.loadLibrary("NativeRegister"). The System.load method is different. It requires the full path of the native library. In our sample project, we can use System.load("/data/data/cookbook.chapter2/lib/libNativeRegister.so") to load the native library. The System.load method can be handy when we want to switch between different versions of a native library, since it allows us to specify the full library path.

    We demonstrated the usage of both the methods in the static initializer of the NativeMethodRegisterActivity.java class. Note that only one method should be enabled when we build and run the sample application.

  • JNIEnv Interface Pointer: Every native method defined in native code at JNI must accept two input parameters, the first one being a pointer to JNIEnv. The JNIEnv interface pointer is pointing to thread-local data, which in turn points to a JNI function table shared by all threads. This can be illustrated using the following diagram:
    How it works…

    The JNIEnv interface pointer is the gateway to access all pre-defined JNI functions, including the functions that enable the native code to process Java objects, access Java fields, invoke Java methods, and so on. The RegisterNatives native function we're going to discuss next is also one of them.

    Tip

    The JNIEnv interface pointer points to thread-local data, so it cannot be shared between threads. In addition, JNIEnv is only accessible by a Java thread. A native thread must call the JNI function AttachCurrentThread to attach itself to the VM, to obtain the JNIEnv interface pointer. We will see an example of this in the Manipulating classes in JNI recipe in this chapter.

  • Registering Native Methods: JNI can automatically discover the native method implementation if its function name follows a specific naming convention as mentioned in Chapter 1, Hello NDK. This is not the only way. In our sample project, we explicitly called the RegisterNatives JNI function to register the native methods. The RegisterNatives function has the following prototype:
    jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);

    The clazz argument is a reference to the class in which the native method is to be registered. The methods argument is an array of the JNINativeMethod data structure. JNINativeMethod is defined as follows:

    typedef struct {
      char *name;
      char *signature;
      void *fnPtr;
    } JNINativeMethod;

    name indicates the native method name, signature is the descriptor of the method's input argument data type and return value data type, and fnPtr is the function pointer pointing to the native method. The last argument, nMethods of RegisterNatives, indicates the number of methods to register. The function returns zero to indicate success, and a negative value otherwise.

    RegisterNatives is handy to register a native method implementation for different classes. In addition, it can simplify the native method name to avoid careless mistakes.

    The typical way of using RegisterNatives is in the JNI_OnLoad method as shown in the following template. JNI_OnLoad is called when the native library is loaded, so we can guarantee that the native methods are registered before they're invoked:

    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pVm, void* reserved)
    {
        JNIEnv* env;
        if ((*pVm)->GetEnv(pVm, (void **)&env, JNI_VERSION_1_6)) {
        return -1;
      }
    
      // Get jclass with env->FindClass.
      // Register methods with env->RegisterNatives.
    
      return JNI_VERSION_1_6;
    }

We demonstrated the usage of the preceding template in the JNI_OnLoad method of our sample code, where we registered two native methods to add and multiply two input integers respectively. The execution result shown earlier proves that the Java code can invoke the two registered native methods successfully.

Note that this example uses some JNI features that we're going to cover in later recipes, including the FindClass function and field descriptors. It is alright if don't fully understand the code at this stage. You can always go back to it after learning more about those topics.

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

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