Kotlin/Native — Use Kotlin In C and Apple Framework [Part 2]

Debanshu Datta
ProAndroidDev
Published in
6 min readSep 29, 2023

--

In the previous article, we embarked on a journey into the world of Kotlin/Native, uncovering its capabilities in compiling Kotlin code to native binaries, free from the constraints of virtual machines. We explored the architecture of Kotlin/Native, delving into its LLVM-based backend and the crucial role played by the runtime implementation in executing platform-specific code.

In the current part, we will focus on how to use Kotlin libraries and other functionalities, in C language and in Apple Framework.

Kotlin/Native as a dynamic library

Kotlin/Native compiler can produce a dynamic library out of the Kotlin code. A dynamic library often comes with a header file, a .h file, which we will use to call the compiled code from C.

Using Ktor Client to make API Calls from Kotlin code, convert this to the dynamic library and use the same in our C language.

  • We will start by creating a new Native Application via IntelliJ. Now we will go to build.gradle.kts and change the following. We have added the sharedLib which will get Gradle to build a dynamic library. Then add the required dependencies nativeMain for making API calls, coroutines, etc.
plugins {
kotlin("multiplatform") version "1.8.0"
}
repositories {
mavenCentral()
}

kotlin {
val ktorVersion = "2.2.2"
macosArm64("native") { // on Apple Silicon macOS
// linuxX64("native") { // on Linux
// macosX64("native") { // on x86_64 macOS
// mingwX64("native") { // on Windows
binaries {
sharedLib {
baseName = "native" // on Linux and macOS
// baseName = "libnative" // on Windows
}
}
sourceSets {
val nativeMain by getting{
dependencies {
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-curl:$ktorVersion")
}
}
val nativeTest by getting
}
}
}
tasks.wrapper {
gradleVersion = "7.3"
distributionType = Wrapper.DistributionType.ALL
}
  • We will now make api a new directory inside src/nativeMain/kotlin. We will start by creating a client file client.kt. Not explaining much since these are basic Kotlin and Ktor.
package api
import io.ktor.client.HttpClient
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import io.ktor.client.engine.curl.Curl

val networkClient:HttpClient = HttpClient(Curl) {
install(ContentNegotiation){
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
  • We will then create a new file postAPI.kt
package api
import io.ktor.client.call.*
import io.ktor.client.request.*

class PostAPI {
suspend fun getPosts(): String {
val response = networkClient.get("https://jsonplaceholder.typicode.com/posts/1")
return response.body<String>().toString();
}
}
  • Finally, to consume this use case class GivePostUsecase.kt, just a wrapper runBlocking to call the suspending function. We can put it inside a new directory api/usecases for better code formatting.
package api.usecase
import api.PostAPI
import kotlinx.coroutines.runBlocking

class GetPostUseCase {
fun getPostDirect() : String {
return runBlocking {
PostAPI().getPosts()
}
}
}
  • We are done with the Kotlin coding part. Furthermore, we will convert this to a dynamic library and consume this in the C program. Besides, we need to run the command.
./gradlew linkNative

We will find a new library libnative.dylib and a new file libnative_api.h pop up inside build/bin/debugShared.

File Structure .dylib

The build generates the following files under the build/bin/debugShared folder, depending on the host OS:

  • macOS: libnative_api.h and libnative.dylib A file with the DYLIB file extension is a Mach-O (Mach Object) dynamic library file that an application references during runtime to perform certain functions on an as-needed basis.
  • Linux: libnative_api.h and libnative.so. A file with the.SO extension is a shared library file. SO file is a shared library file used on Android and Linux operating systems.
  • Windows: libnative_api.h, libnative_symbols.def and libnative.dll. A DLL is a library that contains code and data that can be used by more than one program at the same time.

If we dive deep into the generated libnative_api.h file, we will notice that it is the mapping of the Kotlin Code to C Language. Kotlin uses the libnative_ prefix for all declarations in the created libnative_api.h file. As C does not support namespaces, the Kotlin/Native compiler creates lengthy names to prevent potential conflicts with other symbols already present in the native project.

  • We use this library in the C program. extern libnative_ExportedSymbols* libnative_symbols(void); The extrenkeyword in C is used to declare a variable as a global variable, such that a variable declared in another scope of the same file or another file can be accessed from anywhere in the program. The function libnative_symbols allows us to open the way from the native code to the Kotlin/Native library, an entry point for us. The library name is used as a prefix for the function name. We don't have the concept of class and object, so to call a function is via references. This libnative_kref_api_usecases_GetPostUseCase is already defined in the mapping(.h file), using which we refer forward.
#include <stdio.h>
#include "libnative_api.h"

int main () {
libnative_ExportedSymbols* lib = libnative_symbols();

libnative_kref_api_usecases_GetPostUseCase br = lib->kotlin.root.api.usecases.GetPostUseCase.GetPostUseCase();
const char* pr = lib->kotlin.root.api.usecases.GetPostUseCase.getPostDirect(br);

printf("%s",pr);
}
  • Finally, run the commands clang main.c libnative.dylib(for Mac), ​​gcc main.c libnative.so(for Linux). This command generates our a.out file and runs the command ./a.out to execute the program. Note: Make sure that both libnative.dylib and libnative_api.h are in the same directory as main.c
Expected Output

Interop with Apple Framework

Kotlin/Native also helps with the interoperability with Apple Framework for macOS and iOS. These frameworks consist of all the binaries and requirements needed for interop with Objective-C and Swift. The implementation of the code is similar to our dynamic library. All other dependencies and configurations are mostly the same only a change in dependency of the client used. We have to define the framework we want to build for in our case it isiosSimulatorArm64, just for testing purposes. The only difference we find in our build.gradle.kts

kotlin {
val ktorVersion = "2.2.2"
iosSimulatorArm64("native") {
binaries {
framework {
baseName = "apidemo"
}
}
sourceSets {
val nativeMain by getting{
dependencies {
....
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
}
}
val nativeTest by getting
}
}
}

We are done with the Kotlin coding part. We will convert this to a dynamic library, and we will consume this in the C program. Furthermore, we need to run the command.

./gradlew linkNative

These will build new build/bin/native/debugFramework and build/bin/native/releaseFramework Simply import this framework to our iOS Simulator App and start working with our app. We are not using showing since it is out of the scope of this discussion.

The series goes over Kotlin/Native, which is an LLVM backend for the Kotlin compiler, runtime implementation, and native code generation facility that uses the LLVM toolchain. It compiles Kotlin code into native binaries, making it ideal for platforms where virtual machines are not desirable or possible, or for producing a reasonably-sized self-contained program that does not require additional execution runtime. The series also goes over the Kotlin/Native architecture, which includes source code, Intermediate Representation (IR), a JVM-based environment, a compiler frontend, a compiler backend, and runtime implementation. It shows how to use the C Library in Kotlin code, convert Kotlin/Native code to a dynamic library and use it in C, and interact with the Apple Framework.

For any doubts and suggestions, you can reach out on my Instagram, or LinkedIn. Follow me for Kotlin content and more. Happy Coding!

I will well appreciate one of these 👏

--

--

Android(L2) @Gojek | Mobile Developer | Backend Developer (Java/Kotlin)