Kotlin Help

Mapping function pointers from C – tutorial

Let's explore which C function pointers are visible from Kotlin and examine advanced C interop-related use cases of Kotlin/Native and multiplatform Gradle builds.

In this tutorial, you'll:

Mapping function pointer types from C

To understand the mapping between Kotlin and C, let's declare two functions: one that accepts a function pointer as a parameter and another that returns a function pointer.

In the first part of the series of the series, you've already created a C library with the necessary files. For this step, update the declarations in the interop.def file after the --- separator:

--- 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 provides everything necessary to compile, run, or open the application in an IDE.

Inspect generated Kotlin APIs for a C library

Let's see how C function pointers are mapped into Kotlin/Native and update your project:

  1. In src/nativeMain/kotlin, update your hello.kt file from the previous tutorial with the following content:

    import interop.* import kotlinx.cinterop.ExperimentalForeignApi @OptIn(ExperimentalForeignApi::class) fun main() { println("Hello Kotlin/Native!") accept_fun(/* fix me*/) val useMe = supply_fun() }
  2. Use IntelliJ IDEA's Go to declaration command (Cmd + B/Ctrl + B) to navigate to the following generated API for C functions:

    fun myFun(i: kotlin.Int): kotlin.Int fun accept_fun(f: kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlin.Int) -> kotlin.Int>>? /* from: interop.MyFun? */) fun supply_fun(): kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlin.Int) -> kotlin.Int>>? /* from: interop.MyFun? */

As you can see, C function pointers are represented in Kotlin using CPointer<CFunction<...>>. The accept_fun() function takes an optional function pointer as a parameter, while supply_fun() returns a function pointer.

CFunction<(Int) -> Int> represents the function signature, and CPointer<CFunction<...>>? represents a nullable function pointer. There is an invoke operator extension function available for all CPointer<CFunction<...>> types, allowing you to call function pointers as if they were regular Kotlin functions.

Pass Kotlin function as a C function pointer

It's time to try using C functions from Kotlin code. Call the accept_fun() function and pass the C function pointer to a Kotlin lambda:

import interop.* import kotlinx.cinterop.staticCFunction import kotlinx.cinterop.ExperimentalForeignApi @OptIn(ExperimentalForeignApi::class) fun myFun() { accept_fun(staticCFunction<Int, Int> { it + 1 }) }

This call uses the staticCFunction {} helper function from Kotlin/Native to wrap a Kotlin lambda function into a C function pointer. It allows only unbound and non-capturing lambda functions. For example, it cannot capture a local variable from the function, only globally visible declarations.

Ensure that the function doesn't throw any exceptions. Throwing exceptions from a staticCFunction {} causes non-deterministic side effects.

Use the C function pointer from Kotlin

The next step is to invoke a C function pointer returned from the supply_fun() call:

import interop.* import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.invoke @OptIn(ExperimentalForeignApi::class) 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. You need to first explicitly check for null, which is why the Elvis operator is used in the code above. The cinterop tool allows you to call a C function pointer as a regular Kotlin function call: functionFromC(42).

Update Kotlin code

Now that you've seen all the definitions, try to use them in your project. The code in the hello.kt file may look like this:

import interop.* import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.invoke import kotlinx.cinterop.staticCFunction @OptIn(ExperimentalForeignApi::class) 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) }

To verify that everything works as expected, run the runDebugExecutableNative Gradle task in your IDE or use the following command to run the code:

./gradlew runDebugExecutableNative

Next step

In the next part of the series, you'll learn how strings are mapped between Kotlin and C:

Proceed to the next part

See also

Learn more in the Interoperability with C documentation that covers more advanced scenarios.

Last modified: 27 March 2025