教程 - 使用 C Interop 和 libcurl 创建应用程序
本教程演示如何使用 IntelliJ IDEA 创建一个命令行应用程序. 你将学习如何创建一个简单的 HTTP 客户端程序, 它使用 Kotlin/Native 和 libcurl
库, 可以作为原生程序运行在指定的平台上.
输出将是一个可执行的命令行应用程序, 你可以在 macOS 和 Linux 上运行, 发送简单的 HTTP GET 请求.
开始前的准备工作
下载并安装最新版本的 IntelliJ IDEA 和 Kotlin plugin.
在 IntelliJ IDEA 中选择菜单 File | New | Project from Version Control, 克隆 项目模板.
查看项目结构:
模板创建的项目带有你开始工作时所需要的文件和文件夹. 请注意, 如果代码中不包含与特定平台相关的需求, 那么使用 Kotlin/Native 编写的应用程序可以编译到不同的平台. 你的代码放在
nativeMain
目录中, 此外还有对应的nativeTest
目录. 在这个教程中, 请不要修改这些文件夹结构.打开构建脚本文件
build.gradle.kts
, 其中包含项目的设定. 请特别注意构建脚本文件中的以下内容:kotlin { val hostOs = System.getProperty("os.name") val isArm64 = System.getProperty("os.arch") == "aarch64" val isMingwX64 = hostOs.startsWith("Windows") val nativeTarget = when { hostOs == "Mac OS X" && isArm64 -> macosArm64("native") hostOs == "Mac OS X" && !isArm64 -> macosX64("native") hostOs == "Linux" && isArm64 -> linuxArm64("native") hostOs == "Linux" && !isArm64 -> linuxX64("native") isMingwX64 -> mingwX64("native") else -> throw GradleException("Host OS is not supported in Kotlin/Native.") } nativeTarget.apply { binaries { executable { entryPoint = "main" } } } }
创建一个定义文件
编写原生应用程序时, 你经常需要访问没有包含在 Kotlin 标准库 中的某些功能, 比如发起 HTTP 请求, 读写磁盘, 等等.
Kotlin/Native 帮助你使用 C 的标准库, 使你可以利用 C 的整个生态系统, 其中的功能几乎包含你需要的任何东西. Kotlin/Native 带有一组预构建的 平台库, 提供了标准库之外的一些通用功能.
与 C 交互的理想场景是, 象调用 Kotlin 函数一样调用 C 函数, 使用相同的函数签名和规约. 这就是 cinterop
工具可以帮助你的地方. 它输入一个 C 库, 并生成对应的 Kotlin 绑定, 使得库可以象 Kotlin 代码那样使用.
要生成这些绑定, 要创建一个库定义 .def
文件, 包含一些关于需要的头的信息. 在这个应用程序中, 你需要 libcurl
库来发起 HTTP 调用. 创建一个定义文件的步骤如下:
选择
src
文件夹, 使用 File | New | Directory 创建一个新目录.将新目录命名为 nativeInterop/cinterop. 这是头文件位置的默认约定, 如果你使用不同的位置, 也可以在
build.gradle.kts
文件中修改这个设置.选择新建的子文件夹, 使用 File | New | File 创建一个新的
libcurl.def
文件.将你的文件内容更新为以下代码:
headers = curl/curl.h headerFilter = curl/* compilerOpts.linux = -I/usr/include -I/usr/include/x86_64-linux-gnu linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lcurlheaders
是需要生成 Kotlin 桩(stub)代码的头文件列表. 你可以在这里添加多个文件, 每个在新行加一个\
来分隔. 在这个示例中, 只有curl.h
. 引用的文件路径需要存在于系统路径中(在这个示例中, 是/usr/include/curl
).headerFilter
指定具体包含什么. 在 C 中, 当一个文件使用#include
指令引用另一个文件时, 所有的头文件都会被包含. 有时这些文件是不必要的, 你可以添加这个参数, 使用全局模式 进行细微调节.headerFilter
是一个可选的参数, 当库作为系统库安装时, 经常使用这个参数. 你不希望获取外部的依赖项 (比如系统的stdint.h
头文件) 到你使用的库中. 对于优化库的大小, 修正系统与 Kotlin/Native 编译环境之间的潜在的冲突, 这个参数可能是很重要的.后面的行是提供链接器(linker) 和编译器的参数, 在不同的目标平台这些参数可能不同. 在这个示例中, 是针对 macOS (
.osx
后缀) 和 Linux (.linux
后缀) 环境的参数. 也可以使用没有后缀的参数(比如,linkerOpts=
), 会应用到所有的平台.
规约是, 每个库有自己的定义文件, 通常使用与库相同的名称库. 关于 cinterop
的所有选项, 详情请参见 与 C 代码交互.
向构建过程添加与 C 的交互
要使用头文件, 需要确保在构建过程中生成了它们. 要做到这一点, 请向 build.gradle.kts
文件添加以下内容:
新加的行标注了 // NL
. 首先, 添加 cinterops
, 然后为每个 def
文件添加对应行. 默认情况下, 使用定义文件的名称. 你可以使用额外的参数来修改设定:
关于可用的选项, 请参见 与 C 代码交互.
编写应用程序代码
现在你有了库, 以及对应的 Kotlin 桩代码(stub), 可以在你的应用程序中使用它们了. 本教程将 simple.c 示例代码改写为 Kotlin.
在 src/nativeMain/kotlin/
文件夹中, 将你的 Main.kt
文件更新为以下代码:
你可以看到, 在 Kotlin 版本中删除了明确的变量声明, 但其他一切都和 C 版本一样. 在对应的 Kotlin 代码中, 可以使用 libcurl
库中所有的调用.
编译并运行应用程序
编译应用程序. 方法是, Gradle 运行的 task 中调用
runDebugExecutableNative
, 或者在终端运行以下命令:./gradlew runDebugExecutableNative这里,
cinterop
的生成部分隐含的包含在构建中.如果编译过程中没有错误, 点击
main()
方法旁边侧栏中的绿色的 Run 图标, 或在 IntelliJ IDEA 中使用 Alt+Enter 快捷键启动 launch 菜单.IntelliJ IDEA 会打开 Run 页面, 并显示输出 —
https://example.com
的内容:
你可以看到实际的输出, 因为调用 curl_easy_perform
会将结果打印到标准输出. 你可以使用 curl_easy_setopt
隐藏这些信息.