Kotlin多平台(KMP)下C++代码编译为klib及跨平台通用构建配置方案咨询
我完全懂你这种被分平台配置折腾得头大的感受——重复写Android NDK、iOS CocoaPods、桌面平台编译脚本确实太繁琐了!幸好针对纯标准C++代码(不用android.h、Windows.h这类平台特定头文件),Kotlin多平台(KMP)确实有一套通用的Gradle配置方案,能帮你一次性搞定所有平台的编译、打包和集成,不用再分平台单独折腾。
核心思路:用Kotlin/Native + Gradle统一管理C++编译与跨平台产物
Kotlin/Native本身支持与C/C代码互操作,结合Gradle的KMP插件,我们可以把C代码的编译、链接、klib生成逻辑统一写到一个Gradle脚本里,让Gradle自动为每个目标平台生成对应产物(Android的.so、iOS的原生二进制、桌面的.dll/.so/.dylib、Web的WASM),还能打包成klib供其他KMP模块复用。
方案一:直接在KMP模块中集成C++代码(适合轻量算法代码)
这种方案不需要额外的CMake,直接用Gradle任务编译C++代码,配合Kotlin的CInterop暴露API给Kotlin调用。
1. 项目结构搭建
先创建一个KMP项目,新增一个专门处理C++代码的模块(比如叫cpp-algorithms),目录结构如下:
cpp-algorithms/ ├── build.gradle.kts └── src/ └── main/ └── cpp/ ├── include/ // 暴露给Kotlin的头文件 │ └── algorithms.h └── src/ // C++实现代码 └── algorithms.cpp
2. 编写C++代码
- 头文件
algorithms.h(注意用extern "C"避免C++ name mangling,让Kotlin能正确识别):
#ifndef ALGORITHMS_H #define ALGORITHMS_H extern "C" { // 暴露给Kotlin的函数 int add(int a, int b); long long calculateFibonacci(int n); } #endif
- 实现文件
algorithms.cpp:
#include "algorithms.h" int add(int a, int b) { return a + b; } long long calculateFibonacci(int n) { if (n <= 1) return n; return calculateFibonacci(n-1) + calculateFibonacci(n-2); }
3. 配置Gradle脚本(build.gradle.kts)
这个脚本会自动处理所有目标平台的C++编译、链接,以及klib生成:
plugins { kotlin("multiplatform") version "1.9.20" } kotlin { // 注册所有需要支持的目标平台 // Android Native目标 androidNativeArm32("androidArm32") androidNativeArm64("androidArm64") androidNativeX86("androidX86") androidNativeX64("androidX64") // iOS目标 iosX64() iosArm64() iosSimulatorArm64() // 桌面目标 linuxX64() linuxArm64() macosX64() macosArm64() windowsX64() windowsArm64() // Web WASM目标 wasmJs() // 配置CInterop,暴露C++ API给Kotlin targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> { compilations.main.apply { // 创建CInterop配置 val cppInterop = cinterops.create("cppAlgorithms") { includeDirs("src/main/cpp/include") cppFlags("-std=c++17", "-O2") // C++编译选项 header("algorithms.h") // 指定要暴露的头文件 } // 注册C++静态库编译任务 val compileCppLib = tasks.register<Exec>("compileCppLib${targetName}") { val srcDir = file("src/main/cpp/src") val outputDir = file("build/cpp-libs/${targetName}") outputDir.mkdirs() // 根据平台选择编译器和输出格式 val (compiler, outputLib) = when (targetName) { in listOf("windowsX64", "windowsArm64") -> Pair("cl.exe", "${outputDir}/algorithms.lib") else -> Pair("clang++", "${outputDir}/libalgorithms.a") } // 编译命令:把所有cpp文件编译成静态库 val srcFiles = srcDir.listFiles { it.extension == "cpp" }?.joinToString(" ") ?: "" commandLine( compiler, "-c", srcFiles, "-std=c++17", "-O2", "-I${srcDir.parentFile}/include", "-o", outputLib ) } // 让Kotlin编译依赖C++静态库编译任务 dependencies { implementation(files(compileCppLib.get().outputs.files)) implementation(cppInterop) } } } // 生成通用klib供其他KMP模块引用 tasks.register<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>("generateCommonKlib") { target = kotlin.targets.filterIsInstance<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>().first() sourceSets = listOf(sourceSets["commonMain"]) outputDir = file("build/libs/klib") } }
4. 在其他KMP模块中调用C++代码
比如在你的主App模块的commonMain中,直接导入CInterop生成的API:
import cppAlgorithms.add import cppAlgorithms.calculateFibonacci fun main() { println("2 + 3 = ${add(2, 3)}") println("Fibonacci(10) = ${calculateFibonacci(10)}") }
方案二:用CMake管理复杂C代码(适合大型C项目)
如果你的C++代码依赖第三方库、项目结构复杂,用CMake管理更方便,我们可以在Gradle中集成CMake编译任务:
1. 添加CMakeLists.txt
在cpp-algorithms/src/main/cpp/下创建CMakeLists.txt:
cmake_minimum_required(VERSION 3.10) project(cpp-algorithms) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 收集所有C++源文件 file(GLOB SOURCES "src/*.cpp") # 生成静态库 add_library(cpp-algorithms STATIC ${SOURCES}) # 指定头文件目录 target_include_directories(cpp-algorithms PUBLIC include)
2. 修改Gradle脚本集成CMake
在build.gradle.kts的targets.withType块中替换成CMake编译逻辑:
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> { compilations.main.apply { val cmakeBuild = tasks.register<Exec>("cmakeBuild${targetName}") { val buildDir = file("build/cmake/${targetName}") buildDir.mkdirs() workingDir = buildDir // 根据平台配置CMake工具链(这里以Android和iOS为例,其他平台可类似扩展) val toolchainArgs = when (targetName) { "androidArm32" -> listOf( "-DCMAKE_TOOLCHAIN_FILE=${android.ndkDirectory}/build/cmake/android.toolchain.cmake", "-DANDROID_ABI=armeabi-v7a", "-DANDROID_PLATFORM=android-24" ) "iosArm64" -> listOf( "-DCMAKE_TOOLCHAIN_FILE=../../../../cmake/ios.toolchain.cmake", // 需要iOS CMake工具链 "-DIOS_PLATFORM=OS64", "-DARCHS=arm64" ) else -> emptyList() } // 执行CMake配置和编译 commandLine( "cmake", "../../../src/main/cpp", "-DCMAKE_BUILD_TYPE=Release", *toolchainArgs.toTypedArray() ) commandLine("make") } dependencies { implementation(files("build/cmake/${targetName}/libcpp-algorithms.a")) cinterops.create("cppAlgorithms") { includeDirs("src/main/cpp/include") header("algorithms.h") } } dependsOn(cmakeBuild) } }
关键优势与注意点
- 通用配置,一次编写全平台生效:不用再为每个平台单独写编译脚本,Gradle会自动处理各平台的编译器、工具链和产物格式。
- 自动集成到KMP打包流程:Android平台的
.so会自动嵌入到AAR/JAR,iOS平台会直接集成到IPA的二进制中,桌面平台的库会放在App目录,WASM会打包到Web产物里。 - klib复用:生成的klib可以被其他KMP模块直接引用,避免重复编译C++代码。
- 纯标准C++是前提:如果必须用到平台特定头文件,还是需要分平台添加条件编译,但你已经放宽条件到只用标准算法代码,这个方案完全覆盖你的需求。
内容来源于stack exchange




