Kotlin and C++ Module Interaction: Android NDK
In certain situations, you may need to utilize a C++ module within Kotlin through the Android NDK. In this article, I will elucidate the process of executing C++ code in Kotlin and explore the reasons why such a need may arise.
There may be several reasons why C++ code is needed in Kotlin:
Performance: C++ is usually faster than Kotlin because it is a low-level language and closer to the hardware. This is important for applications that are CPU-intensive.
Existing Libraries: There are many powerful and optimized libraries written in C++. Using these libraries can be more efficient for solving specific problems.
Language Features: C++ has some language features that may not be available in Kotlin. These features may be required for an application that needs them.
Platform Independence: C++ code can run on various platforms. This allows the same code to be used on multiple platforms.
Hardware Access: C++ provides more direct access to the hardware. This is useful for situations that require hardware-specific operations.
For these reasons, running C++ code in Kotlin is sometimes necessary. However, this may not always be the right approach and it is important to carefully evaluate the requirements of the project to determine what is the best solution for each situation.
Android NDK and JNI
The Android NDK (Native Development Kit) is a toolkit that enables the use of C and C++ code in conjunction with Android. The NDK operates in conjunction with the Java Native Interface (JNI), managing the interaction between Java code and native code.
Incorporating a C++ Module into a Kotlin Project
It is relatively straightforward. Open Android Studio and your project, then follow these steps:
- Navigate to: File > New > New Module > Android Native Library.
2. This will integrate the C++ module into your project, and you will observe the presence of your C++ module directory. In this article, I will refer to it as “nativelib,” which is the name of my project’s module.
Now, let’s analyze nativelib.cpp and the NativeLib Kotlin class.
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativelib_NativeLib_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
JNIEnv is the Java Native Interface (JNI) environment. It is a data structure that provides access to the Java environment from native code. The JNI environment is passed to native functions by the Java Virtual Machine (JVM) when a native function is called from Java code.
jobject is a handle to a Java object. It is a value that represents a Java object in native code. The JNI environment provides functions for creating, accessing, and destroying jobjects.
In the provided code snippet, the Java_com_example_nativelib_NativeLib_stringFromJNI() function is a native function that is called from Java code. The function takes two arguments:
JNIEnv* env: This is the JNI environment.
jobject /* this */: This is a handle to the Java object that called the native function. The placeholder /* this */ indicates that the value of this argument is not used in the function.
The function creates a std::string object that contains the string “Hello from C++”. It then calls the env->NewStringUTF() function to create a new Java string object from the std::string object. Finally, it returns the Java string object to the JVM.
class NativeLib {
/**
* A native method that is implemented by the 'nativelib' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String
companion object {
// Used to load the 'nativelib' library on application startup.
init {
System.loadLibrary("nativelib")
}
}
}
This code defines a Kotlin class NativeLib. The class has one method, stringFromJNI(), which is a native method. This means that the method is implemented in native code, such as C or C++. The external keyword tells Kotlin that the method is implemented in native code and that it needs to be loaded from a native library.
The class also has a companion object, which is a special type of object that is associated with the class itself, rather than with individual instances of the class. The companion object contains an initialization block (init), which is used to load the ‘nativelib’ native library on application startup. The System.loadLibrary() function is used to load the library from the application’s APK file.
Let us create a function in C++ that adds two integers and returns the result as an integer.
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jint JNICALL
Java_com_example_nativelib_NativeLib_sumTwoNumbers(JNIEnv *env,jobject, int x, int y) {
return x+y;
}
Subsequently, create an external function to facilitate access to this C++ function in Kotlin
class NativeLib {
external fun sumTwoNumbers(x:Int, y:Int): Int
companion object {
init {
System.loadLibrary("nativelib")
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val nativeLib = NativeLib()
val total = nativeLib.sumTwoNumbers(15, 17)
setContent {
TestCProjectTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Text(
text = total.toString()
)
}
}
}
}
}
Here, it is important to note that the C++ code’s function name must include the module name you used when creating the C++ module.