改进翻译

映射来自 C 语言的原始数据类型

作者 Eugene Petrenko,乔禹昂(翻译)
最近更新 2019-04-15
来自 C 语言的原始数据类型及其在 Kotlin/Native 中的样子

在本教程中,我们将学习到如何将 C 的数据类型在 Kotlin/Native 中可见,反之亦然。我们将会:

C 语言中的类型

我们如何在 C 语言中拥有类型?我们先来列出所有这些类型。我引用了这篇 C 数据类型维基百科上的文章作为基础。 在 C 语言中有如下这些类型:

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

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

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

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

在 Kotlin 中查看 C 数据类型的最佳方法就是尝试一下

一个 C 库示例

我们创建一个 lib.h 文件来看看如何将 C 函数映射到 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++ 的重载函数的时候这也许是必要的。该 C++ 兼容性问答包含了更多关于此内容的细节。

对于每组 .h 文件, 我们将使用来自 Kotlin/Native 的 cinterop C 库来生成 Kotlin/Native 库, 或者 .klib。生成的库将会桥接 Kotlin/Native 到 C 语言的调用。这包括.h 文件中各定义的 Kotlin 声明。 只需要一个 .h 文件来运行 cinterop 工具。并且我们不需要创建一个 lib.c 文件,除非我们想编译并运行该示例。 更多关于这些内容的细节被涵盖在 C 库页面。这篇教程的内容足够我们使用下面的内容来创建 lib.def 文件:

headers = lib.h

我们可以在 --- 分隔符之后将所有声明直接包含在 .def 文件中。 将宏或其他 C 定义包含在 cinterop 工具生成的代码中会很有帮助。 方法体同样被编译以及完全包含到二进制文件中。我们使用这个功能并且在不使用 C 编译器的情况下来得到一个可运行的示例。 为了实现这个,我们需要在 lib.h 文件中添加 C 函数的实现, 并将这些函数放入 .def 文件中。 我们将得到如下的interop.def 结果:


---

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 中打开它。 现在是时候创建工程文件,且在 IntelliJ IDEA 中打开并运行它。

探查为 C 库生成的 Kotlin API

虽然可以使用命令行或直接通过将它与脚本文件(即 sh 或 bat 文件)相结合,但我们应该注意到, 对于拥有数百个文件以及库的大型项目来说,这不能很好地扩展。 所以最好使用附带构建系统的 Kotlin/Native 编译器,它可以帮助下载与缓存 Kotlin/Native 编译器二进制文件与库传递依赖,以及运行该编译器并测试。 Kotlin/Native 可以通过 kotlin 多平台插件来使用 Gradle 构建系统。

基本 Kotlin/Native 应用程序这篇教程涵盖了使用 Gradle 创建 IDE 兼容工程的基础知识。如果你正在寻找关于第一步的更多细节以及如何开始一个新的 Kotlin/Native 项目并在 IntelliJ IDEA 中打开它的说明,则请你阅读这篇教程,我们将看到关于在 Kotlin/Native 中进行高级的 C 互操作的相关用法以及使用 multiplatform(Kotlin 多平台插件)及 Gradle 进行构建。

首先,让我们创建一个工程目录。在本教程中的所有路径都是相对于这个目录的。有时在添加任何新文件之前,都必须创建缺少的目录。

我们将使用下面的 build.gradle build.gradle.kts Gradle 构建文件并添加以下内容:

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

repositories {
    mavenCentral()
}

kotlin {
  macosX64("native") {
    compilations.main.cinterops {
      interop 
    }
    
    binaries {
      executable()
    }
  }
}

wrapper {
  gradleVersion = "5.3.1"
  distributionType = "ALL"
}
plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.3.21'
}

repositories {
    mavenCentral()
}

kotlin {
  linuxX64("native") {
    compilations.main.cinterops {
      interop 
    }
    
    binaries {
      executable()
    }
  }
}

