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
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.