7.3.3.2.1 从 C 映射原始数据类型 – 教程

原文链接: https://kotlinlang.org/docs/mapping-primitive-data-types-from-c.html

7.3.3.2.1 从 C 映射原始数据类型 – 教程

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

我们来探索哪些 C 数据类型在 Kotlin/Native 中可见(以及反向的情况),并考察 Kotlin/Native 与多平台 Gradle 构建中与 C 互操作相关的高级用例。

在本教程中,你将:

你可以使用命令行直接生成 Kotlin 库,也可以通过脚本文件(例如 .sh 或 .bat 文件)来完成。不过,对于包含数百个文件和库的较大项目,这种方式扩展性不好。使用构建系统可以下载并缓存 Kotlin/Native 编译器二进制文件及其传递依赖的库,并负责运行编译器和测试,从而简化整个过程。Kotlin/Native 可以通过 Kotlin Multiplatform 插件使用 Gradle 构建系统。

C 语言中的类型

C 编程语言具有以下数据类型:

  • 基本类型:char, int, float, double,可带 signed, unsigned, short, long 修饰符
  • 结构体、联合体、数组
  • 指针
  • 函数指针

还有一些更具体的类型:

  • 布尔类型(来自 C99)
  • size_t 和 ptrdiff_t(还有 ssize_t)
  • 固定宽度整数类型,例如 int32_t 或 uint64_t(来自 C99)

C 语言中还有以下类型限定符:const、volatile、restrict、atomic。

我们来看看哪些 C 数据类型在 Kotlin 中可见。

创建 C 库

在本教程中,你不会创建 lib.c 源文件,只有在你想要编译并运行 C 库时才需要它。对于这个设置,你只需要一个 .h 头文件,这是运行 cinterop 工具所必需的。

cinterop 工具会为每组 .h 文件生成一个 Kotlin/Native 库(一个 .klib 文件)。生成的库有助于把 Kotlin/Native 到 C 的调用桥接起来。它包含与 .h 文件中定义相对应的 Kotlin 声明。

要创建 C 库:

  1. 为你的未来项目创建一个空文件夹。
  2. 在其中创建一个包含以下内容的 lib.h 文件,以了解 C 函数如何映射到 Kotlin:
1
2
3
4
5
6
7
8
   #ifndef LIB2_H_INCLUDED
   #define LIB2_H_INCLUDED

   void ints(char c, short d, int e, long f);
   void uints(unsigned char c, unsigned short d, unsigned int e, unsigned long f);
   void doubles(float a, double b);

   #endif

该文件没有 extern "C" 块,本示例不需要它,但如果你使用 C++ 和重载函数,则可能需要。更多细节请参阅这个 Stackoverflow 讨论串。

  1. 创建包含以下内容的 lib.def 定义文件:
1
   headers = lib.h
  1. 把宏或其他 C 定义包含进 cinterop 工具生成的代码中可能会很有帮助。这样方法体也会被编译并完整包含在二进制文件中。借助这一特性,你无需 C 编译器就能创建一个可运行的示例。

为此,请在新的 interop.def 文件中、--- 分隔符之后为 lib.h 文件中的 C 函数添加实现:

1
2
3
4
5
6

   ---

   void ints(char c, short d, int e, long f) { }
   void uints(unsigned char c, unsigned short d, unsigned int e, unsigned long f) { }
   void doubles(float a, double b) { }

interop.def 文件提供了编译、运行或在 IDE 中打开该应用所需的一切。

创建 Kotlin/Native 项目

提示: 关于详细的第一步以及如何创建新的 Kotlin/Native 项目并在 IntelliJ IDEA 中打开它,请参阅 Kotlin/Native 入门教程。

要创建项目文件:

  1. 在你的项目文件夹中,创建一个包含以下内容的 build.gradle(.kts) Gradle 构建文件:

