C 互操作基础@ExperimentalForeignApi
Kotlin/Native 提供了强大的 C 互操作能力,允许直接调用 C 库而无需编写额外的粘合代码。这是 Kotlin/Native 最核心的特性之一,使其能够访问几乎所有平台的底层 API。
WARNING
C 互操作 API 目前处于 Beta 状态,需要使用 @OptIn(ExperimentalForeignApi::class) 注解。
cinterop 工作原理
编译时代码生成
cinterop 是一个编译时工具,它的工作流程如下:
C 头文件 (.h)
↓
cinterop 工具
↓
解析 C 声明(使用 Clang)
↓
生成 Kotlin 绑定代码
↓
生成 C 粘合代码
↓
打包成 klib 库
↓
在 Kotlin 中使用// C 头文件: math_lib.h
int add(int a, int b);
// cinterop 自动生成 Kotlin 代码
@ExperimentalForeignApi
fun add(a: Int, b: Int): Int定义文件 (.def)
定义文件告诉 cinterop 如何处理 C 库:
# mylib.def
headers = stdio.h stdlib.h mylib.h
package = com.example.nativelib
compilerOpts = -I/usr/local/include
linkerOpts = -L/usr/local/lib -lmylib基础类型映射
整数类型
C 和 Kotlin 的整数类型直接对应:
| C 类型 | Kotlin 类型 | 大小 |
|---|---|---|
char | Byte | 8 位 |
unsigned char | UByte | 8 位 |
short | Short | 16 位 |
unsigned short | UShort | 16 位 |
int | Int | 32 位 |
unsigned int | UInt | 32 位 |
long / long long | Long | 64 位 |
unsigned long / unsigned long long | ULong | 64 位 |
@OptIn(ExperimentalForeignApi::class)
fun main() {
// C: void process_numbers(int a, unsigned int b)
process_numbers(42, 100u) // Kotlin 调用
}浮点类型
| C 类型 | Kotlin 类型 |
|---|---|
float | Float |
double | Double |
@OptIn(ExperimentalForeignApi::class)
fun calculateVolume(radius: Float, height: Float): Float {
// C: float calculate_volume(float r, float h)
return calculate_volume(radius, height)
}布尔类型
C99 的 _Bool 和 stdbool.h 的 bool 映射为 Kotlin Boolean:
@OptIn(ExperimentalForeignApi::class)
fun main() {
// C: bool is_valid(int value)
val valid: Boolean = is_valid(42)
println(valid)
}指针类型
CPointer<T>
C 指针映射为 Kotlin 的 CPointer<T> 类型:
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*
// C: int* create_array(int size)
fun createIntArray(size: Int): CPointer<IntVar> {
return create_array(size)
}
// C: void set_value(int* ptr, int index, int value)
fun setArrayValue(ptr: CPointer<IntVar>, index: Int, value: Int) {
ptr[index] = value
}空指针
C 的 NULL 对应 Kotlin 的 null:
@OptIn(ExperimentalForeignApi::class)
fun main() {
// C: char* get_name() - 可能返回 NULL
val name: CPointer<ByteVar>? = get_name()
if (name != null) {
println(name.toKString())
} else {
println("Name is NULL")
}
}指针运算
可以对指针进行运算:
@OptIn(ExperimentalForeignApi::class)
fun processArray(ptr: CPointer<IntVar>, size: Int) {
for (i in 0 until size) {
val element = ptr[i] // 等价于 *(ptr + i)
println(element)
}
// 指针偏移
val nextPtr = ptr + 1 // 指向下一个元素
println(nextPtr[0])
}字符串互操作
C 字符串 → Kotlin String
const char* 自动转换为 String:
@OptIn(ExperimentalForeignApi::class)
fun main() {
// C: const char* get_message()
val message: String = get_message() // 自动转换
println(message)
}手动转换:
@OptIn(ExperimentalForeignApi::class)
fun convertCString(cStr: CPointer<ByteVar>?) {
val kotlinStr: String? = cStr?.toKString()
println(kotlinStr ?: "NULL")
}Kotlin String → C 字符串
使用 cstr 扩展属性:
@OptIn(ExperimentalForeignApi::class)
fun main() {
val kotlinStr = "Hello from Kotlin"
// C: void print_message(const char* msg)
kotlinStr.cstr.use { cStr ->
// cStr 是 CPointer<ByteVar>
print_message(cStr)
}
// cStr 在 use 块结束后自动释放
}WARNING
cstr 创建的指针只在 use 块内有效,离开作用域后会被释放。
函数调用
无返回值函数
C 的 void 函数映射为 Kotlin Unit:
@OptIn(ExperimentalForeignApi::class)
fun main() {
// C: void initialize_library()
initialize_library()
// C: void log_message(const char* msg)
log_message("Kotlin/Native rocks!")
}返回值函数
@OptIn(ExperimentalForeignApi::class)
fun main() {
// C: int calculate(int a, int b)
val result: Int = calculate(10, 20)
println("Result: $result")
}可变参数函数
C 的可变参数函数(variadic)部分支持:
@OptIn(ExperimentalForeignApi::class)
fun main() {
// C: int printf(const char* format, ...)
// 需要手动传递固定数量的参数
memScoped {
printf("Number: %d, String: %s\n".cstr.ptr, 42, "Hello".cstr.ptr)
}
}TIP
对于复杂的可变参数函数,建议在 C 侧编写包装函数。
内存管理
memScoped 作用域
用于分配临时 C 内存:
@OptIn(ExperimentalForeignApi::class)
fun main() {
memScoped {
// 在这个作用域内分配的内存会自动释放
val buffer = allocArray<ByteVar>(1024)
// C: int read_data(char* buf, int size)
read_data(buffer, 1024)
val str = buffer.toKString()
println(str)
}
// buffer 在此处已被释放
}nativeHeap 手动管理
需要长期保留的内存使用 nativeHeap:
@OptIn(ExperimentalForeignApi::class)
class NativeBuffer {
private val buffer: CPointer<ByteVar> = nativeHeap.allocArray(1024)
fun use() {
// 使用 buffer
}
fun release() {
nativeHeap.free(buffer) // 手动释放
}
}DANGER
使用 nativeHeap 分配的内存必须手动释放,否则会内存泄漏。
alloc 与 allocArray
@OptIn(ExperimentalForeignApi::class)
memScoped {
// 分配单个 int
val intPtr = alloc<IntVar>()
intPtr.value = 42
println(intPtr.value)
}@OptIn(ExperimentalForeignApi::class)
memScoped {
// 分配 int 数组
val array = allocArray<IntVar>(10)
for (i in 0 until 10) {
array[i] = i * 2
}
}使用 POSIX API
Kotlin/Native 预置了 POSIX 平台库:
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*
import platform.posix.*
fun readFileContent(path: String): String? {
val file = fopen(path, "r") ?: return null
try {
memScoped {
val buffer = allocArray<ByteVar>(4096)
val content = buildString {
while (true) {
val line = fgets(buffer, 4096, file) ?: break
append(line.toKString())
}
}
return content
}
} finally {
fclose(file)
}
}常用 POSIX 函数
@OptIn(ExperimentalForeignApi::class)
import platform.posix.*
fun posixExamples() {
// 文件操作
val fd = open("/tmp/test.txt", O_RDWR or O_CREAT, 0644)
write(fd, "Hello".cstr.ptr, 5u)
close(fd)
// 进程操作
val pid = getpid()
println("Process ID: $pid")
// 环境变量
val home = getenv("HOME")?.toKString()
println("HOME: $home")
// 时间
val now = time(null)
println("Unix timestamp: $now")
}Gradle 配置
配置 cinterop
// build.gradle.kts
kotlin {
linuxX64 {
compilations.getByName("main") {
cinterops {
val mylib by creating {
// 定义文件路径
defFile(project.file("src/nativeInterop/cinterop/mylib.def"))
// 包名
packageName("com.example.mylib")
// 额外的编译器选项
compilerOpts("-I/usr/local/include")
// 额外的头文件搜索路径
includeDirs.allHeaders("/usr/local/include")
}
}
}
}
}定义文件示例
# mylib.def
headers = mylib.h
package = com.example.mylib# mylib.def
headers = mylib.h another.h
headerFilter = mylib.h
package = com.example.mylib
compilerOpts = -I/usr/local/include -DDEBUG
linkerOpts = -L/usr/local/lib -lmylib# sqlite.def
headers = sqlite3.h
headerFilter = sqlite3.h
package = sqlite
linkerOpts.osx = -lsqlite3
linkerOpts.linux = -lsqlite3 -ldl -lpthread完整示例
C 库代码
// mathlib.h
#ifndef MATHLIB_H
#define MATHLIB_H
typedef struct Point {
double x;
double y;
} Point;
// 计算两点距离
double distance(Point* p1, Point* p2);
// 创建点
Point* create_point(double x, double y);
// 释放点
void free_point(Point* p);
#endif// mathlib.c
#include "mathlib.h"
#include <stdlib.h>
#include <math.h>
double distance(Point* p1, Point* p2) {
double dx = p1->x - p2->x;
double dy = p1->y - p2->y;
return sqrt(dx*dx + dy*dy);
}
Point* create_point(double x, double y) {
Point* p = (Point*)malloc(sizeof(Point));
p->x = x;
p->y = y;
return p;
}
void free_point(Point* p) {
free(p);
}定义文件
# mathlib.def
headers = mathlib.h
headerFilter = mathlib.h
package = mathlib
compilerOpts = -I./include
linkerOpts = -L./lib -lmathlibKotlin 使用代码
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*
import mathlib.*
fun main() {
// 创建两个点
val p1 = create_point(0.0, 0.0) ?: error("Failed to create point")
val p2 = create_point(3.0, 4.0) ?: error("Failed to create point")
try {
// 计算距离
val dist = distance(p1, p2)
println("Distance: $dist") // 输出: Distance: 5.0
// 访问结构体字段
println("Point 1: (${p1.pointed.x}, ${p1.pointed.y})")
println("Point 2: (${p2.pointed.x}, ${p2.pointed.y})")
} finally {
// 释放资源
free_point(p1)
free_point(p2)
}
}Gradle 配置
// build.gradle.kts
plugins {
kotlin("multiplatform") version "2.0.21"
}
kotlin {
linuxX64 {
binaries {
executable {
entryPoint = "main"
}
}
compilations.getByName("main") {
cinterops {
val mathlib by creating {
defFile(project.file("src/nativeInterop/cinterop/mathlib.def"))
packageName("mathlib")
includeDirs(project.file("include"))
}
}
}
}
}常见问题
找不到头文件
确保在 def 文件中指定正确的头文件路径:
compilerOpts = -I/usr/local/include -I./mylib/include链接错误
检查库文件路径和库名:
linkerOpts = -L/usr/local/lib -lmylib类型不匹配
检查 C 类型和 Kotlin 类型的对应关系,特别注意:
- 指针类型需要使用
CPointer<T> - 结构体需要通过
.pointed访问字段
内存泄漏
确保:
memScoped块内分配的内存会自动释放nativeHeap分配的内存需要手动free- C 函数分配的内存需要调用对应的释放函数
掌握 C 互操作基础是深入 Kotlin/Native 开发的关键第一步。通过 cinterop 工具,Kotlin 能够安全、高效地调用 C 代码,为访问系统 API 和使用现有 C 库提供了强大支持。