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库互操作:- 在
src/nativeInterop/cinterop目录下创建def配置文件,声明C库的头文件、库文件路径,示例配置:
// myclib.def headers = my_c_lib.h staticLibraries = libmyclib.a libraryPaths = ../../native_lib/lib/[target] package = com.example.myclib- 在构建脚本里注册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")) } } } }- 同步项目后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
相关产品推荐
相关产品推荐

