Kotlin JNI for Native Code
How to call native code from Kotlin.
I’ve become a fan of TensorFlow for building machine learning models. But TensorFlow is currently not available for Kotlin. There is a Java wrapper that has been built but it doesn’t support the full TensorFlow library and requires an additional inter-oping with Java. I wanted a Kotlin wrapper directly over the C++ TensorFlow library.
I decided to start building it out myself. To that end, I needed to get JNI up and running within Kotlin. JNI allows us to access native libraries from within JVM languages, including Kotlin. Searching around the internet didn’t turn up a ton of info on using JNI with Kotlin specifically, so I’m sharing my own findings tinkering with it.
High-Level Overview
- First, we write our Kotlin code that calls the C/C++ code and compile it.
- Create a C/C++ header file for JNI to know how to talk to the native library.
- Then, we build our C/C++ code.
- Finally, we can run the Kotlin program with an option pointing to the C/C++ library we want to call.
The Kotlin Code
Here’s an example of how to build a basic Kotlin class that can call C code:
class NativeSample {
init {
System.loadLibrary("hello")
} external fun sayHello()
}
We’ll save that in a text file called NativeSample.kt
. Now, let’s create another file to run our main
function in Main.kt
:
fun main() {
NativeSample().sayHello()
}
Now we can compile these two files to NativeSample.jar
:
kotlinc-jvm -include-runtime -d NativeSample.jar *.kt
The C Code
Now let’s create the C code that our Kotlin class will call. First, we’ll create a file called hello.c
:
#include <stdio.h>
#include "NativeSample.h"JNIEXPORT void JNICALL Java_NativeSample_sayHello(JNIEnv *env, jobject obj) {
printf("Hello World!\n");
return;
}
You’ll notice the #include "NativeSample.h"
added to the second line of hello.c
. We will need to create that header file. Note that the line that starts with JNIEXPORT
has Java_NativeSample_sayHello
. This needs to match our class and method in the NativeSample.kt
file. It starts with Java
, then follows with the name of our class NativeSample
. Next it contains the method name sayHello
.
Next, make a new text file NativeSample.h
and add this to it:
#include <jni.h>#ifndef _Included_NativeSample
#define _Included_NativeSample
#ifdef __cplusplus
extern "C" {
#endifJNIEXPORT void JNICALL Java_NativeSample_sayHello(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif
Now we’re ready to compile our C code:
gcc hello.c -o libhello.so -shared -fPIC -I /usr/lib/jvm/java-12-openjdk-amd64/include -I /usr/lib/jvm/java-12-openjdk-amd64/include/linux
You’ll need to replace /usr/lib/jvm/java-12-openjdk-amd64
with your own JDK path. The above command will produce a file called libhello.so
. This is our C library that prints Hello World!
.
Running The Program
Now we’re ready to run Kotlin code we compiled earlier to call our C program:
java -jar NativeSample.jar
You should see:
Hello World!
Conclusion
You’ve now successfully compiled a Kotlin JVM program that calls a C library! This process is what I’m using to build out a Kotlin API around the C++ TensorFlow library. Stay tuned as I continue my saga of bringing TensorFlow to Kotlin!