Kotlin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

    plugins {
        kotlin("multiplatform") version "2.4.20"
    }

    repositories {
        mavenCentral()
    }

    kotlin {
        macosArm64() // 在 Apple 芯片上的 macOS
        // linuxArm64() // ARM64 平台上的 Linux
        // linuxX64()   // x86_64 平台上的 Linux
        // mingwX64()   // x86_64 平台上的 Windows

        targets.withType<KotlinNativeTarget>().configureEach {
            val main by compilations.getting
            val interop by main.cinterops.creating

            binaries {
                executable()
            }
        }
    }

    tasks.wrapper {
        gradleVersion = "9.7.0"
        distributionType = Wrapper.DistributionType.BIN
    }

Groovy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

    plugins {
        id 'org.jetbrains.kotlin.multiplatform' version '2.4.20'
    }

    repositories {
        mavenCentral()
    }

    kotlin {
        macosArm64() // Apple 芯片的 macOS
        // linuxArm64() // ARM64 平台上的 Linux
        // linuxX64()   // x86_64 平台上的 Linux
        // mingwX64()   // x86_64 平台上的 Windows

        targets.withType(KotlinNativeTarget).configureEach {
            compilations.main.cinterops {
                interop
            }

            binaries {
                executable()
            }
        }
    }

    wrapper {
        gradleVersion = '9.7.0'
        distributionType = 'BIN'
    }

该工程文件把 C 互操作配置为一个额外的构建步骤。请查看 Multiplatform Gradle DSL 参考以了解配置它的不同方式。

  1. 把你的 interop.def、lib.h 和 lib.def 文件移动到 src/nativeInterop/cinterop 目录中。
  2. 创建 src/nativeMain/kotlin 目录。按照 Gradle 关于使用约定而非配置的建议,所有源文件都应放在这里。

默认情况下,来自 C 的所有符号都被导入到 interop 包中。

  1. 在 src/nativeMain/kotlin 中,创建一个包含以下内容的 hello.kt 桩文件:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    import interop.*
    import kotlinx.cinterop.ExperimentalForeignApi

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

        ints(/* 待补充 */)
        uints(/* 待补充 */)
        doubles(/* 待补充 */)
    }

在了解了 C 原始类型声明从 Kotlin 一侧看是什么样子之后,你稍后会补全这些代码。

检查为 C 库生成的 Kotlin API

我们来看看 C 原始类型如何映射到 Kotlin/Native,并相应地更新示例项目。

使用 IntelliJ IDEA 的转到声明命令(Cmd + B/Ctrl + B)导航到为 C 函数生成的以下 API:

1
2
3
fun ints(c: kotlin.Byte, d: kotlin.Short, e: kotlin.Int, f: kotlin.Long)
fun uints(c: kotlin.UByte, d: kotlin.UShort, e: kotlin.UInt, f: kotlin.ULong)
fun doubles(a: kotlin.Float, b: kotlin.Double)

除 char 类型之外,C 类型都是直接映射的;char 映射为 kotlin.Byte,因为它通常是 8 位有符号值:

| C | Kotlin |

| char | kotlin.Byte | | unsigned char | kotlin.UByte | | short | kotlin.Short | | unsigned short | kotlin.UShort | | int | kotlin.Int | | unsigned int | kotlin.UInt | | long long | kotlin.Long | | unsigned long long | kotlin.ULong | | float | kotlin.Float | | double | kotlin.Double |

更新 Kotlin 代码

现在你已经看到了 C 定义,可以更新你的 Kotlin 代码了。hello.kt 文件中的最终代码可能如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi

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

    ints(1, 2, 3, 4)
    uints(5u, 6u, 7u, 8u)
    doubles(9.0f, 10.0)
}

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

1
./gradlew runDebugExecutableMacosArm64

下一步

在本系列的下一部分中,你将了解结构体和联合体类型如何在 Kotlin 与 C 之间映射:

下一步

另请参阅

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