I am trying to use a C library that is built using cmake in an Android Studio project that uses Kotlin. So far I was able to install the NDK and cmake in Android Studio, include the C library's CMakeLists.txt file in the project and make the project compile, so that I see the .so file in it.
BUT I have no idea how to call the C library functions from Kotlin. Should the C library header file include jni.h ? If so, where do I find it? What should the signature of the library API functions be, so that Android Studio sees them? How do I use these in Android Studio Kotlin?
I know there's a lot of documentation out there but it's really fragmented and confusing, most of it pointing either at Kotlin Native or java. I would appreciate clear code samples of a C header file to define the library API and a Kotlin file that uses it. I mean, do I import something in the .kt file or what?
Thank you, I very much appreciate any help!
I figured out how to use a shared C library built with CMake in a Koltin Android Studio project:
In Andoid Studio, add a new cmake shared library to your Kotlin project and point to the library's CMakeLists.txt
Declare the C-library function in the top scope of your MainACtivity.kt like this:
external fun addNumbers( a:Int, b:Int) :Int
The name of the function in the header file of the C library has to include a lot of prefixes related to the project. Android Studio tells you what these are if you add this external declaration and then open the menu next to the red light bulb telling you it can't find this function. There, you will find an option to create the function in a C file, so do that and it will have the full prefixed name.
Once the cmake-built C library is a part of your project, you have to refresh it using "Refresh Linked C++ Projects" in the Build menu. You need to do that whenever you change the C library source.
Then you just build your project and you should be ready to go.
Android provides super useful examples at https://github.com/android/ndk-samples.
The major takeaways from these are:
Let's create and use a C library called doSomething. It provides two public functions that show how to pass numeric and string arguments to and return them from a C library in Kotlin. The name of the project is Greeting Card (yes, it's a Kotlin tutorial project).
#you should always set this
cmake_minimum_required(VERSION 3.22)
project(doSomething VERSION 1.0.0 DESCRIPTION "An exercise to build a C library with CMake in Android Studio.")
add_library(doSomething SHARED src/doSomething.c )
set_target_properties(doSomething PROPERTIES VERSION ${PROJECT_VERSION})
# this is where the header file is
set_target_properties(doSomething PROPERTIES PUBLIC_HEADER include/doSomething.h)
# define include base dir for the sources
target_include_directories(doSomething PRIVATE include)
target_include_directories(doSomething PRIVATE src)
/* Dont worry where the jni.h header comes from.
* Android Studio takes care of that.
*/
#include <jni.h>
/* JNIEXPORT and JNICALL are magic words that just have to be there.
* jint is the type of the returned value.
* The full name of the function with all the prefixes
* is generated by Android Studio as described above.
*/
JNIEXPORT jint JNICALL
Java_com_example_greetingcard_MainActivityKt_addNumbers(JNIEnv *env, jclass thiz, jint a, jint b);
JNIEXPORT jstring JNICALL
Java_com_example_greetingcard_MainActivityKt_flipString(JNIEnv *env, jclass thiz, jstring instr);
#include "include/doSomething.h"
#include <stdio.h>
#include <string.h>
JNIEXPORT jint JNICALL
Java_com_example_greetingcard_MainActivityKt_addNumbers(JNIEnv *env, jobject thiz, jint a, jint b) {
return a + b;
}
JNIEXPORT jstring JNICALL
Java_com_example_greetingcard_MainActivityKt_flipString(JNIEnv *env, jobject thiz, jstring instr) {
char *str;
jboolean isCopy;
// Get the string from the kotlin parameter
str = (*env)->GetStringUTFChars(env, instr, &isCopy);
size_t len = strlen( str );
for( size_t i = 0; i < len / 2; ++i ) {
char c = str[i];
str[i] = str[ len - i - 1 ];
str[ len - i - 1] = c;
}
return (*env)->NewStringUTF(env,str);
}
At the top scope of this file (i.e., not inside the MainActivity class), declare the functions from the C library:
external fun addNumbers( a:Int, b:Int) :Int
external fun flipString( instr: String ): String
Then you can use these anywhere in the MainActivity.kt file as you would expect:
println("If we add 1 + 2, we get ${addNumbers(1,2)}!")
println("Flip \"dog\" and get \"${flipString("dog")}\".")