In case you didn’t already know, Kotlin is a fairly new yet surprisingly mature programming language that targets the JVM (Java Virtual Machine). Simply put, Kotlin compiles to the same bytecode that Java compiles to (compilation to JavaScript is also supported). Kotlin is fully supported for writing apps using Android Studio, hence its surge in popularity, and can call native C/C++ code in the same way that Java can, through the JNI (Java Native Interface), being the subject of this article.
The header file jni.h
is a prerequisite for creating a suitable C++ module (actually a DLL .dll
under Windows or a Shared Library .so
under Linux/Android). This is in fact a C header, which is fully compatible with C++ too. However, the goal being C++ called from Kotlin, you will be pleased to learn that no actual C or Java code is required. However, lets take a look at the client (caller) code before looking at the native (callee) code (apologies for lack of syntax highlighting):
class HelloFromCpp(val from: String, val number: Int) {
fun show() {
println(bridge(from, number))
}
private external fun bridge(s: String, n: Int): String
companion object {
init {
System.loadLibrary("cppfromkotlin")
}
}
}
fun main(args: Array<String>) {
val hello = HelloFromCpp("Hello from C++! ", 5)
hello.show()
}
Lines 1-14 define a Kotlin class called HelloFromCpp
which has immutable properties of a String
and an Int
. The single public method show()
calls the private (native) method bridge()
with these two properties, and prints out its return value. This native method is loaded (at run-time) upon creation of the first HelloFromCpp
instance and there is only one reference even if multiple instances are created, due to it being in the init-block of a companion object (this is Kotlin-specific).
Lines 16-19 define a main()
function outside of any Kotlin class—this is not possible in Java—which creates an instance of the HelloFromCpp
class (with parameters) and then calls its show()
method. To compile this file I used kotlinc.bat
under Windows (located in C:\Program Files\Android\Android Studio\plugins\Kotlin\kotlinc\bin\
) with the filename HelloFromCpp.kt
. This produces a number of files: HelloFromCpp$Companion.class
, HelloFromCpp.class
, HelloFromCppKt.class
, the last of these is the one we want to execute. Attempting to run it (with kotlin.bat HelloFromCppKt
using no .kt
or .class
extension) produces an unsatisfied link error (if you get the name of the JNI function slightly wrong, you will get the same error, so be warned):
Exception in thread "main" java.lang.UnsatisfiedLinkError: no cppfromkotlin in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at HelloFromCpp.<clinit>(HelloFromCpp.kt:11)
at HelloFromCppKt.main(HelloFromCpp.kt:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.jetbrains.kotlin.runner.AbstractRunner.run(runners.kt:64)
at org.jetbrains.kotlin.runner.Main.run(Main.kt:176)
at org.jetbrains.kotlin.runner.Main.main(Main.kt:186)
So, of course, we need to write some native C++ code. The name of the C++ function (with C linkage) that we need to write takes its name from the package name (if any) as well as the Kotlin class and method names. If we used package com.learnmoderncpp.hellofromcpp
in our Kotlin file the full name would be Java_com_learnmodercpp_hellofromcpp_HelloFromCpp_bridge
, and in production code this would be recommended to avoid any chance of a name collision. Due to the fact we haven’t used a package name the function name becomes briefer:
extern "C" JNIEXPORT jstring JNICALL Java_HelloFromCpp_bridge(JNIEnv *env, jobject, jstring s, jint i)
The capitalized macros and extern "C"
are needed for all linkable native functions, as well as the first two parameters, even though C++ code can be used in the function body). The remaining JNI-typed parameters and return type must match those in the Kotlin code (as in private external fun bridge(s: String, n: Int): String
) which means you are restricted to types with an exact Java equivalent.
For the rest of the code the class name is replicated in C++, however with a single get()
method that returns a std::string
. The bridge function creates two variables native_from
and native_number
from the supplementary parameters of type jstring
and jint
, and uses these to create a C++ class instance. The return value from this instance’s get()
method is used to create the return type, again of type jstring
. The complete source file looks like this:
#include <jni.h>
#include <string>
using std::string;
class HelloFromCpp {
string d_from;
int d_number;
public:
HelloFromCpp(const string& from, const int number)
: d_from{ from }, d_number{ number } {}
string get() {
string r{};
for (int i = 0; i < d_number; ++i) {
r.append(d_from);
}
return r;
}
};
extern "C" JNIEXPORT jstring JNICALL
Java_HelloFromCpp_bridge(JNIEnv *env, jobject, jstring s, jint i) {
string native_from{ env->GetStringUTFChars(s, nullptr) };
int native_number{ i };
HelloFromCpp hello(native_from, native_number);
return env->NewStringUTF(hello.get().c_str());
}
To compile this as a DLL and correctly reference jni.h
I used the following command under a 32-bit Visual Studio 2019 command prompt:
cl /LD /I "C:\Program Files\Java\jdk1.8.0_181\include" /I "C:\Program Files\Java\jdk1.8.0_181\include\win32" cppfromkotlin.cpp
With this DLL cppfromkotlin.dll
in the same directory as the three .class
binaries, the command:
"C:\Program Files\Android\Android Studio\plugins\Kotlin\kotlinc\bin\kotlin.bat" HelloFromCppKt
Produces the output:
Hello from C++! Hello from C++! Hello from C++! Hello from C++! Hello from C++!
The method of duplicating the class name in Kotlin and C++ is of course optional, but it helps when creating bridge function(s) which have to channel the necessary types to and from native code. The code shown here generalizes to larger projects which have multiple classes and native functions and, as promised, whilst we have used C linkage and JNI types, no C or Java code was needed.