Skip to content

C/ObjC 内存交互

源:Memory Management and Interop - Kotlin/Native

Kotlin/Native 与 C/Objective-C 的内存交互需要特别注意生命周期管理。本文详解安全的跨边界内存管理模式。

内存管理系统概览

三种内存管理机制

Kotlin/Native 项目中可能同时存在三种内存管理机制:

系统管理方式使用场景
Kotlin GC自动垃圾回收Kotlin 对象
Objective-C ARC自动引用计数ObjC 对象 (iOS/macOS)
手动管理malloc/freeC 代码分配的内存

Kotlin ↔ C 内存边界

Kotlin 分配,C 使用

当 Kotlin 分配内存并传递给 C 代码时,Kotlin 负责内存生命周期

kotlin
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*

// 场景1:临时传递(作用域内)
memScoped {
    val buffer = allocArray<ByteVar>(1024)
    
    // C: void process_buffer(uint8_t* data, size_t size)
    process_buffer(buffer, 1024u)
    
    // memScoped 结束时自动释放
}

// 场景2:持久化分配
class NativeBuffer(val size: Int) : AutoCloseable {
    private val ptr = nativeHeap.allocArray<ByteVar>(size)
    
    fun use(block: (CPointer<ByteVar>) -> Unit) {
        block(ptr)
    }
    
    override fun close() {
        nativeHeap.free(ptr)  // ✅ Kotlin 负责释放
    }
}

// 使用
NativeBuffer(1024).use { buffer ->
    process_buffer(buffer, 1024u)
}
c
// C 代码只使用,不释放
void process_buffer(uint8_t* data, size_t size) {
    // 使用 data
    for (size_t i = 0; i < size; i++) {
        data[i] = (uint8_t)i;
    }
    
    // ❌ 不要 free(data)
}

C 分配,Kotlin 使用

当 C 代码分配内存并传递给 Kotlin 时,需要明确所有权:

kotlin
@OptIn(ExperimentalForeignApi::class)
import platform.posix.*

// C 函数: char* create_string()
fun receiveFromC(): String? {
    val cStr = create_string()  // C 分配的字符串
    
    if (cStr != null) {
        try {
            // 转换为 Kotlin String
            return cStr.toKString()
        } finally {
            // ✅ 用 C 的 free 释放
            free(cStr)
        }
    }
    
    return null
}
c
#include <stdlib.h>
#include <string.h>

// C 分配内存
char* create_string() {
    char* str = (char*)malloc(100);
    strcpy(str, "Hello from C");
    return str;
}

内存所有权原则

谁分配,谁释放

kotlin
@OptIn(ExperimentalForeignApi::class)

// ✅ 正确:Kotlin 分配 → Kotlin 释放
val kPtr = nativeHeap.allocArray<IntVar>(10)
// ... 使用 ...
nativeHeap.free(kPtr)

// ✅ 正确:C 分配 → C 释放
val cPtr = malloc(40u)?.reinterpret<IntVar>()
// ... 使用 ...
free(cPtr)

// ❌ 错误:混用分配器
val ptr = malloc(40u)?.reinterpret<IntVar>()
nativeHeap.free(ptr)  // 崩溃!

Objective-C ARC 互操作

ARC 自动管理

Kotlin/Native 与 ObjC 的 ARC 系统无缝集成:

kotlin
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
import platform.Foundation.*

fun useNSObject() {
    // ObjC 对象由 ARC 管理
    val array = NSMutableArray()
    
    array.addObject("Hello")
    array.addObject("World")
    
    // ✅ 无需手动 release
    // ARC 自动管理引用计数
}

生命周期管理

kotlin
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
class KotlinWrapper {
    // Kotlin 持有 ObjC 对象
    private val nsData: NSData = "Data".NSData
    
    fun getData(): NSData {
        // ✅ 返回时自动 retain
        return nsData
    }
    
    // ✅ Kotlin GC 回收时,ObjC 对象自动 release
}

引用循环问题

混合语言的引用循环无法自动回收

kotlin
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
import platform.Foundation.*

// ❌ 引用循环:Kotlin ↔ ObjC
class KotlinObject {
    var delegate: NSObject? = null  // Kotlin → ObjC
}

// ObjC 侧
// @interface MyDelegate : NSObject
// @property (strong) id kotlinRef;  // ObjC → Kotlin
// @end

// 循环无法回收!
kotlin
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
import platform.Foundation.*
import kotlin.native.ref.*

// ✅ 使用弱引用打破循环
class KotlinObject {
    // 使用 WeakReference
    private var weakDelegate: WeakReference<NSObject>? = null
    
    fun setDelegate(delegate: NSObject) {
        weakDelegate = WeakReference(delegate)
    }
    
    fun getDelegate(): NSObject? {
        return weakDelegate?.get()
    }
}

