Skip to content

第三方 C/C++ 库集成

源:Using C Libraries

Kotlin/Native 可集成任意 C/C++ 库。本文深入讲解常见第三方库的集成方法和最佳实践。

集成流程

完整流程图

1. 准备 C/C++ 库
   ├─ 获取头文件 (.h)
   ├─ 获取库文件 (.so/.a/.dll)
   └─ 了解 API 文档

2. 创建定义文件 (.def)
   ├─ 指定头文件
   ├─ 设置编译选项
   └─ 设置链接选项

3. 配置 Gradle 构建
   ├─ 添加 cinterop 配置
   └─ 设置平台特定参数

4. 生成 Kotlin 绑定
   └─ 运行编译,生成 klib

5. 在 Kotlin 中使用
   └─ 导入并调用 API

案例1:OpenSSL集成

定义文件

properties
# openssl.def
headers = openssl/ssl.h openssl/crypto.h openssl/sha.h
headerFilter = openssl/*.h
package = openssl

# macOS 配置
compilerOpts.osx = -I/usr/local/opt/openssl/include
linkerOpts.osx = -L/usr/local/opt/openssl/lib -lssl -lcrypto

# Linux 配置
compilerOpts.linux = -I/usr/include/openssl
linkerOpts.linux = -lssl -lcrypto -ldl -lpthread

# Windows 配置
compilerOpts.mingw = -IC:/OpenSSL/include
linkerOpts.mingw = -LC:/OpenSSL/lib -lssl -lcrypto -lws2_32

Gradle 配置

kotlin
// build.gradle.kts
kotlin {
    linuxX64 {
        compilations.getByName("main") {
            cinterops {
                val openssl by creating {
                    defFile(project.file("src/nativeInterop/cinterop/openssl.def"))
                    packageName("openssl")
                    
                    // 额外的头文件路径
                    includeDirs.apply {
                        allHeaders("/usr/include", "/usr/local/include")
                    }
                }
            }
        }
    }
}

Kotlin 使用示例

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

object CryptoUtils {
    /**
     * SHA256 哈希
     */
    fun sha256(data: ByteArray): ByteArray {
        memScoped {
            val hash = allocArray<UByteVar>(SHA256_DIGEST_LENGTH)
            
            data.usePinned { pinned ->
                SHA256(
                    pinned.addressOf(0).reinterpret(),
                    data.size.toULong(),
                    hash
                )
            }
            
            return ByteArray(SHA256_DIGEST_LENGTH) { i ->
                hash[i].toByte()
            }
        }
    }
    
    /**
     * AES 加密
     */
    fun aesEncrypt(data: ByteArray, key: ByteArray, iv: ByteArray): ByteArray {
        memScoped {
            val ctx = alloc<EVP_CIPHER_CTX>()
            EVP_CIPHER_CTX_init(ctx.ptr)
            
            val output = ByteArray(data.size + 16)  // 考虑padding
            val outLen = alloc<IntVar>()
            
            try {
                // 初始化加密
                EVP_EncryptInit_ex(
                    ctx.ptr,
                    EVP_aes_256_cbc(),
                    null,
                    key.toCValues(),
                    iv.toCValues()
                )
                
                // 加密数据
                output.usePinned { pinnedOut ->
                    data.usePinned { pinnedIn ->
                        EVP_EncryptUpdate(
                            ctx.ptr,
                            pinnedOut.addressOf(0).reinterpret(),
                            outLen.ptr,
                            pinnedIn.addressOf(0).reinterpret(),
                            data.size
                        )
                    }
                }
                
                val written = outLen.value
                
                // Finalize
                EVP_EncryptFinal_ex(
                    ctx.ptr,
                    output.toCValues().ptr.plus(written)!!.reinterpret(),
                    outLen.ptr
                )
                
                return output.copyOf(written + outLen.value)
            } finally {
                EVP_CIPHER_CTX_cleanup(ctx.ptr)
            }
        }
    }
}

// 使用
fun main() {
    val data = "Hello, World!".encodeToByteArray()
    val hash = CryptoUtils.sha256(data)
    println("SHA256: ${hash.joinToString("") { "%02x".format(it) }}")
}

案例2:SQLite 集成

定义文件

properties
# sqlite.def
headers = sqlite3.h
headerFilter = sqlite3.h
package = sqlite

linkerOpts.osx = -lsqlite3
linkerOpts.linux = -lsqlite3 -ldl -lpthread
linkerOpts.mingw = -lsqlite3

数据库操作封装

kotlin
@OptIn(ExperimentalForeignApi::class)
import sqlite.*

class SQLiteDatabase(private val path: String) : AutoCloseable {
    private var db: CPointer<sqlite3>? = null
    
    init {
        memScoped {
            val dbPtr = allocPointerTo<sqlite3>()
            val result = sqlite3_open(path.cstr.ptr, dbPtr.ptr)
            
            if (result != SQLITE_OK) {
                throw Exception("Failed to open database: $result")
            }
            
            db = dbPtr.value
        }
    }
    
    fun execute(sql: String) {
        val result = sqlite3_exec(
            db,
            sql.cstr.ptr,
            null,
            null,
            null
        )
        
        if (result != SQLITE_OK) {
            val error = sqlite3_errmsg(db)?.toKString()
            throw Exception("SQL error: $error")
        }
    }
    
