The magic of Kotlin/Native: Part 2

Vivek Singh
AndroIDIOTS
Published in
6 min readDec 6, 2018

Kotlin/Native Series

1. The Magic of Kotlin Native: Part 1

2. The Magic of Kotlin Native: Part 2 ( You are here)

3. The Magic of Kotlin Native: Part 3

In the Part 1 of this series we talked about what is Kotlin/Native and how it enables us to create programs for native platforms. In this article we are going to discuss how you can interop Kotlin code with C and Objective-C/Swift.

But first let me address the burning question that you may have.

When would I ever need to inter-op with C?

The simple answer is a lot of code and libraries that run on machines natively is written in C (or Objective-C in case of macOS). If you want to develop applications that would run natively you would need to ultimately inter-op with C code. Also there may be a great library written in C that you want to include in your application. Instead of re-inventing the wheel and implement the said library yourself you can just include it in your Kotlin/Native project.

DISCLAIMER : If you feel uncomfortable around C code please proceed only at your own risk

Background

Libraries in C can be of two types

  • Static libraries (.a): Library of object code which is linked with, and becomes part of the application.
  • Dynamically linked shared object libraries (.so): The libraries must be available during compile/link phase. The shared objects are not included into the executable component but are tied to the execution.

To reference methods inside these libraries they have a .h file which contains the stubs for all the exposed functions.

Creating C libraries via gcc is out of scope for this article but if you want to dive deep into it you can read:

Consuming C Library in Kotlin code

The following workflow is expected when interacting with the native library.

  • create a .def file describing what to include into bindings
  • use the cinterop tool to produce Kotlin bindings
  • run Kotlin/Native compiler on an application to produce the final executable

Let’s understand these steps one by one by the help of an example.

Below I have a snippet of getJoke method implemented in RandomJoke.h which get’s a random Chuck Norris joke.

char* getJoke() {
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
struct string s;
init_string(&s);
curl_easy_setopt(curl, CURLOPT_URL, "http://api.icndb.com/jokes/random");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
res = curl_easy_perform(curl);
return s.ptr;
}
return "";
}

Note: Header files should not have implementations. Here I have implemented the method inside the header file for the sake of brevity

Step 1: Create a .def file

A .def file is a configuration file which tells the cinterop tool how to package a given C library. A typical .def file would look similar to:

headers = /Users/viveksingh/Personal/Projects/Joke/src/main/c_interop/randomJoke/RandomJoke.h
compilerOpts = -D_POSIX_SOURCE
linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl
linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lcurl

headers specifies the header files that need to be mapped to kotlin code.

compilerOpts and linkerOpts are used by the underlying gcc (the c/c++ compiler) to compile and link any libraries.

This line instructs the compiler to look for curl in the following two locations.

linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl

Step 2: Run the ‘cinterop’ command

cinterop -def RandomJoke.def -o randomJoke

This command will produce a randomJoke.klib compiled library and randomJoke-build/kotlin directory containing Kotlin source code for the library.

The .klib are Kotlin/Native libraries which you can plug and play in any Kotlin/Native project. They are the jars of rhe Kotlin/Native world.

Note: The .klib does not contain the implementation code of curl methods but only the stubs. When your programs runs on a machine it will expect curl to be installed on that machine.

Step 3: Compile your program

Below is my PrintJoke.kt

import RandomJoke.*
import kotlinx.cinterop.*
fun main(args: Array<String>) {
println(getJoke()?.toKString())
}

Run the following command which will create a joke.kexe

kotlinc PrintJoke.kt -l randomJoke -o joke

Run joke.kexe and voila

{ "type": "success", "value": { "id": 163, "joke": "Ninjas want to grow up to be just like Chuck Norris. But usually they grow up just to be killed by Chuck Norris.", "categories": [] } }

Static Libraries

As I mentioned earlier curl implementation will not be bundled inside the .klib that is produced. But its not always desirable to assume that a library will be present in a target machine.

Imagine you have a great C library foo whose code you want to bundle in the generated .klib. Just add the following lines to the .def file

staticLibraries = foo.a 
libraryPaths = /opt/local/lib /usr/local/opt/curl/lib

When given the above snippet the cinterop tool will search foo.a in /opt/local/lib and /usr/local/opt/curl/lib, and if it is found include the library binary into klib.

When using such klib in your program, the library is linked automatically.

Consuming Kotlin code in C

So far we have talked about how to include a C library in kotlin code. But you cannot claim full interoperability if you can’t do the vice-versa.
Below is an awesome kotlin library to add two integers add.kt

fun add(a: Int, b: Int): Int {
return a + b
}

Now run the following command

kotlinc add.kt -p dynamic -o add

The kotlinc generates the following files, depending on the host OS:

  • macOS: add_api.h and libadd.dylib
  • Linux: add_api.h and libadd.so
  • Windows: add_api.h, add_symbols.def and add.dll

On macOS 10.13 with Xcode, we compile the C code and link it with the dynamic library with the following command:

clang main.c libadd.dylib

On Linux we call a similar command:

gcc main.c libadd.so

To delve deep into the generated Header file this Kotlin tutorial provides an excellent reference.

Type Mappings

Below are the cheat sheets of C types and keywords and how they are consumed in the Kotlin world.

C types in Kotlin
Objective C

To know more about C bindings in Kotlin you can go through the official documentation which is pretty detailed in itself.

For Objective-C/Swift mappings the official documentation provides an excellent reference

Kotlin/Native as an Apple Framework

To interop with Objective-C/Swift you need your kotlin code to be in the form of .framework files.

$ kotlinc add.kt -produce framework -target macos_x64 -output macOS/Add $ kotlinc add.kt -produce framework -target ios_arm64 -output iOS/Add$ kotlinc add.kt -produce framework -target ios_x64 -output iOS_sim/Add

The above command creates a .framework file that you can than consume in your Objective-C/Swift project. You can define different targets depending on the target platform.

Conclusion

So far we have covered how Kotlin/Native enables us with tools to create application for native environments and inter-op with C and Objective-C/Swift.

In the next article we will create an application for Android and iOS using Kotlin/Multi platform and Kotlin/Native plugin.

Source code for this article:

References

Thanks for reading! Share this article if you found it useful.
Please do Clap 👏 to show some love :)

Follow Androidiots for more such amazing android related content.

--

--