Skip to content

C 互操作基础@ExperimentalForeignApi

源:Interoperability with C

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 中使用
kotlin
// C 头文件: math_lib.h
int add(int a, int b);

// cinterop 自动生成 Kotlin 代码
@ExperimentalForeignApi
fun add(a: Int, b: Int): Int

定义文件 (.def)

定义文件告诉 cinterop 如何处理 C 库:

properties
# 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 类型大小
charByte8 位
unsigned charUByte8 位
shortShort16 位
unsigned shortUShort16 位
intInt32 位
unsigned intUInt32 位
long / long longLong64 位
unsigned long / unsigned long longULong64 位
kotlin
@OptIn(ExperimentalForeignApi::class)
fun main() {
    // C: void process_numbers(int a, unsigned int b)
    process_numbers(42, 100u)  // Kotlin 调用
}

浮点类型

C 类型Kotlin 类型
floatFloat
doubleDouble
kotlin
@OptIn(ExperimentalForeignApi::class)
fun calculateVolume(radius: Float, height: Float): Float {
    // C: float calculate_volume(float r, float h)
    return calculate_volume(radius, height)
}

布尔类型

C99 的 _Boolstdbool.hbool 映射为 Kotlin Boolean

kotlin
@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> 类型:

kotlin
@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

kotlin
@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")
    }
}

指针运算

可以对指针进行运算:

kotlin
@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

kotlin
@OptIn(ExperimentalForeignApi::class)
fun main() {
    // C: const char* get_message()
    val message: String = get_message()  // 自动转换
    println(message)
}

手动转换:

kotlin
@OptIn(ExperimentalForeignApi::class)
fun convertCString(cStr: CPointer<ByteVar>?) {
    val kotlinStr: String? = cStr?.toKString()
    println(kotlinStr ?: "NULL")
}

Kotlin String → C 字符串

使用 cstr 扩展属性:

kotlin
@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

kotlin
@OptIn(ExperimentalForeignApi::class)
fun main() {
    // C: void initialize_library()
    initialize_library()
    
    // C: void log_message(const char* msg)
    log_message("Kotlin/Native rocks!")
}

返回值函数

kotlin
@OptIn(ExperimentalForeignApi::class)
fun main() {
    // C: int calculate(int a, int b)
    val result: Int = calculate(10, 20)
    println("Result: $result")
}

可变参数函数

C 的可变参数函数(variadic)部分支持:

kotlin
@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 内存:

kotlin
@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

kotlin
@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

kotlin
@OptIn(ExperimentalForeignApi::class)
memScoped {
    // 分配单个 int
    val intPtr = alloc<IntVar>()
    intPtr.value = 42
    println(intPtr.value)
}
kotlin
@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 平台库:

kotlin
@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 函数

kotlin
@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

kotlin
// 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")
                }
            }
        }
    }
}

定义文件示例

properties
# mylib.def
headers = mylib.h
package = com.example.mylib
properties
# 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
properties
# sqlite.def
headers = sqlite3.h
headerFilter = sqlite3.h
package = sqlite
linkerOpts.osx = -lsqlite3
linkerOpts.linux = -lsqlite3 -ldl -lpthread

完整示例

C 库代码

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
c
// 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);
}

定义文件

properties
# mathlib.def
headers = mathlib.h
headerFilter = mathlib.h
package = mathlib
compilerOpts = -I./include
linkerOpts = -L./lib -lmathlib

Kotlin 使用代码

kotlin
@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 配置

kotlin
// 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 文件中指定正确的头文件路径:

properties
compilerOpts = -I/usr/local/include -I./mylib/include

链接错误

检查库文件路径和库名:

properties
linkerOpts = -L/usr/local/lib -lmylib

类型不匹配

检查 C 类型和 Kotlin 类型的对应关系,特别注意:

  • 指针类型需要使用 CPointer<T>
  • 结构体需要通过 .pointed 访问字段

内存泄漏

确保:

  • memScoped 块内分配的内存会自动释放
  • nativeHeap 分配的内存需要手动 free
  • C 函数分配的内存需要调用对应的释放函数

掌握 C 互操作基础是深入 Kotlin/Native 开发的关键第一步。通过 cinterop 工具,Kotlin 能够安全、高效地调用 C 代码,为访问系统 API 和使用现有 C 库提供了强大支持。