You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Multiplatform如何将Kotlin/Native作为公共代码绑定C库

该需求完全可以实现,不需要退回手写JNI的方案,核心是基于Kotlin Multiplatform(以下简称KMP)的分层源集+expect/actual机制,结合Kotlin/Native(以下简称K/N)的cinterop能力做跨平台C库绑定,全程不需要手写JNI层代码。

核心认知纠偏

你之前的认知存在一个偏差:K/N不是和JVM、Android平行的、只能独立输出产物的「端」,它是Kotlin的原生编译后端,负责把Kotlin代码编译为对应平台的原生机器码,覆盖桌面Windows/macOS/Linux、Android Native、iOS等所有原生目标。

  • KMP的编译目标确实分为JVM(含跑在ART上的常规Android)、Native、JS等几类,各目标平级,但公共common层不需要直接依赖平台专属API,通过expect/actual的语法机制做接口隔离,完全可以把C库的调用逻辑收敛到各目标的实现层
  • 其中Native目标直接用cinterop做C库绑定,JVM/Android目标由编译器自动生成桥接,不需要手写JNI。
可落地的实现步骤
  • 第一步:搭建标准KMP项目,按需声明编译目标:常规Android选androidTarget,Windows桌面选mingwX64,macOS选macosX64/macosArm64,Linux桌面选linuxX64
  • 第二步:在commonMain公共源集里用expect定义C库对外暴露的统一接口,所有业务层只需要依赖这个接口,不需要感知底层实现差异:
// commonMain/CLibWrapper.kt
expect class CLibWrapper {
    fun callCLibMethod(param: String): Int
    companion object {
        fun init(): CLibWrapper
    }
}
  • 第三步:实现桌面端的C库绑定:
    桌面端全部属于K/N目标,所有桌面目标共享nativeMain源集,直接在这个源集里做C库互操作:
    1. 在src/nativeInterop/cinterop目录下创建def配置文件,声明C库的头文件、库文件路径,示例配置:
    // myclib.def
    headers = my_c_lib.h
    staticLibraries = libmyclib.a
    libraryPaths = ../../native_lib/lib/[target]
    package = com.example.myclib
    
    1. 在构建脚本里注册cinterop任务,示例Gradle配置:
    // build.gradle.kts
    kotlin {
        // 这里省略之前声明的各编译目标
        val nativeMain by getting {
            cinterops {
                val myclib by creating {
                    defFile(project.file("src/nativeInterop/cinterop/myclib.def"))
                    includeDirs.headerFilterOnly(project.file("native_lib/include"))
                }
            }
        }
    }
    
    1. 同步项目后cinterop会自动生成C库对应的Kotlin API,直接在nativeMain的actual实现里调用即可,和调用普通Kotlin函数没有区别:
    // nativeMain/CLibWrapper.kt
    import com.example.myclib.my_c_lib_method
    actual class CLibWrapper {
        actual fun callCLibMethod(param: String): Int {
            return my_c_lib_method(param) // 直接调用cinterop生成的C库函数
        }
        actual companion object {
            actual fun init(): CLibWrapper = CLibWrapper()
        }
    }
    
  • 第四步:实现Android端的C库绑定:
    常规Android项目跑在ART上属于JVM目标,不需要手写JNI,开启KMP的JVM Native桥接功能(Kotlin 1.9+版本支持)后,直接在androidMain源集里写和nativeMain几乎一致的actual实现,编译器会自动生成JNI桥接代码、把C库和桥接层打包进APK,全程不需要写一行C/C++的JNI代码。如果你的Android项目不需要兼容Jetpack等JVM生态库,也可以直接选择androidNativeArm64/androidNativeX64等K/N的Android原生目标,直接复用nativeMain的C库绑定代码,产物为原生so库。
为什么常规Android/桌面JVM项目不能直接引用K/N库

核心原因是产物运行时格式不兼容:

  • K/N编译的输出是平台专属原生机器码:Android上是ELF格式的.so文件,Windows上是PE格式的.dll,macOS上是Mach-O格式的.dylib,这类产物只能被对应平台的原生运行环境直接加载执行
  • 常规Android项目(基于ART虚拟机)、桌面JVM项目(基于HotSpot/OpenJDK虚拟机)的运行时只能识别加载JVM字节码(Android上是dex格式,桌面JVM是class格式),无法直接执行原生机器码,必须通过桥接层做转换
  • 你了解到的「K/N支持编译Android/桌面端产物」是准确的,但这些产物是给原生运行环境准备的,不是给JVM/ART虚拟机准备的,因此不能直接在常规JVM系的Android/桌面项目里引用,必须通过KMP的多目标编译机制,为JVM目标生成对应字节码和桥接层后才能正常使用。
注意事项
  • 不要在commonMain里直接调用cinterop生成的API:cinterop绑定的C库API属于K/N目标的专属API,commonMain作为所有目标共享的通用代码层,不能直接依赖平台专属实现,必须通过expect/actual做隔离
  • 不需要手写JNI:cinterop+自动JVM桥接已经覆盖了99%以上的C库绑定场景,只有需要做极致性能优化、自定义内存管理逻辑时才需要手动编写JNI代码

内容的提问来源于stack exchange,提问作者Aulaulz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 14:51:20