Compile C code in Android NDK
I’ve limited love to Android tools provided by Google and never understood why Google tries to make it so complicated to run native code on the device. In the end Android is some form of Linux and some parts of Android framework are implemented in C/C++
. I also have limited love (and knowledge) to Java and don’t really like to use it.
Anyways, here below I present 2 methods of compiling C programs with Android NDK.
Let’s use standard “hello world” as an application that we want to run on Android dev board (main.c
):
#include <stdio.h>
int main()
{
("Hello World\n");
printfreturn 0;
}
Method 1: Using ndk-build
This method follows Android’ic way of doing things:
Create required directories
mkdir -p hello_world/jni mkdir -p hello_world/libs
In the jni
directory create
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS}
# give module name
LOCAL_MODULE := hello_world
# list your C files to compile
LOCAL_SRC_FILES := main.c
include $(BUILD_EXECUTABLE)
- Copy/create
main.c
tojni
directory - Go to
jni
directory, callndk-build
. Compilation result should be inhello_world/libs/armeabi/hello_world
Method 2: Makefile
With the second (and my prefered) way you have better control over files being compiled, compiler settings, etc. There is also no “magic” that ndk-build
provides.
Following Makefile uses clang
from NDK 16b
in order to compile a file for Android with API version 27 and for ARMv8 CPU. The makefile can be used a template.
# Change this to whereever you keep NDK
NDK = /opt/android-ndk
SRCDIR = .
OBJDIR = .
DBG ?= 0
# Debug/Release configuration
ifeq ($(DBG),1)
MODE_FLAGS = -DDEBUG -g -O0
else
MODE_FLAGS = -Os -fdata-sections -ffunction-sections
endif
## NDK configuration (clang)
# NDK Version
NDK_TARGETVER = 27
# Target arch - here aarch64 for android
NDK_TARGETARCH = aarch64-linux-android
# Target CPU (ARMv8)
NDK_TARGETSHORTARCH = arm64
# Toolchain version
NDK_TOOLVER = 4.9
# Architecture of a machine that does cross compilation
NDK_HOSTARCH = linux-x86_64
# Set needed preprocessor symbols
NDK_TOOLS = $(NDK)/toolchains/llvm/prebuilt/$(NDK_HOSTARCH)/bin
NDK_SYSROOT = $(NDK)/sysroot
NDK_TOOL = $(NDK_TOOLS)/clang
NDK_LIBS = $(NDK)/toolchains/$(NDK_TARGETARCH)-$(NDK_TOOLVER)/prebuilt/linux-x86_64/lib/gcc/$(NDK_TARGETARCH)/4.9.x
NDK_INCLUDES = -I$(NDK)/sysroot/usr/include \
-I$(NDK)/sysroot/usr/include/$(NDK_TARGETARCH)
NDK_SYSROOT = $(NDK)/platforms/android-$(NDK_TARGETVER)/arch-$(NDK_TARGETSHORTARCH)
# Options common to compiler and linker
OPT = $(MODE_FLAGS) \
-std=c99 \
-fPIE \
-Wall \
-target $(NDK_TARGETARCH)
# Compiler options
CFLAGS = $(OPT) \
$(NDK_INCLUDES)
# Linker options
LDFLAGS = $(OPT) \
$(MODE_FLAGS) \
-pie \
--sysroot=$(NDK_SYSROOT) \
-B $(ANDROID_NDK)/toolchains/$(NDK_TARGETARCH)-$(NDK_TOOLVER)/prebuilt/linux-x86_64/$(NDK_TARGETARCH)/bin \
-L$(NDK_LIBS)
all:
$(NDK_TOOL) -c $(SRCDIR)/main.c -o $(OBJDIR)/main.o $(CFLAGS)
$(NDK_TOOL) -o main $(OBJDIR)/main.o $(LDFLAGS)
adb-prepare:
adb root
adb remount
push: adb-prepare
adb push main /data/app/
run: adb-prepare push
adb shell /data/app/main
Copy this file to same directory as main.c
and try
make all
make run
This should compile the file, push it to target and run (if target is connected).
hdc@cryptoden 23:49 > ~/example
> make run
adb root
adb remount
remount succeeded
adb push main /data/app/
main: 1 file pushed. 0.7 MB/s (6000 bytes in 0.008s)
adb shell /data/app/main
Hello World