手动引用管理

某些情况需要手动控制 ObjC 对象生命周期:

kotlin
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
import platform.CoreFoundation.*
import platform.Foundation.*

fun manualRetain() {
    val array = NSMutableArray()
    
    // 增加引用计数
    val cfArray = CFBridgingRetain(array)
    
    // 传递给需要手动管理的 C API
    // ...
    
    // 减少引用计数
    CFBridgingRelease(cfArray)
}

// Core Foundation 对象
fun useCFString() {
    val cfStr = CFStringCreateWithCString(
        null,
        "Hello".cstr.ptr,
        kCFStringEncodingUTF8
    )
    
    try {
        // 使用 CFString
        val length = CFStringGetLength(cfStr)
        println("Length: $length")
    } finally {
        // ✅ 手动释放 Core Foundation 对象
        CFRelease(cfStr)
    }
}

StableRef 跨边界传递

Kotlin 对象传递给 C

StableRef 允许将 Kotlin 对象作为不透明指针传递给 C/ObjC:

kotlin
@OptIn(ExperimentalForeignApi::class)
import kotlin.native.ref.*

class KotlinData(val value: String)

// Kotlin → C
fun passToC(): COpaquePointer {
    val data = KotlinData("Important Data")
    
    // 创建稳定引用
    val ref = StableRef.create(data)
    
    // 转换为 C 指针
    return ref.asCPointer()
}

// C 回调中恢复
val callback = staticCFunction<COpaquePointer?, Unit> { ptr ->
    if (ptr != null) {
        // 恢复 Kotlin 对象
        val data = ptr.asStableRef<KotlinData>().get()
        println(data.value)
    }
}

// 清理
fun cleanup(ptr: COpaquePointer) {
    ptr.asStableRef<KotlinData>().dispose()
}

StableRef 生命周期

kotlin
@OptIn(ExperimentalForeignApi::class)
import kotlin.native.ref.*

class ResourceManager {
    private val refs = mutableListOf<StableRef<*>>()
    
    fun <T : Any> registerKotlinObject(obj: T): COpaquePointer {
        val ref = StableRef.create(obj)
        refs.add(ref)
        return ref.asCPointer()
    }
    
    fun cleanup() {
        refs.forEach { it.dispose() }
        refs.clear()
    }
}

内存调试

检测泄漏

kotlin
@OptIn(ExperimentalStdlibApi::class, ExperimentalForeignApi::class)
import kotlin.native.runtime.GC
import kotlin.native.runtime.NativeRuntimeApi

// 监控 Kotlin 内存
fun monitorMemory() {
    GC.collect()
    
    val info = GC.lastGCInfo
    if (info != null) {
        println("Allocated: ${info.allocatedBytes} bytes")
        println("Freed: ${info.freedBytes} bytes")
    }
}

// Xcode Instruments (iOS/macOS)
// VM Tracker 可以追踪 Kotlin 分配的内存

常见问题排查

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
val ptr = nativeHeap.alloc<IntVar>().ptr
nativeHeap.free(ptr)
nativeHeap.free(ptr)  // 崩溃!
kotlin
@OptIn(ExperimentalForeignApi::class)
// ✅ 正确
var ptr: CPointer<IntVar>? = nativeHeap.alloc<IntVar>().ptr
ptr?.let { nativeHeap.free(it) }
ptr = null

最佳实践

实践:RAII 模式

kotlin
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*

class NativeResource<T : CVariable>(
    private val allocate: () -> CPointer<T>,
    private val deallocate: (CPointer<T>) -> Unit
) : AutoCloseable {
    private var ptr: CPointer<T>? = allocate()
    
    fun use(): CPointer<T> {
        return ptr ?: throw IllegalStateException("Resource已释放")
    }
    
    override fun close() {
        ptr?.let { deallocate(it) }
        ptr = null
    }
}

// 使用
NativeResource(
    allocate = { nativeHeap.allocArray<ByteVar>(1024) },
    deallocate = { nativeHeap.free(it) }
).use { resource ->
    val buffer = resource.use()
    // 使用 buffer
}  // 自动释放

实践:明确边界

kotlin
@OptIn(ExperimentalForeignApi::class)
// 封装 C 资源
class CFile(path: String) : AutoCloseable {
    private var file: CPointer<FILE>? = fopen(path, "r")
    
    fun read(buffer: CPointer<ByteVar>, size: Int): Int {
        val f = file ?: throw IllegalStateException("File closed")
        return fread(buffer, 1u, size.toULong(), f).toInt()
    }
    
    override fun close() {
        file?.let { fclose(it) }
        file = null
    }
}

跨边界内存管理的核心是明确所有权。遵循"谁分配谁释放"原则,使用 RAII 模式能避免绝大多数内存问题。