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, 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.ktfile 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.