wrapper {
  gradleVersion = "5.3.1"
  distributionType = "ALL"
}
plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.3.21'
}

repositories {
    mavenCentral()
}

kotlin {
  mingwX64("native") {
    compilations.main.cinterops {
      interop 
    }
    
    binaries {
      executable()
    }
  }
}

wrapper {
  gradleVersion = "5.3.1"
  distributionType = "ALL"
}
plugins {
    kotlin("multiplatform") version "1.3.21"
}

repositories {
    mavenCentral()
}

kotlin {
  macosX64("native") {
    val main by compilations.getting
    val interop by main.cinterops.creating
    
    binaries {
      executable()
    }
  }
}

tasks.wrapper {
  gradleVersion = "5.3.1"
  distributionType = Wrapper.DistributionType.ALL
}
plugins {
    kotlin("multiplatform") version "1.3.21"
}

repositories {
    mavenCentral()
}

kotlin {
  linuxX64("native") {
    val main by compilations.getting
    val interop by main.cinterops.creating
    
    binaries {
      executable()
    }
  }
}

tasks.wrapper {
  gradleVersion = "5.3.1"
  distributionType = Wrapper.DistributionType.ALL
}
plugins {
    kotlin("multiplatform") version "1.3.21"
}

repositories {
    mavenCentral()
}

kotlin {
  mingwX64("native") {
    val main by compilations.getting
    val interop by main.cinterops.creating
    
    binaries {
      executable()
    }
  }
}

tasks.wrapper {
  gradleVersion = "5.3.1"
  distributionType = Wrapper.DistributionType.ALL
}

这个已经准备好的项目源文件可以直接在这里下载: GitHub GitHub GitHub GitHub GitHub GitHub

该项目文件将 C 互操作配置为构建的附加步骤。 让我们将 interop.def 文件移动到 src/nativeInterop/cinterop 目录。 Gradle 建议使用约定而不是配置, 比如说,这个源文件被期望位于 src/nativeMain/kotlin 文件夹。 默认的,C 中的所有符号都被导入到 interop 包中, 我们也许想要将整个包导入到我们的 .kt 文件。 查看 kotlin 多平台插件文档来学习所有不同的配置方式。

我们使用以下内容来创建一个 src/nativeMain/kotlin/hello.kt 存根文件来查看 C 的原始类型声明是如何在 Kotlin 中可见的:

import interop.*

fun main() {
  println("Hello Kotlin/Native!")
  
  ints(/* fix me*/)
  uints(/* fix me*/)
  doubles(/* fix me*/)
}

现在我们已经准备好在 IntelliJ IDEA 中打开这个工程并且看看如何修正这个示例工程。当我们做完这些之后, 我们将检查 C 的原始类型是如何映射到 Kotlin/Native 的。

Kotlin 中的原始类型

在 IntelliJ IDEA 的 Goto Declaration 或编译器错误的帮助下我们会看到为 C 函数生成的 API:

fun ints(c: Byte, d: Short, e: Int, f: Long)
fun uints(c: UByte, d: UShort, e: UInt, f: ULong)
fun doubles(a: Float, b: 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

修改代码

我们已经看到了所有的定义并且是时候来修改代码了。 我们在 IDE 中运行 runDebugExecutableNative Gradle 任务或使用下面的命令来运行代码:

./gradlew runDebugExecutableNative
./gradlew runDebugExecutableNative
gradlew.bat runDebugExecutableNative

hello.kt 文件中的代码最终看起来会是这样的:

import interop.*

fun main() {
  println("Hello Kotlin/Native!")
  
  ints(1, 2, 3, 4)
  uints(5, 6, 7, 8)
  doubles(9.0f, 10.0)
}

接下来

我们将在接下来的几篇教程中继续探索更复杂的 C 语言类型及其在 Kotlin/Native 中的表示:

这篇 C 互操作文档涵盖了更多的高级互操作场景