Mastering Android NDK
上QQ阅读APP看书,第一时间看更新

Dealing with precompiled static libraries

Let's put the source code of a library we need to build for different platforms into the src directory. The Makefile script should start as follows:

CFLAGS = -O2 -I src

This line defines a variable CFLAGS with a list of compiler command-line parameters. In our case, we instruct the compiler to search the src directory for header files. If the library source code spans across multiple directories, we need to add the –I switch for each of the directories. The -O2 switch tells the compiler to enable level 2 optimizations. Next, we add the following lines for each source file:

<SourceFileName>.o:
gcc $(CFLAGS) –c <SourceFile>.cpp –o <SourceFile>.o

The string <SourceFileName> should be replaced with the actual name of the .cpp source file, and these lines should be written for each of the source files.

Now, we add the list of object files:

ObjectFiles = <SourceFile1>.o <SourceFile2>.o

Finally, we will write the target for our library:

<LibraryName>:
ar –rvs <LibraryName>.a $(ObjectList)

Every line in the Makefile script except the empty lines and the names of the targets should start with a tabulation character. To build the library, invoke the following command:

>make <LibraryName>.a

When using the library in our programs, we pass the LibraryName.a file as a parameter to gcc.

Makefiles consist of targets similar to subroutines in programming languages, and usually each target results in an object file being generated. For example, we have seen that each source file of the library gets compiled into the corresponding object file.

Target names may include the filename pattern to avoid copying and pasting, but in the simplest case, we just list all the source files and duplicate those lines replacing the SourceFileName strings by the appropriate file names. The –c switch after the gcc command is the option to compile the source file, and –o specifies the name of the output object file. The $(CFLAGS) symbol denotes the substitution of the value of the CFLAGS variable into the command line.

The GCC toolchain for Windows includes the ar tool, which is an abbreviation for an archiver. Makefiles for our libraries invoke this tool to create a static version of the library. This is done in the last lines of the Makefile script.

When a line with a list of object files becomes too long, it can be split into multiple lines using the backslash symbol as follows:

ObjectFileList = FileName1.o \
                 ... \
                 FileNameN.o

There should be no spaces after the backslash, since it is a limitation of the make tool. The make tool is portable, hence the same rules apply exactly to all desktop operating systems we use: Windows, Linux, and OS X.

Now, we are able to build most of the libraries using Makefiles and the command line. Let's build them for Android. First, create a folder named jni and create the jni/Application.mk file with the appropriate compiler switches and set the name of the library accordingly. For example, one for the Theora library should look like the following:

APP_OPTIM := release
APP_PLATFORM := android-19
APP_STL := gnustl_static
APP_CPPFLAGS += -frtti
APP_CPPFLAGS += -fexceptions
APP_CPPFLAGS += -DANDROID
APP_ABI := armeabi-v7a-hard
APP_MODULES := Theora
NDK_TOOLCHAIN_VERSION := clang

Note

Here, we will use armeabi-v7a-hard as one of the most widely supported modern ABIs. The Android NDK supports many other architectures and CPUs. Refer to the NDK Programmer's Guide for a complete up-to-date list.

It will use the latest version of the Clang compiler available in the installed NDK. The jni/Android.mk file is similar to the one we wrote in the previous chapter for the 3_NDK sample application, yet with a few exceptions. At the top of the file, some required variables must be defined. Let's see how the Android.mk file for the OpenAL-Soft library might look:

TARGET_PLATFORM := android-19
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := OpenAL
LOCAL_C_INCLUDES += src
LOCAL_SRC_FILES += <ListOfSourceFiles>

Define some common compiler options: treat all warnings as errors (-Werror), the ANDROID preprocessing symbol is defined:

COMMON_CFLAGS := -Werror -DANDROID

The compilation flags are defined according to the selected CPU architecture:

ifeq ($(TARGET_ARCH),x86)
  LOCAL_CFLAGS := $(COMMON_CFLAGS)
else
  LOCAL_CFLAGS := -mfpu=vfp -mfloat-abi=hard -mhard-float -fno-short-enums -D_NDK_MATH_NO_SOFTFP=1 $(COMMON_CFLAGS)
endif

In all our examples, we will use the hardware floating point ABI armeabi-v7a-hard, so let's build the libraries accordingly.

Note

The major difference between armeabi-v7a-hard and armeabi-v7a is that the hardware floating point ABI does passing of the floating point function arguments inside FPU registers. In floating point heavy applications, this can drastically increase the performance of the code where floating point values are passed to and from different functions.

Since we are building a static library, we need the following line at the end of Android.mk:

include $(BUILD_STATIC_LIBRARY)

Building the static library now requires a single call to the ndk-build script. Let's proceed to the compilation of actual libraries after a small remark on dynamic linking and Windows platform.