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()
{
  printf("Hello World\n");
  return 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

    1    LOCAL_PATH := $(call my-dir)
    2    include $(CLEAR_VARS}
    3    # give module name
    4    LOCAL_MODULE := hello_world
    5    # list your C files to compile
    6    LOCAL_SRC_FILES := main.c
    7    include $(BUILD_EXECUTABLE)
    8    
    
  • Copy/create main.c to jni directory

  • Go to jni directory, call ndk-build. Compilation result should be in hello_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.

 1# Change this to whereever you keep NDK
 2NDK            = /opt/android-ndk
 3SRCDIR         = .
 4OBJDIR         = .
 5DBG           ?= 0
 6
 7# Debug/Release configuration
 8ifeq ($(DBG),1)
 9MODE_FLAGS     = -DDEBUG -g -O0
10else
11MODE_FLAGS     = -Os -fdata-sections -ffunction-sections
12endif
13
14## NDK configuration (clang)
15
16# NDK Version
17NDK_TARGETVER  = 27
18
19# Target arch - here aarch64 for android
20NDK_TARGETARCH = aarch64-linux-android
21
22# Target CPU (ARMv8)
23NDK_TARGETSHORTARCH = arm64
24
25# Toolchain version
26NDK_TOOLVER  = 4.9
27
28# Architecture of a machine that does cross compilation
29NDK_HOSTARCH = linux-x86_64
30
31# Set needed preprocessor symbols
32NDK_TOOLS    = $(NDK)/toolchains/llvm/prebuilt/$(NDK_HOSTARCH)/bin
33NDK_SYSROOT  = $(NDK)/sysroot
34NDK_TOOL     = $(NDK_TOOLS)/clang
35NDK_LIBS     = $(NDK)/toolchains/$(NDK_TARGETARCH)-$(NDK_TOOLVER)/prebuilt/linux-x86_64/lib/gcc/$(NDK_TARGETARCH)/4.9.x
36NDK_INCLUDES = -I$(NDK)/sysroot/usr/include \
37               -I$(NDK)/sysroot/usr/include/$(NDK_TARGETARCH)
38NDK_SYSROOT  = $(NDK)/platforms/android-$(NDK_TARGETVER)/arch-$(NDK_TARGETSHORTARCH)
39
40# Options common to compiler and linker
41OPT          = $(MODE_FLAGS) \
42               -std=c99 \
43               -fPIE \
44               -Wall \
45               -target $(NDK_TARGETARCH)
46
47# Compiler options
48CFLAGS       = $(OPT) \
49               $(NDK_INCLUDES)
50
51# Linker options
52LDFLAGS      = $(OPT) \
53               $(MODE_FLAGS) \
54               -pie \
55               --sysroot=$(NDK_SYSROOT) \
56               -B $(ANDROID_NDK)/toolchains/$(NDK_TARGETARCH)-$(NDK_TOOLVER)/prebuilt/linux-x86_64/$(NDK_TARGETARCH)/bin \
57               -L$(NDK_LIBS)
58
59all:
60    $(NDK_TOOL) -c $(SRCDIR)/main.c -o $(OBJDIR)/main.o $(CFLAGS)
61    $(NDK_TOOL) -o main $(OBJDIR)/main.o $(LDFLAGS)
62
63adb-prepare:
64    adb root
65    adb remount
66
67push: adb-prepare
68    adb push main /data/app/
69
70run: adb-prepare push
71    adb shell /data/app/main

Copy this file to same directory as main.c and try

1make all
2make 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