W3cubDocs

/Kotlin

Mapping Function Pointers from C

Last Updated 15 April 2019
Function pointers from C and how they look in Kotlin/Native

This is the third post in the series. The very first tutorial is Mapping Primitive Data Types from C. There are also Mapping Struct and Union Types from C and Mapping Strings from C tutorials.

In this tutorial We will learn how to:

Mapping Function Pointer Types from C

The best way to understand the mapping between Kotlin and C is to try a tiny example. We declare a function that accepts a function pointer as a parameter and another function that returns a function pointer.

Kotlin/Native comes with the cinterop tool; the tool generates bindings between the C language and Kotlin. It uses a .def file to specify a C library to import. More details on this are in the Interop with C Libraries tutorial.

The quickest way to try out C API mapping is to have all C declarations in the interop.def file, without creating any .h of .c files at all. Then place the C declarations in a .def file after the special --- separator line:

---

int myFun(int i) {
  return i+1;
}

typedef int  (*MyFun)(int);

void accept_fun(MyFun f) {
  f(42);
}

MyFun supply_fun() {
  return myFun;
}

The interop.def file is enough to compile and run the application or open it in an IDE. Now it is time to create project files, open the project in IntelliJ IDEA and run it.

Inspecting Generated Kotlin APIs for a C library

While it is possible to use the command line, either directly or by combining it with a script file (i.e., sh or bat file), we should notice, that it does not scale well for big projects that have hundreds of files and libraries. It is then better to use the Kotlin/Native compiler with a build system, as it helps to download and cache the Kotlin/Native compiler binaries and libraries with transitive dependencies and run the compiler and tests. Kotlin/Native can use the Gradle build system through the kotlin-multiplatform plugin.

We covered the basics of setting up an IDE compatible project with Gradle in the A Basic Kotlin/Native Application tutorial. Please check it out if you are looking for detailed first steps and instructions on how to start a new Kotlin/Native project and open it in IntelliJ IDEA. In this tutorial, we'll look at the advanced C interop related usages of Kotlin/Native and multiplatform builds with Gradle.

First, let's create a project folder. All the paths in this tutorial will be relative to this folder. Sometimes the missing directories will have to be created before any new files can be added.

We'll use the following build.gradle build.gradle.kts Gradle build file with the following contents:

plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.3.21'
}

repositories {
    mavenCentral()
}

kotlin {
  macosX64("native") {
    compilations.main.cinterops {
      interop 
    }
    
    binaries {
      executable()
    }
  }
}

wrapper {
  gradleVersion = "5.3.1"
  distributionType = "ALL"
}
plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.3.21'
}

repositories {
    mavenCentral()
}

kotlin {
  linuxX64("native") {
    compilations.main.cinterops {
      interop 
    }
    
    binaries {
      executable()
    }
  }
}

wrapper {
  gradleVersion = "5.3.1"
  distributionType = "ALL"
}
plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.3.21'
}

repositories {
    mavenCentral()
}

kotlin {
  mingwX64("native") {
    compilations.main.cinterops {
      interop 
    }
    
    binaries {
      executable()
    }
  }
}

wrapper {
  gradleVersion = "5.3.1"
  distributionType = "ALL"
}
plugins {
    kotlin("multiplatform") version "1.3.21"
}

repositories {
    mavenCentral()
}

kotlin {
  macosX64("native") {
    val main by compilations.getting
    val interop by main.cinterops.creating
    
    binaries {
      executable()
    }
  }
}

tasks.withType<Wrapper> {
  gradleVersion = "5.3.1"
  distributionType = Wrapper.DistributionType.ALL
}
plugins {
    kotlin("multiplatform") version "1.3.21"
}

repositories {
    mavenCentral()
}

kotlin {
  linuxX64("native") {
    val main by compilations.getting
    val interop by main.cinterops.creating
    
    binaries {
      executable()
    }
  }
}

tasks.withType<Wrapper> {
  gradleVersion = "5.3.1"
  distributionType = Wrapper.DistributionType.ALL
}
plugins {
    kotlin("multiplatform") version "1.3.21"
}

repositories {
    mavenCentral()
}

