映射来自 C 语言的函数指针——教程
This is the third part of the Mapping Kotlin and C tutorial series. Before proceeding, make sure you've completed the previous steps.
Mapping primitive data types from C
Mapping struct and union types from C
Mapping function pointers
Mapping strings from C
The C libraries import is Experimental. All Kotlin declarations generated by the cinterop tool from C libraries should have the
@ExperimentalForeignApi
annotation.Native platform libraries shipped with Kotlin/Native (like Foundation, UIKit, and POSIX) require opt-in only for some APIs.
{style="warning"}
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.
在本教程中会:
映射 C 中的函数指针类型
理解在 Kotlin 与 C 之间进行映射的方式是,我们来声明两个函数:一个接收函数指针作为参数,另一个返回一个函数指针。
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;
}
interop.def
文件提供了在 IDE 中编译、运行或打开应用程序所需的一切。
探查为 C 库生成的 Kotlin API
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
kotlin.Int>>? /* from: interop.MyFun? */) fun supply_fun(): kotlinx.cinterop.CPointer kotlin.Int>>? /* from: interop.MyFun? */
As you can see, C function pointers are represented in Kotlin using CPointer
. 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
represents a nullable
function pointer. There is an invoke
operator extension function available for all CPointer
types,
allowing you to call function pointers as if they were regular Kotlin functions.
将 Kotlin 函数作为 C 函数指针传递
是时候尝试在 Kotlin 代码中使用 C 函数了。调用 accept_fun()
函数并传递 C 函数指针到一个 Kotlin lambda 表达式:
import interop.*
import kotlinx.cinterop.staticCFunction
import kotlinx.cinterop.ExperimentalForeignApi
@OptIn(ExperimentalForeignApi::class)
fun myFun() {
accept_fun(staticCFunction { it + 1 })
}
该调用使用 Kotlin/Native 中的 staticCFunction {}
辅助函数将一个 Kotlin lambda 函数包装为 C
函数指针。它只能是非绑定的以及没有发生变量捕捉的 lambda functions。举例来说,它不能捕获函数中的局部变量,只能捕获全局可见的声明。
Ensure that the function doesn't throw any exceptions. 在 staticCFunction {}
中抛出异常会导致非确定性的副作用。
在 Kotlin 中使用 C 函数指针
接下来是调用 supply_fun()
返回的 C 函数指针:
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 将函数指针返回类型转换为一个可空的 CPointer
对象。需要首先显式检测 null
值,这就是为什么上述代码中使用了 Elvis 操作符。
cinterop
工具让你可以像调用常规 Kotlin 函数一样调用 C 函数指针:functionFromC(42)
。
Update Kotlin code
Now that you've seen all the definitions, try to use them in your project.
hello.kt
文件中的代码最终看起来会是这样的:
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 { 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
接下来
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.