Kotlin 多平台¶
使用 CCGO 构建包含原生 C++ 代码的 Kotlin 多平台(KMP)库的完整指南。
概览¶
CCGO 实现了 C++ 库与 Kotlin 多平台项目的无缝集成,使您能够:
- 跨平台共享 C++ 代码 - 在所有 KMP 平台(Android、iOS、macOS、Linux、Windows)间共享
- 统一构建系统 - 使用单个命令为所有平台构建原生库
- 类型安全绑定 - 为 C++ API 生成 Kotlin expect/actual 声明
- 原生性能 - 直接 JNI/Objective-C 互操作,无额外开销
- Gradle 集成 - 在 KMP Gradle 构建中获得一流支持
支持的 KMP 目标平台: - Android(ARM64、ARMv7、x86_64、x86) - iOS(arm64、模拟器) - macOS(x86_64、arm64) - Linux(x86_64) - Windows(x86_64)
前置要求¶
必需工具¶
平台 SDK¶
| 平台 | 要求 |
|---|---|
| Android | Android SDK、NDK 21+ |
| iOS | 带 Xcode 的 macOS |
| macOS | 带 Xcode 的 macOS |
| Linux | GCC 或 Clang |
| Windows | Visual Studio 或 MinGW |
快速开始¶
创建新的 KMP 项目¶
项目结构¶
my-kmp-lib/
├── CCGO.toml # CCGO 配置
├── build.gradle.kts # 根 Gradle 构建
├── settings.gradle.kts
├── src/
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/example/
│ │ └── MyLib.kt # Kotlin expect 声明
│ ├── androidMain/
│ │ └── kotlin/
│ │ └── com/example/
│ │ └── MyLib.android.kt # Android actual(JNI)
│ ├── iosMain/
│ │ └── kotlin/
│ │ └── com/example/
│ │ └── MyLib.ios.kt # iOS actual(Objective-C)
│ └── nativeMain/ # 桌面平台
│ └── kotlin/
│ └── com/example/
│ └── MyLib.native.kt # cinterop 绑定
├── cpp/
│ ├── include/
│ │ └── mylib/
│ │ └── mylib.h # C++ 公共头文件
│ ├── src/
│ │ └── mylib.cpp # C++ 实现
│ ├── jni/ # Android 的 JNI 包装器
│ │ └── mylib_jni.cpp
│ └── objc/ # iOS 的 Objective-C 包装器
│ ├── MyLibWrapper.h
│ └── MyLibWrapper.mm
└── target/ # 构建输出
├── android/
├── ios/
├── macos/
├── linux/
└── windows/
配置¶
CCGO.toml¶
[package]
name = "mylib"
version = "1.0.0"
type = "kmp"
[kmp]
# Kotlin 包名
package_name = "com.example.mylib"
# KMP 目标平台
targets = ["android", "ios", "macos", "linux", "windows"]
# Android 配置
[kmp.android]
min_sdk = 21
target_sdk = 33
namespace = "com.example.mylib"
# iOS 配置
[kmp.ios]
min_deployment_target = "12.0"
framework_name = "MyLib"
# macOS 配置
[kmp.macos]
min_deployment_target = "10.14"
[dependencies]
# C++ 依赖
cpp = [
{ name = "openssl", version = "1.1.1" }
]
build.gradle.kts¶
plugins {
kotlin("multiplatform") version "1.9.20"
id("com.android.library") version "8.1.0"
}
kotlin {
// Android 目标
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
// iOS 目标
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "MyLib"
}
}
// macOS
macosX64()
macosArm64()
// Linux
linuxX64()
// Windows(通过 MinGW)
mingwX64()
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
}
}
val androidMain by getting {
dependencies {
// JNI 绑定自动包含
}
}
val iosMain by getting {
dependencies {
// Framework 绑定自动包含
}
}
val nativeMain by getting {
dependencies {
// 桌面平台的 cinterop 绑定
}
}
}
}
android {
namespace = "com.example.mylib"
compileSdk = 33
defaultConfig {
minSdk = 21
}
// 链接 CCGO 构建的原生库
sourceSets {
getByName("main") {
jniLibs.srcDirs("${projectDir}/target/android")
}
}
}
构建 KMP 库¶
构建所有平台¶
# 为所有 KMP 目标构建原生代码
ccgo build kmp
# 输出结构:
# target/
# ├── android/
# │ ├── armeabi-v7a/libmylib.so
# │ ├── arm64-v8a/libmylib.so
# │ └── x86_64/libmylib.so
# ├── ios/
# │ └── MyLib.framework
# ├── macos/
# │ └── libmylib.dylib
# ├── linux/
# │ └── libmylib.so
# └── windows/
# └── mylib.dll
构建特定平台¶
# 仅 Android
ccgo build kmp --target android
# 仅 iOS
ccgo build kmp --target ios
# 桌面平台
ccgo build kmp --target macos
ccgo build kmp --target linux
ccgo build kmp --target windows
Gradle 集成¶
# 构建 Kotlin 代码和原生库
./gradlew build
# 发布到 Maven Local
./gradlew publishToMavenLocal
# 创建 iOS XCFramework
./gradlew linkReleaseFrameworkIos
原生互操作¶
Android(JNI)¶
C++ 头文件:
// include/mylib/mylib.h
#pragma once
#include <string>
namespace mylib {
class Calculator {
public:
static int add(int a, int b);
static std::string greet(const std::string& name);
};
}
JNI 包装器:
// cpp/jni/mylib_jni.cpp
#include <jni.h>
#include "mylib/mylib.h"
extern "C" {
JNIEXPORT jint JNICALL
Java_com_example_mylib_Calculator_add(JNIEnv* env, jclass clazz,
jint a, jint b) {
return mylib::Calculator::add(a, b);
}
JNIEXPORT jstring JNICALL
Java_com_example_mylib_Calculator_greet(JNIEnv* env, jclass clazz,
jstring name) {
const char* cName = env->GetStringUTFChars(name, nullptr);
std::string result = mylib::Calculator::greet(cName);
env->ReleaseStringUTFChars(name, cName);
return env->NewStringUTF(result.c_str());
}
} // extern "C"
Kotlin Expect/Actual:
// commonMain/kotlin/Calculator.kt
expect object Calculator {
fun add(a: Int, b: Int): Int
fun greet(name: String): String
}
// androidMain/kotlin/Calculator.android.kt
actual object Calculator {
init {
System.loadLibrary("mylib")
}
actual external fun add(a: Int, b: Int): Int
actual external fun greet(name: String): String
}
iOS(Objective-C++)¶
Objective-C++ 包装器:
// cpp/objc/MyLibWrapper.h
#import <Foundation/Foundation.h>
@interface MyLibCalculator : NSObject
+ (NSInteger)add:(NSInteger)a b:(NSInteger)b;
+ (NSString*)greet:(NSString*)name;
@end
// cpp/objc/MyLibWrapper.mm
#import "MyLibWrapper.h"
#include "mylib/mylib.h"
@implementation MyLibCalculator
+ (NSInteger)add:(NSInteger)a b:(NSInteger)b {
return mylib::Calculator::add((int)a, (int)b);
}
+ (NSString*)greet:(NSString*)name {
std::string result = mylib::Calculator::greet([name UTF8String]);
return [NSString stringWithUTF8String:result.c_str()];
}
@end
Kotlin Actual:
// iosMain/kotlin/Calculator.ios.kt
import platform.Foundation.*
import kotlinx.cinterop.*
actual object Calculator {
actual fun add(a: Int, b: Int): Int {
return MyLibCalculator.add(a.toLong(), b.toLong()).toInt()
}
actual fun greet(name: String): String {
return MyLibCalculator.greet(name) ?: ""
}
}
桌面(cinterop)¶
def 文件:
# nativeInterop/cinterop/mylib.def
headers = mylib.h
headerFilter = mylib/*
package = com.example.mylib.native
compilerOpts.linux = -I/usr/include
compilerOpts.macos = -I/usr/local/include
linkerOpts.linux = -L/usr/lib -lmylib
linkerOpts.macos = -L/usr/local/lib -lmylib
Kotlin Actual:
// nativeMain/kotlin/Calculator.native.kt
import com.example.mylib.native.*
import kotlinx.cinterop.*
actual object Calculator {
actual fun add(a: Int, b: Int): Int {
return mylib_add(a, b)
}
actual fun greet(name: String): String {
return mylib_greet(name)?.toKString() ?: ""
}
}
测试¶
单元测试¶
// commonTest/kotlin/CalculatorTest.kt
import kotlin.test.Test
import kotlin.test.assertEquals
class CalculatorTest {
@Test
fun testAdd() {
assertEquals(5, Calculator.add(2, 3))
}
@Test
fun testGreet() {
assertEquals("Hello, World!", Calculator.greet("World"))
}
}
运行测试¶
# 所有平台
./gradlew allTests
# Android
./gradlew testDebugUnitTest
# iOS 模拟器
./gradlew iosSimulatorArm64Test
# 桌面
./gradlew macosX64Test
./gradlew linuxX64Test
./gradlew mingwX64Test
发布¶
Maven Central¶
# 发布所有平台
ccgo publish kmp --registry official
# 或使用 Gradle
./gradlew publishAllPublicationsToMavenCentral
使用库¶
常见问题¶
JNI 方法未找到¶
问题:
解决方案:
# 验证库已构建
ls target/android/arm64-v8a/libmylib.so
# 检查 JNI 方法签名
javap -s com.example.mylib.Calculator
# 验证方法命名匹配
# Java: Java_com_example_mylib_Calculator_add
iOS Framework 未找到¶
问题:
解决方案:
# 重新构建 iOS framework
ccgo build kmp --target ios
# 验证 framework 结构
ls -la target/ios/MyLib.framework/
# 添加 framework 到 Xcode 搜索路径
桌面库加载失败¶
问题:
解决方案:
# Linux:设置 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/path/to/target/linux:$LD_LIBRARY_PATH
# macOS:设置 DYLD_LIBRARY_PATH
export DYLD_LIBRARY_PATH=/path/to/target/macos:$DYLD_LIBRARY_PATH
# Windows:添加到 PATH
set PATH=%PATH%;C:\path\to\target\windows
最佳实践¶
1. API 设计¶
- 保持 C++ API 简单且与 C 兼容以便更容易绑定
- 尽可能使用原始类型(int、double、const char*)
- 避免在公共头文件中使用 C++ 模板和复杂类型
- 提供清晰的错误处理(返回码、异常)
2. 内存管理¶
// 好:清晰的所有权
char* create_string() {
char* str = (char*)malloc(100);
strcpy(str, "Hello");
return str; // 调用者必须释放
}
void free_string(char* str) {
free(str);
}
// 更好:内部使用智能指针
std::string get_string() {
return "Hello";
}
3. 线程安全¶
// 使共享状态线程安全
class ThreadSafeCounter {
std::mutex mutex;
int count = 0;
public:
int increment() {
std::lock_guard<std::mutex> lock(mutex);
return ++count;
}
};
4. 平台特定代码¶
#ifdef __ANDROID__
// Android 特定代码
#elif defined(__APPLE__)
#include <TargetConditionals.h>
#if TARGET_OS_IOS
// iOS 特定代码
#elif TARGET_OS_OSX
// macOS 特定代码
#endif
#elif defined(__linux__)
// Linux 特定代码
#elif defined(_WIN32)
// Windows 特定代码
#endif
性能优化¶
1. 最小化 JNI 调用¶
// 差:多次 JNI 调用
fun processArray(data: IntArray): IntArray {
return data.map { Calculator.add(it, 1) }.toIntArray()
}
// 好:单次 JNI 调用
external fun processArray(data: IntArray): IntArray // C++ 处理整个数组
2. 使用直接 ByteBuffer¶
3. 缓存 JNI 引用¶
// 缓存类和方法 ID
static jclass calculatorClass = nullptr;
static jmethodID addMethod = nullptr;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
vm->GetEnv((void**)&env, JNI_VERSION_1_6);
calculatorClass = (jclass)env->NewGlobalRef(
env->FindClass("com/example/mylib/Calculator"));
addMethod = env->GetMethodID(calculatorClass, "add", "(II)I");
return JNI_VERSION_1_6;
}
示例¶
完整项目¶
参见 ccgo-kmp-example 获取完整的 KMP 项目。
最小示例¶
CCGO 模板中可用: