Porting a library as a shared library module with the Android NDK build system

This recipe will discuss how to port an existing library as a shared library with the Android NDK build system. We will use the open source libbmp library as an example.

Getting ready

Readers are recommended to read the Building an Android NDK application at the command line recipe in Chapter 3, Build and Debug NDK Applications, before going through this one.

How to do it...

The following steps describe how to create our sample Android project that demonstrates porting the libbmp library as a shared library:

  1. Create an Android application named PortingShared with native support. Set the package name as cookbook.chapter8.portingshared. Please refer to the Loading native libraries and registering native methods recipe of Chapter 2, Java Native Interface, if you want more detailed instructions.
  2. Add a Java file MainActivity.java under the cookbook.chapter8.portingshared package. This Java file simply loads the shared library .bmp and PortingShared, and calls the native method naCreateABmp.
  3. Download the libbmp library from http://code.google.com/p/libbmp/downloads/list, and extract the archive file. Create a folder named libbmp under the jni folder, and copy the src/bmpfile.c and src/bmpfile.h files from the extracted folder to the libbmp folder.
  4. Remove the following code from bmpfile.h if you are using NDK r8 and below:
    #ifndef uint8_t
    typedef unsigned char uint8_t;
    #endif
    #ifndef uint16_t
    typedef unsigned short uint16_t;
    #endif
    #ifndef uint32_t
    typedef unsigned int uint32_t;
    #endif
  5. Then, add the following line of code:
    #include <stdint.h>

    Note

    The code changes for bmpfile.h are only necessary for Android NDK r8 and below. Compiling the library will return an error "error: redefinition of typedef 'uint8_t'". This is a bug in the NDK build system as the uint8_t definition is enclosed by the #ifndef preprocessor. It has been fixed since NDK r8b, and we don't need to change the code if we're using r8b and above.

  6. Create an Android.mk file under the libbmp folder to compile libbmp as a shared library libbmp.so. The content of this Android.mk file is as follows:
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := libbmp
    LOCAL_SRC_FILES := bmpfile.c
    include $(BUILD_SHARED_LIBRARY)
  7. Create another folder named libbmptest under the jni folder. Add the mylog.h and PortingShared.c files under it. PortingShared.c implements the native method naCreateABmp, which uses functions defined in the libbmp library to create a bitmap image and save it to /sdcard/test_shared.bmp. You will need to change directory if the /sdcard directory is not available on your devices:
    void naCreateABmp(JNIEnv* env, jclass clazz, jint width, jint height, jint depth) {
      bmpfile_t *bmp;
      int i, j;
      rgb_pixel_t pixel = {128, 64, 0, 0};
      for (i = 10, j = 10; j < height; ++i, ++j) {
        bmp_set_pixel(bmp, i, j, pixel);
        pixel.red++;
        pixel.green++;
        pixel.blue++;
        bmp_set_pixel(bmp, i + 1, j, pixel);
        bmp_set_pixel(bmp, i, j + 1, pixel);
      }
      bmp_save(bmp, "/sdcard/test_shared.bmp");
      bmp_destroy(bmp);
    }
  8. Create another Android.mk file under the libbmptest folder to compile the PortingShared.c file as another shared library libPortingShared.so. The content of this Android.mk file is as follows:
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := PortingShared
    LOCAL_C_INCLUDES := $(LOCAL_PATH)/../libbmp/
    LOCAL_SRC_FILES := PortingShared.c
    LOCAL_SHARED_LIBRARIES := libbmp
    LOCAL_LDLIBS := -llog
    include $(BUILD_SHARED_LIBRARY)
  9. Create an Android.mk file under the jni folder with the following content:
    LOCAL_PATH := $(call my-dir)
    include $(call all-subdir-makefiles)
  10. Add the WRITE_EXTERNAL_STORAGE permission to the AndroidManifest.xml file as follows:
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  11. Build and run the Android project. A bitmap file test_shared.bmp should be created at the sdcard folder of the Android device. We can use the following command to get the file:
    $ adb pull /sdcard/test_shared.bmp .

    The following is a .bmp file:

    How to do it...

How it works...

The sample project demonstrates how to port the libbmp code as a shared library and use it in the native code PortingShared.c.

