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:
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:
In
src/nativeMain/kotlin
, update yourhello.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() }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:
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:
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:
To verify that everything works as expected, run the runDebugExecutableNative
Gradle task in your IDE or use the following command to run the code:
Next step
In the next part of the series, you'll learn how strings are mapped between Kotlin and C:
See also
Learn more in the Interoperability with C documentation that covers more advanced scenarios.