映射来自 C 语言的原始数据类型
作者 | Eugene Petrenko,乔禹昂(翻译) |
最近更新 | 2019-04-15 |
在本教程中,我们将学习到如何将 C 的数据类型在 Kotlin/Native 中可见,反之亦然。我们将会:
- 看到什么是 C 语言中的数据类型
- 创建一个小型 C 库来使这些类型向外暴露
- 在 C 库中查看生成的 Kotlin API
- 找到如何将 Kotlin 中的原始类型映射到 C 的方法
C 语言中的类型
我们如何在 C 语言中拥有类型?我们先来列出所有这些类型。我引用了这篇 C 数据类型维基百科上的文章作为基础。 在 C 语言中有如下这些类型:
- 基本类型
char、int、float、double
以及带修饰符的signed、unsigned、short、long
- 结构体、联合体、数组
- 指针
- 函数指针
还有一些更多的具体类型:
C 语言中还有以下类型限定符:const
、volatile
、restruct
、atomic
。
在 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 互操作文档涵盖了更多的高级互操作场景