映射来自 C 语言的原生数据类型——教程

This is the first part of the Mapping Kotlin and C tutorial series.

First step Mapping primitive data types from C
Second step Mapping struct and union types from C
Third step Mapping function pointers
Fourth step 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 data types are visible in Kotlin/Native and vice versa and examine advanced C interop-related use cases of Kotlin/Native and multiplatform Gradle builds.

在本教程中会:

You can use the command line to generate a Kotlin library, either directly or with a script file (such as .sh or .bat file). However, this approach doesn't scale well for larger projects that have hundreds of files and libraries. Using a build system simplifies the process by downloading and caching the Kotlin/Native compiler binaries and libraries with transitive dependencies, as well as by running the compiler and tests. Kotlin/Native can use the Gradle build system through the Kotlin Multiplatform plugin.

C 语言中的类型

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

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

还有一些更多的具体类型:

  • 布尔类型(源于 C99
  • size_tptrdiff_t(也作 ssize_t
  • 固定长度的整型例如:int32_tuint64_t(源于 C99

C 语言中还有以下类型限定符:constvolatilerestrictatomic

Let's see which C data types are visible in Kotlin.

Create a C library

In this tutorial, you won't create a lib.c source file, which is only necessary if you want to compile and run your C library. For this setup, you'll only need a .h header file that is required for running the cinterop tool.

The cinterop tool generates a Kotlin/Native library (a .klib file) for each set of .h files. The generated library helps bridge calls from Kotlin/Native to C. It includes Kotlin declarations that correspond to the definitions from the .h files.

To create a C library:

  1. Create an empty folder for your future project.
  2. Inside, create a lib.h file with the following content to see how C functions are mapped into Kotlin:

    #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 这贴了解更多细节。

  3. Create the lib.def definition file with the following content:

    headers = lib.h
    
  4. 将宏或其他 C 定义包含在 cinterop 工具生成的代码中会很有帮助。这样, 方法体同样被编译以及完全包含到二进制文件中。使用这个特性,可以创建一个无需 C 编译器的可运行示例。

  5. 为做到这点,在新 interop.def 文件中 --- 分隔符之后,为 lib.h 文件中的 C 函数添加实现:

    
    ---
    
    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 中编译、运行或打开应用程序所需的一切。

Create a Kotlin/Native project

See the Get started with Kotlin/Native tutorial for detailed first steps and instructions on how to create a new Kotlin/Native project and open it in IntelliJ IDEA.

{style="tip"}

To create project files:

  1. In your project folder, create a build.gradle(.kts) Gradle build file with the following content:

【Kotlin】

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

    repositories {
        mavenCentral()
    }

    kotlin {
        macosArm64("native") {    // macOS on Apple Silicon
        // macosX64("native") {   // macOS on x86_64 platforms
        // linuxArm64("native") { // Linux on ARM64 platforms 
        // linuxX64("native") {   // Linux on x86_64 platforms
        // mingwX64("native") {   // on Windows
            val main by compilations.getting
            val interop by main.cinterops.creating

            binaries {
                executable()
            }
        }
    }

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

【Groovy】

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

    repositories {
        mavenCentral()
    }

    kotlin {
        macosArm64("native") {    // Apple Silicon macOS
        // macosX64("native") {   // macOS on x86_64 platforms
        // linuxArm64("native") { // Linux on ARM64 platforms
        // linuxX64("native") {   // Linux on x86_64 platforms
        // mingwX64("native") {   // Windows
            compilations.main.cinterops {
                interop 
            }

            binaries {
                executable()
            }
        }
    }

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

The project file configures the C interop as an additional build step. Check out the Multiplatform Gradle DSL reference to learn about different ways you can configure it.

  1. Move your interop.def, lib.h, and lib.def files to the src/nativeInterop/cinterop directory.
  2. Create a src/nativeMain/kotlin directory. This is where you should place all the source files, following Gradle's recommendations on using conventions instead of configurations.

    By default, all the symbols from C are imported to the interop package.

  3. src/nativeMain/kotlin 中,使用以下内容创建一个 hello.kt 存根文件:

     import interop.*
     import kotlinx.cinterop.ExperimentalForeignApi
    
     @OptIn(ExperimentalForeignApi::class)
     fun main() {
         println("Hello Kotlin/Native!")
    
         ints(/* fix me*/)
         uints(/* fix me*/)
         doubles(/* fix me*/)
     }
    

You'll complete the code later as you learn how C primitive type declarations look from the Kotlin side.

Inspect generated Kotlin APIs for a C library

Let's see how C primitive types are mapped into Kotlin/Native and update the example project accordingly.

Use IntelliJ IDEA's Go to declaration command (Cmd + B/Ctrl + B) to navigate to the following generated API for C functions:

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)

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

Update Kotlin code

Now that you've seen the C definitions, you can update your Kotlin code. hello.kt 文件中的代码最终看起来会是这样的:

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)
}

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 struct and union types are mapped between Kotlin and C:

Proceed to the next part

See also

Learn more in the Interoperability with C documentation that covers more advanced scenarios.