使用 cinterop 和 .def 文件进行 C 互操作
使用 cinterop 工具和定义文件从 Kotlin/Native 调用 C 库。
使用 cinterop 和 .def 文件进行 C 互操作 是 CoddyKit 上的免费 Kotlin Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Kotlin Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Kotlin Academy 课程共包含 4 节课。
为什么需要 C 互操作
Kotlin/Native 可以直接调用 C 库——无需 JNI 包装器,也无需桥接层。这样,使用 Kotlin 编译为原生二进制文件的代码,就能调用平台系统库、硬件驱动程序或任何兼容 C 的库。
.def 文件
.def 文件描述要导入的 C 库。它指定头文件、库名称以及可选的编译器/链接器标志。将它放在 src/nativeInterop/cinterop/ 中:
# src/nativeInterop/cinterop/libcurl.def
headers = curl/curl.h
headerFilter = curl/**
linkerOpts.linux = -lcurl
linkerOpts.macos = -lcurl在 Gradle 中注册互操作
在目标的 compilations 块中引用 .def 文件:
kotlin {
linuxX64 {
compilations["main"].cinterops {
val libcurl by creating {
defFile(project.file("src/nativeInterop/cinterop/libcurl.def"))
}
}
}
}运行 cinterop
运行 ./gradlew cinteropLibcurlLinuxX64,根据 C 头文件生成 .klib。生成的 Kotlin 绑定会出现在 build/cinterop/ 中,并自动添加到编译类路径中。
在 Kotlin 中使用 C 函数
生成完成后,导入 .def 文件中声明的包,并像调用 Kotlin 函数一样调用 C 函数。指针类型表示为 CPointer:
import libcurl.*
import kotlinx.cinterop.*
fun fetchUrl(url: String): String = memScoped {
val handle = curl_easy_init() ?: error("curl init failed")
curl_easy_setopt(handle, CURLOPT_URL, url.cstr.ptr)
curl_easy_perform(handle)
curl_easy_cleanup(handle)
"done"
}memScoped:栈分配的内存
memScoped { } 会创建一个内存作用域,在代码块退出时自动释放本地内存分配(alloc()、allocArray())。请始终将它用于临时 C 内存,以避免内存泄漏。
memScoped {
val value = alloc<IntVar>()
value.value = 42
println(value.value) // 42
// freed automatically at end of memScoped
}为 C 固定 Kotlin 对象
要将 Kotlin 对象的地址传递给 C 函数(例如回调函数),请使用 StableRef 或 Pinned 固定该对象,以防垃圾回收器移动它:
val stableRef = StableRef.create(myKotlinObject)
try {
c_function_expecting_user_data(stableRef.asCPointer())
} finally {
stableRef.dispose()
}C 字符串与 Kotlin 字符串
使用以下方式在 Kotlin String 和 C const char* 之间进行转换:
"hello".cstr→ CValues,仅在memScoped内有效cPointer.toKString()→ 从CPointer<ByteVar>转换得到 Kotlin String
memScoped {
val cStr = "Hello, C!".cstr.ptr // CPointer<ByteVar>
val back = cStr.toKString() // "Hello, C!"
}访问结构体
C 结构体会转换为带属性的 Kotlin 类。使用点号运算符访问结构体字段。在 memScoped { } 内使用 alloc 分配结构体:
memScoped {
val addr = alloc<sockaddr_in>()
addr.sin_family = AF_INET.convert()
addr.sin_port = htons(8080u).convert()
}嵌入 inline C 代码
对于简短的 C 代码片段,可以在 .def 文件中使用 cCode 块编写 inline C 代码,该代码会与绑定一起编译:
# In .def file
headers = stdio.h
---
// Inline C code after ---
static inline int add(int a, int b) { return a + b; }调试 C 互操作问题
常见问题包括:缺少头文件(检查 headerFilter)、缺少链接器标志(添加 linkerOpts)、类型不匹配(检查生成的 .klib API)。在 def 文件或 Gradle 任务配置中使用 -verbose 启用详细的 cinterop 日志。
快速检查
memScoped { } 在 Kotlin/Native C 互操作中的作用是什么?
回顾:使用 cinterop 和 .def 文件进行 C 互操作
要点:
.def文件指定头文件、链接器选项和可选的 inline C 代码- 通过
cinterops { val name by creating { defFile(...) } }在 Gradle 中注册 - 运行
cinterop<Name><Target>Gradle 任务以生成绑定 - 使用
memScoped { }进行临时原生内存分配 - 使用
StableRef固定长期存在的对象;使用.cstr/.toKString()转换字符串
常见问题解答
「使用 cinterop 和 .def 文件进行 C 互操作」课时是免费的吗?
是的 — 「使用 cinterop 和 .def 文件进行 C 互操作」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Kotlin Academy 课程的其余内容,请升级到 CoddyKit PRO。 Kotlin Academy 课程共包含 4 节课。
「使用 cinterop 和 .def 文件进行 C 互操作」这节课中我会学到什么?
使用 cinterop 工具和定义文件从 Kotlin/Native 调用 C 库。 你通过在浏览器中直接运行的动手代码来练习 Kotlin Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Kotlin Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Kotlin Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「使用 cinterop 和 .def 文件进行 C 互操作」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Kotlin Academy 课中编写并运行代码吗?
能。每节 Kotlin Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- Kotlin/Native 概览:目标、工具链与编译
- Kotlin/Native 中的内存管理:新版 MM
- 使用 cinterop 和 .def 文件进行 C 互操作
- Kotlin/WASM:为浏览器编译为 WebAssembly