7.3.3.2.3 从 C 映射函数指针 – 教程

原文链接: https://kotlinlang.org/docs/mapping-function-pointers-from-c.html

7.3.3.2.3 从 C 映射函数指针 – 教程

注意: C 库导入处于 Beta 阶段。由 cinterop 工具从 C 库生成的所有 Kotlin 声明都应带有 @ExperimentalForeignApi 注解。随 Kotlin/Native 提供的原生平台库(例如 Foundation、UIKit 和 POSIX)只对部分 API 要求选择启用。

我们来探索哪些 C 函数指针在 Kotlin 中可见,并考察 Kotlin/Native 与多平台 Gradle 构建中与 C 互操作相关的高级用例。

在本教程中,你将:

从 C 映射函数指针类型

为了理解 Kotlin 与 C 之间的映射,我们来声明两个函数:一个接受函数指针作为参数,另一个返回函数指针。

在本系列的第一部分中,你已经创建了包含必要文件的 C 库。对于这一步,请更新 interop.def 文件中 --- 分隔符之后的声明:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

---

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

我们来看看 C 函数指针如何映射到 Kotlin/Native,并更新你的项目:

  1. 在 src/nativeMain/kotlin 中,把上一个教程中的 hello.kt 文件更新为以下内容:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
   import interop.*
   import kotlinx.cinterop.ExperimentalForeignApi

   @OptIn(ExperimentalForeignApi::class)
   fun main() {
       println("Hello Kotlin/Native!")

       accept_fun(/* 待补充 */)
       val useMe = supply_fun()
   }
  1. 使用 IntelliJ IDEA 的转到声明命令(Cmd + B/Ctrl + B)导航到为 C 函数生成的以下 API:
1
2
3
   fun myFun(i: kotlin.Int): kotlin.Int
   fun accept_fun(f: kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlin.Int) -> kotlin.Int>>? /* 来自:interop.MyFun? */)
   fun supply_fun(): kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlin.Int) -> kotlin.Int>>? /* 来自:interop.MyFun? */

如你所见,C 函数指针在 Kotlin 中用 CPointer<CFunction<...>> 表示。accept_fun() 函数接受一个可选的函数指针作为参数,而 supply_fun() 返回一个函数指针。

CFunction<(Int) -> Int> 表示函数签名,而 CPointer<CFunction<...>>? 表示可空的函数指针。所有 CPointer<CFunction<...>> 类型都有可用的 .invoke() 运算符扩展函数,让你可以像调用普通 Kotlin 函数那样调用函数指针。

把 Kotlin 函数作为 C 函数指针传递

现在该在 Kotlin 代码中试用 C 函数了。调用 accept_fun() 函数,并把 C 函数指针传给它一个 Kotlin lambda:

1
2
3
4
5
6
7
8
import interop.*
import kotlinx.cinterop.staticCFunction
import kotlinx.cinterop.ExperimentalForeignApi

@OptIn(ExperimentalForeignApi::class)
fun myFun() {
    accept_fun(staticCFunction<Int, Int> { it + 1 })
}

该调用使用了 Kotlin/Native 中的 staticCFunction {} 辅助函数,把 Kotlin lambda 函数包装为 C 函数指针。它只允许非绑定且不捕获的 lambda 函数。例如,它不能捕获函数中的局部变量,只能使用全局可见的声明。

请确保该函数不抛出任何异常。从 staticCFunction {} 中抛出异常会导致不确定的副作用。

在 Kotlin 中使用 C 函数指针

下一步是调用由 supply_fun() 调用返回的 C 函数指针:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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<CFunction<>> 对象。你需要先显式检查 null,这就是上面代码中使用 Elvis 运算符的原因。cinterop 工具允许你像调用普通 Kotlin 函数那样调用 C 函数指针:functionFromC(42)。

更新 Kotlin 代码

现在你已经看到了所有定义,试着在你的项目中使用它们。hello.kt 文件中的代码可能如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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)
}

要验证一切是否按预期工作,请在你的 IDE 中运行 runDebugExecutable<YourTargetName> Gradle 任务,或在终端中使用控制台命令,例如:

1
./gradlew runDebugExecutableMacosArm64

下一步

在本系列的下一部分中,你将了解字符串如何在 Kotlin 与 C 之间映射:

上一步

下一步

另请参阅

在与 C 互操作文档中了解更多内容,其中涵盖了更多高级场景。