kotlin {
  mingwX64("native") {
    val main by compilations.getting
    val interop by main.cinterops.creating
    
    binaries {
      executable()
    }
  }
}

tasks.withType<Wrapper> {
  gradleVersion = "5.3.1"
  distributionType = Wrapper.DistributionType.ALL
}

The prepared project sources can be downloaded directly from GitHub. GitHub. GitHub. GitHub. GitHub. GitHub.

The project file configures the C interop as an additional step of the build. Let's move the interop.def file to the src/nativeInterop/cinterop directory. Gradle recommends using conventions instead of configurations, for example, the source files are expected to be in the src/nativeMain/kotlin folder. By default, all the symbols from C are imported to the interop package, we may want to import the whole package in our .kt files. Check out the kotlin-multiplatform plugin documentation to learn about all the different ways you could configure it.

Let's create a src/nativeMain/kotlin/hello.kt stub file with the following content to see how C primitive type declarations are visible from Kotlin:

import interop.*

fun main() {
  println("Hello Kotlin/Native!")
  
  accept_fun(/*fix me */)
  val useMe = supply_fun()
}

Now we are ready to open the project in IntelliJ IDEA and to see how to fix the example project. While doing that, we'll examine how C functions are mapped into Kotlin/Native declarations.

C Function Pointers in Kotlin

With the help of IntelliJ IDEA's Goto Declaration or compiler errors we see the following declarations for our C functions:

fun accept_fun(f: MyFun? /* = CPointer<CFunction<(Int) -> Int>>? */)
fun supply_fun(): MyFun? /* = CPointer<CFunction<(Int) -> Int>>? */

fun myFun(i: kotlin.Int): kotlin.Int

typealias MyFun = kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlin.Int) -> kotlin.Int>>

typealias MyFunVar = kotlinx.cinterop.CPointerVarOf<lib.MyFun>

We see that our function typedef from C has been turned into Kotlin typealias. It uses CPointer<..> type to represent the pointer parameters, and CFunction<(Int)->Int> to represent the function signature. There is an invoke operator extension function available for all CPointer<CFunction<..> types, so that it is possible to call it as we would call any other function in Kotlin.

Passing Kotlin Function as C Function Pointer

It is the time to try using C Functions from our Kotlin program. Let's call the accept_fun function and pass the C function pointer to a Kotlin lambda:

fun myFun() {
  accept_fun(staticCFunction<Int, Int> { it + 1 })
}

We use the staticCFunction{..} helper function from Kotlin/Native to wrap a Kotlin lambda function into a C function pointer. It only allows having unbound and non-capturing lambda functions. For example, it is not able to use a local variable from the function. We may only use globally visible declarations. Throwing exceptions from a staticCFunction{..} will end up in non-deterministic side-effects. It is vital to make sure that we are not throwing any sudden exceptions from it.

Using the C Function Pointer from Kotlin

The next step is to call a C function pointer from a C pointer that we have from the supply_fun() call:

fun myFun2() {
  val functionFromC = supply_fun() ?: error("No function is returned")
  
  functionFromC(42)
}

Kotlin turns the function pointer return type into a nullable CPointer<CFunction<..> object. There is the need to explicitly check for null first. We use elvis operator for that. The cinterop tool helps us to turn a C function pointer into an easy to call object in Kotlin. This is what we did on the last line.

Fixing the Code

We've seen all definitions and it is time to fix and run the code. Let's run the runDebugExecutableNative Gradle task in the IDE or use the following command to run the code:

./gradlew runDebugExecutableNative
./gradlew runDebugExecutableNative
gradlew.bat runDebugExecutableNative

The code in the hello.kt file may look like this:

import interop.*
import kotlinx.cinterop.*

fun main() {
  println("Hello Kotlin/Native!")
 
  val cFunctionPointer = staticCFunction<Int, Int> { it + 1 }
  accept_fun(cFunctionPointer)

  val funFromC = supply_fun() ?: error("No function is returned")
  funFromC(42)
}

Next Steps

We will continue exploring more C language types and their representation in Kotlin/Native in next tutorials:

The C Interop documentation documentation covers more advanced scenarios of the interop.

© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/tutorials/native/mapping-function-pointers-from-c.html