    fun query(sql: String): List<Map<String, String>> {
        val results = mutableListOf<Map<String, String>>()
        
        memScoped {
            val stmtPtr = allocPointerTo<sqlite3_stmt>()
            
            sqlite3_prepare_v2(
                db,
                sql.cstr.ptr,
                -1,
                stmtPtr.ptr,
                null
            )
            
            val stmt = stmtPtr.value
            
            try {
                val columnCount = sqlite3_column_count(stmt)
                
                while (sqlite3_step(stmt) == SQLITE_ROW) {
                    val row = mutableMapOf<String, String>()
                    
                    for (i in 0 until columnCount) {
                        val name = sqlite3_column_name(stmt, i)?.toKString() ?: "col$i"
                        val value = sqlite3_column_text(stmt, i)?.toKString() ?: ""
                        row[name] = value
                    }
                    
                    results.add(row)
                }
            } finally {
                sqlite3_finalize(stmt)
            }
        }
        
        return results
    }
    
    override fun close() {
        db?.let { sqlite3_close(it) }
        db = null
    }
}

// 使用
SQLiteDatabase("test.db").use { db ->
    db.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
    db.execute("INSERT INTO users (name) VALUES ('Alice')")
    
    val users = db.query("SELECT * FROM users")
    users.forEach { user ->
        println("User: ${user["name"]}")
    }
}

案例3:libcurl 网络库

定义文件

properties
# curl.def
headers = curl/curl.h
headerFilter = curl/*.h
package = curl

compilerOpts.osx = -I/usr/local/opt/curl/include
linkerOpts.osx = -L/usr/local/opt/curl/lib -lcurl

compilerOpts.linux = -I/usr/include/curl
linkerOpts.linux = -lcurl

compilerOpts.mingw = -IC:/curl/include
linkerOpts.mingw = -LC:/curl/lib -lcurl -lws2_32

HTTP 请求封装

kotlin
@OptIn(ExperimentalForeignApi::class)
import curl.*

object HttpClient {
    init {
        curl_global_init(CURL_GLOBAL_ALL)
    }
    
    private val writeCallback = staticCFunction<
        CPointer<ByteVar>?, 
        size_t, 
        size_t, 
        COpaquePointer?, 
        size_t
    > { ptr, size, nmemb, userdata ->
        if (ptr != null && userdata != null) {
            val totalSize = (size * nmemb).toInt()
            val buffer = userdata.asStableRef<StringBuilder>().get()
            
            val data = ByteArray(totalSize) { i ->
                ptr[i]
            }
            
            buffer.append(data.decodeToString())
        }
        
        size * nmemb
    }
    
    fun get(url: String): String {
        val curl = curl_easy_init() ?: throw Exception("Failed to init curl")
        val response = StringBuilder()
        val stableRef = StableRef.create(response)
        
        try {
            curl_easy_setopt(curl, CURLOPT_URL, url.cstr.ptr)
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback)
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, stableRef.asCPointer())
            
            val res = curl_easy_perform(curl)
            
            if (res != CURLE_OK) {
                val error = curl_easy_strerror(res)?.toKString()
                throw Exception("Curl error: $error")
            }
            
            return response.toString()
        } finally {
            curl_easy_cleanup(curl)
            stableRef.dispose()
        }
    }
}

// 使用
val html = HttpClient.get("https://example.com")
println(html)

常见问题与解决方案

问题1:找不到头文件

properties
# ❌ 错误配置
headers = mylib.h

# ✅ 正确配置
headers = mylib.h
compilerOpts = -I/usr/local/include -I/opt/mylib/include

问题2:链接失败

bash
# 检查库文件是否存在
ls /usr/local/lib/libmylib.so

#检查符号是否存在
nm -D /usr/local/lib/libmylib.so | grep my_function
properties
# 修复链接配置
linkerOpts = -L/usr/local/lib -lmylib -lpthread

问题3:符号冲突

properties
# 使用 headerFilter 只导出需要的符号
headers = lib1.h lib2.h system.h
headerFilter = lib1.h  # 只导出 lib1 的符号

最佳实践

实践1:版本管理

properties
# 在 def 文件中记录版本
# Library: OpenSSL
# Version: 3.0.0
# Updated: 2024-01-01
headers = openssl/ssl.h

实践2:错误处理

kotlin
@OptIn(ExperimentalForeignApi::class)
inline fun <T> withCLibrary(
    errorCheck: () -> Boolean,
    errorMessage: () -> String,
    block: () -> T
): T {
    val result = block()
    
    if (errorCheck()) {
        throw Exception(errorMessage())
    }
    
    return result
}

实践3:内存安全

kotlin
@OptIn(ExperimentalForeignApi::class)
class SafeCPointer<T : CVariable>(private val ptr: CPointer<T>) : AutoCloseable {
    fun use(): CPointer<T> = ptr
    
    override fun close() {
        nativeHeap.free(ptr)
    }
}

通过 cinterop,几乎所有 C/C++ 库都可无缝集成到 Kotlin/Native 项目中。关键是正确配置定义文件和处理好内存管理。