Recently, I tried to use Kotlin and JavaFX together. In this post I'm taking a peek into Kotlin native development with the Kotlin multiplatform plugin.
A long time ago, I had this idea of writing my own window manager for X. I actually started to do that a few months ago. I used C as a language, because there are some good tutorials as well as window manager implementations and XLib as well as XCB are C libraries. I got the break through and then let the project rest for a while.
Now that I started using Kotlin, I got this idea of doing the project in Kotlin. I used JNI before, so my thought was that it shouldn't be too hard to do this.
There are tutorials on Kotlin native development on the Kotlin website as well as a page about interoperability with C in the language reference. Those pages are a good read to start writing a Kotlin native application. The tutorial explains how to work with different kinds of data as well as function pointers and also describes how to set up a Kotlin native project with Gradle. I'm using Intellij IDEA to create this project.
The first thing to notice is the different language plugin.
org.jetbrains.kotlin.multiplatform is used instead of
org.jetbrains.kotlin.jvm. Then there is the setup for the native environment, which looks like this in my case:
The native environment is Linux and I named my interop compilation xcb, because the XCB libraries are the ones that I include and use for X development.
Next, the required libraries need to be added to the project and there are two ways to do so. The first way uses def files, which is also explained in the tutorial pages of Kotlin. My def file looks like this:
This includes the listed XCB headers from the path /usr/include in the compiler call and it uses the libraries xcb and xcb-util from the directory /usr/lib for the linking.
This information could also be added to the Gradle build file as properties of xcb like this example for the compiler options.
While this is a possible way, I got the impression during my research that using the def file is more encouraged. However, putting the options in the Gradle file might be necessary when the paths need to be computed. More information on the Gradle configurations can be found on this page about the Gradle plugin.
Building the project
The part that positively surprised me is this: The mutliplatform plugin brings compiler and linker in form of Clang along. The toolchain also has tasks/tools to generate the interface methods to access the C libraries from Kotlin. The result is a klib containing knm files that look like this (excerpt from the generated XCB interface, added line breaks for readability):
Using this klib (and the generated kt file in the kotlin folder of the klib-build), it is now possible to programmatically access the previously declared C libraries in Kotlin without any other actions.
Theres one thing to note: At the time of writing this article, the compiler or linker that comes with the multiplatform plugin requires libtinfo.so.5 on Linux. This library is a little outdated, the current one is libtinfo.so.6. On my system (Arch Linux) I needed to install the ncurses5-compat-libs package, which includes this library. This is only required during compile and/or linking time.
Working with C libraries
Now, that everything is ready to start coding, let's talk about interface code and how to work with it. The tutorials already describe a huge part of this, including the handling of primitive datatypes, strings (char*), structs, unions and function pointers.
There are a few things that stand out from the usual Kotlin development. Of course, there's the naming conventions. More interesting is that it may be necessary to handle the allocation and deallocation of memory. memScoped is a great help here. It creates a scope in which memory can be allocated for variables with the alloc function. At the end of the scope, the memory will automatically be freed again. For detailed handling, there are alloc and free functions on the nativeHeap object.
Secondly, it will likely happen that there's data that sometimes needs to be accessed as value and other times as pointer. Pointers are handled by CPointer or CValues wrapper classes for the data types. The value property can be used to get a value from a pointer and the ptr property can be used to get the pointer from a value.
I recommend to take a deeper look into the functions and types that are provided for the C interface code. The multiplatform plugin brings a lot of useful tooling, especially in terms of data conversion. For instance, there are to*-functions (toUInt, toByte, ...) as well as a convert function that handles conversion into types like size_t or uint8_t, which are not standard primitive types. Those are definitly the most used interface functions for me, because C enums (there are a lot in XCB!) are converted into constants for use in Kotlin and the constants type for number enums, which is usually Int, often doesn't fit. I'd like to see that the multiplatform tooling generates Kotlin enum classes for C enums. The values of the C enum could be forwarded as constructor parameters.
The last thing I want to mention here is null checks. Every reference in a C function declaration will get a ? attached in the generated Kotlin code and therefore needs to be checked for null.
Except for the additional library dependency, the Kotlin native project setup is straight forward. The multiplatform plugin brings a lot of tool support, with my favorite being the one-click-generation of the klib. Working with memory allocation and the pointer/value mix in Kotlin is strange at first but it is nicely integrated into the language.