Post

Android NDK Tutorial: Compiling C/C++ Code with ndk-build

Android NDK Tutorial: Compiling C/C++ Code with ndk-build

The Android Native Development Kit (NDK) is a toolset that allows you to implement parts of your Android app using native-code languages like C and C++.

You can download the NDK from the official Android Developer website or via the command line on Linux using:

1
2
# sdkmanager --install "ndk;version"
sudo sdkmanager --install "ndk;22.1.7171670"

Step 1: Set Up Project Structure

Create a directory structure like this:

1
2
3
4
5
6
MyNativeProject/
│── jni/               # Native code goes here
│   ├── Android.mk     # Makefile for NDK
│   ├── Application.mk # (Optional) Build configurations
│   └── main.c         # Your C/C++ code
└── libs/              # (Auto-generated) Compiled libraries

Step 2: Write a Simple C Program

Create jni/main.c:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <android/log.h>

void hello_world() {
    printf("Hello from C!\n");
    __android_log_print(ANDROID_LOG_INFO, "NDK", "Hello from NDK!"); // Logcat output
}

int main() {
    hello_world();
    return 0;
}

Step 3: Create Android.mk

This file tells ndk-build how to compile your code.

Create jni/Android.mk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# Name of your executable or library
LOCAL_MODULE    := my_program  

# List your C/C++ source files
LOCAL_SRC_FILES := main.c

# Link against Android-specific libraries (log, dl, etc.)
LOCAL_LDLIBS    := -llog -ldl  

# Build as an executable (use BUILD_SHARED_LIBRARY for .so)
include $(BUILD_EXECUTABLE)  

Step 4: (Optional) Configure Application.mk

This file defines build settings like target ABI and API level.

Create jni/Application.mk:

1
2
3
4
5
6
7
8
9
10
11
12
# Target architectures (arm64-v8a, armeabi-v7a, x86, x86_64)
# APP_ABI := all  # Build for all ABIs, or specify like armeabi-v7a arm64-v8a x86 x86_64
APP_ABI := arm64-v8a armeabi-v7a  

# Minimum Android API level
APP_PLATFORM := android-21  

# Use Clang (recommended)
NDK_TOOLCHAIN_VERSION := clang  

# Enable C++ exceptions/RTTI (if using C++)
# APP_CPPFLAGS += -fexceptions -frtti

Step 5: Run ndk-build

Open a terminal in MyNativeProject/ and run:

1
2
3
4
5
6
7
8
9
10
11
12
13
# If NDK is in PATH:
$ ndk-build

# If NDK is not in PATH, specify full path:
# /opt/android-sdk/ndk/<version>/ndk-build
$ /opt/android-sdk/ndk/22.1.7171670/ndk-build 
[arm64-v8a] Compile        : my_program <= main.c
[arm64-v8a] Executable     : my_program
[arm64-v8a] Install        : my_program => libs/arm64-v8a/my_program
[armeabi-v7a] Compile thumb  : my_program <= main.c
[armeabi-v7a] Executable     : my_program
[armeabi-v7a] Install        : my_program => libs/armeabi-v7a/my_program

Output:

  • Compiled binary: ./libs/<ABI>/my_program
  • Debug symbols (if any): ./obj/local/<ABI>/

Common ndk-build Commands

CommandDescription
ndk-buildCompile for all ABIs
ndk-build cleanDelete build artifacts
ndk-build NDK_DEBUG=1Build with debug symbols
ndk-build V=1Show detailed build logs
ndk-build APP_ABI=arm64-v8aBuild only for 64-bit ARM

Verify

1
2
3
4
5
6
7
8
9
10
$ adb shell getprop ro.product.cpu.abi
arm64-v8a
# Push the compiled binary 
$ adb push ../libs/arm64-v8a/my_program /data/local/tmp
$ adb shell
bonito:/ $ cd /data/local/tmp
bonito:/data/local/tmp $ ./my_program                                                              
Hello from C!
bonito:/data/local/tmp $

By creating standalone binaries that run in /data/local/tmp, we’ve explored one of the fundamental use cases of the NDK - executing native code directly on Android devices.

Coming Up Next: In my next blog post, I’ll show you how to integrate native code directly into your Android applications using JNI (Java Native Interface). We’ll explore this amazing bridge between Java and native code, covering everything from basic integration to fuzzing it.

This post is licensed under CC BY 4.0 by the author.