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
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
}
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;
}
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)
}
};
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
)
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++");
Primitive arrays can also be accessed:
jsize length = env->GetArrayLength(values);
jint* data = env->GetIntArrayElements(
values,
nullptr
);
Always release acquired resources:
env->ReleaseIntArrayElements(
values,
data,
JNI_ABORT
);
Native Memory
Kotlin-managed memory and native memory have different lifecycles.
Use modern C++ ownership tools such as:
std::unique_ptr<MyEngine> engine;
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
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)