DEV Community

vmodal_ai
vmodal_ai

Posted on

Kotlin Native Interop: Calling C and C++ from Android

Kotlin Native Interop: Calling C and C++ from Android

Android applications can combine Kotlin with native C and C++ code when performance, existing native libraries, or lower-level functionality requires it.

The Android NDK provides the native development toolchain, while JNI provides the bridge between Kotlin/Java and native code.

Architecture

Kotlin
  |
JNI
  |
C/C++
  |
Native Library
Enter fullscreen mode Exit fullscreen mode

Keep the Kotlin-facing API small and hide native implementation details behind a stable abstraction.

Kotlin Native Method

A Kotlin class can load a native library:

class NativeEngine {

    companion object {
        init {
            System.loadLibrary("native_engine")
        }
    }

    external fun calculate(value: Int): Int
}
Enter fullscreen mode Exit fullscreen mode

C++ Implementation

A simple JNI implementation:

#include <jni.h>

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_app_NativeEngine_calculate(
        JNIEnv* env,
        jobject thiz,
        jint value) {

    return value * 2;
}
Enter fullscreen mode Exit fullscreen mode

The traditional JNI symbol includes the package, class, and method name.

Dynamic Registration

For larger projects, native methods can be registered dynamically using JNINativeMethod:

static JNINativeMethod methods[] = {
    {
        "calculate",
        "(I)I",
        reinterpret_cast<void*>(calculate)
    }
};
Enter fullscreen mode Exit fullscreen mode

This can make native APIs easier to maintain.

CMake

A simplified CMakeLists.txt:

cmake_minimum_required(VERSION 3.22.1)

project("native_engine")

add_library(
    native_engine
    SHARED
    native_engine.cpp
)
Enter fullscreen mode Exit fullscreen mode

Android Gradle configuration connects the native build to the application.

Strings and Arrays

JNI supports conversion between managed and native data.

For example:

return env->NewStringUTF("Hello from C++");
Enter fullscreen mode Exit fullscreen mode

Primitive arrays can also be accessed:

jsize length = env->GetArrayLength(values);

jint* data = env->GetIntArrayElements(
    values,
    nullptr
);
Enter fullscreen mode Exit fullscreen mode

Always release acquired resources:

env->ReleaseIntArrayElements(
    values,
    data,
    JNI_ABORT
);
Enter fullscreen mode Exit fullscreen mode

Native Memory

Kotlin-managed memory and native memory have different lifecycles.

Use modern C++ ownership tools such as:

std::unique_ptr<MyEngine> engine;
Enter fullscreen mode Exit fullscreen mode

Prefer RAII instead of manually managing native resources.

Exceptions

Do not allow arbitrary C++ exceptions to cross the JNI boundary.

Catch native exceptions and translate them into appropriate Java/Kotlin exceptions.

JNI Performance

JNI has overhead. Avoid crossing the boundary repeatedly in a performance-critical loop.

Prefer:

Kotlin
 ↓
One JNI call
 ↓
Large native operation
 ↓
Result
Enter fullscreen mode Exit fullscreen mode

instead of many tiny calls.

Threading

Native-created threads that need to interact with the JVM must be attached correctly. Keep ownership and thread lifecycle explicit.

Kotlin coroutines can be used on the application side to keep native work away from the UI thread.

Debugging

Native errors can cause:

  • Segmentation faults
  • Memory corruption
  • Deadlocks
  • JNI reference errors
  • Application crashes

Use native debugging and sanitizers during development.

Conclusion

Kotlin and C/C++ interoperability is powerful, but the boundary must be carefully designed.

Keep the Kotlin API small, manage native resources explicitly, avoid unnecessary JNI calls, and isolate native code behind a stable interface.

Useful Links

SDK Flutter: https://github.com/v-modal/vmodal_sdk_flutter

SDK Android: https://github.com/v-modal/vmodal_sdk_android

Discord: https://discord.gg/K72z28KUx

Top comments (0)