Shared library: A shared library can be shared by multiple executables and libraries. The Android native code is usually compiled as shared libraries and loaded by the Java code. In fact, the Android build system only packages shared libraries into the application's apk file. Therefore, we must provide at least one shared library to contain our native code.

Note

We can still use static libraries to generate shared libraries, as we will see in the Porting a library as static library module with Android NDK build system recipe.

Our sample project builds two shared libraries, namely libbmp.so and libPortingShared.so. We can find these libraries under the libs folder of the project. libPortingShared.so depends on libbmp.so, since PortingShared.c calls functions defined in the libbmp library.

In our Java file, we need to load libbmp.so before libPortingShared.so, as follows:

static {
       System.loadLibrary("bmp");
         System.loadLibrary("PortingShared");
}

Understand the Android.mk files: Android NDK provides an easy-to-use build system, which frees us from writing makefiles. However, we still need to provide some basic inputs to the system through Android.mk and Application.mk. We only discuss Android.mk in this recipe.

The Android.mk file is a GNU makefile fragment that describes the sources to the Android build system. The sources are grouped into modules. Each module is a static or shared library. The Android NDK provides a few predefined variables and macros. Here, we will briefly describe the ones used in this recipe. We will introduce more predefined variables and macros in subsequent recipes and you can also refer to Android NDK docs/ANDROID-MK.html for more information.

  • CLEAR_VARS: This variable points to a script, which undefines nearly all module description variables except LOCAL_PATH. We must include it before every new module, as follows:
    include $(CLEAR_VARS)
  • BUILD_SHARED_LIBRARY: This variable points to a build script, which determines how to build a shared library from the sources listed, based on the module description. We must have LOCAL_MODULE and LOCAL_SRC_FILES defined when including this variable, as follows:
    include $(BUILD_SHARED_LIBRARY)

    Including it will generate a shared library lib$(LOCAL_MODULE).so.

  • my-dir: This must be evaluated by using $(call <macro>). The my-dir macro returns the path of the last included makefile, which is usually the directory containing the current Android.mk file. It is typically used to define the LOCAL_PATH, as follows:
    LOCAL_PATH := $(call my-dir)
  • all-subdir-makefiles: This macro returns a list of Android.mk files located in all subdirectories of the current my-dir path. In our example, we used this macro in the Android.mk file under the jni, as follows:
    include $(call all-subdir-makefiles)

    This will include the two Android.mk files under libbmp and libbmptest.

  • LOCAL_PATH: This is a module description variable, which is used to locate the path to the sources. It is usually used with the my-dir macro, as follows:
    LOCAL_PATH := $(call my-dir)
  • LOCAL_MODULE: This is a module description variable, which defines the name of our module. Note that it must be unique among all module names and must not contain any space.
  • LOCAL_SRC_FILES: This is a module description variable, which lists out the sources used to build the module. Note that the sources should be relative to LOCAL_PATH.
  • LOCAL_C_INCLUDES: This is an optional module description variable, which provides a list of the paths that will be appended to the include search path at compilation. The paths should be relative to the NDK root directory. In Android.mk, under the libbmptest folder of our sample project, we used this variable as follows:
    LOCAL_C_INCLUDES := $(LOCAL_PATH)/../libbmp/
  • LOCAL_SHARED_LIBRARIES: This is an optional module description variable, which provides a list of the shared libraries the current module depends on. In Android.mk, under the libbmptest folder of our sample project, we used this variable to include the libbmp.so shared library:
    LOCAL_SHARED_LIBRARIES := libbmp
  • LOCAL_LDLIBS: This is an optional module description variable, which provides a list of linker flags. It is useful to pass the system libraries with the -l prefix. In our sample project, we used it to link the system log library:
    LOCAL_LDLIBS := -llog

With the preceding description, it is now fairly easy to understand the three Android.mk files used in our sample project. Android.mk under jni simply includes another two Android.mk files. Android.mk under the libbmp folder compiles the libbmp sources as a shared library libbmp.so, and Android.mk under the libbmptest folder compiles PortingShared.c as the libPortingShared.so shared library, which depends upon the libbmp.so library.

See also

It is possible to use a shared library in the native code, as we have demonstrated in the Programming with dynamic linker library at Android NDK recipe in Chapter 6, Other Android NDK API.

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

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