前言
本文成文环境:
- macOS 26
- Xcode 26 以及 iOS SDK
- Android Studio MeerKat Feature Drop 以及 Android SDK、NDK
- JDK 17
- Gradle 8.11.1
Kotlin Multiplatfrom 通过Cinterop工具调用C/C++方法,也可以通过JNI规范调用C/C++方法。
本质上,都是调用C方法,C++方法需要通过extern "C"
修饰为C方法。
本文将介绍Kotlin/Native在Android和iOS环境,如何调用C/C++方法。
Android Studio
推荐使用 Android Studio 开发Kotlin/Native (Kotlin更像是一款Android开发语言)
Gradle
Android Studio的御用构建工具(相比其构建工具,复杂且难用)
CMake
一个事实上的C/C++构建标准工具,在Android和iOS环境调用各自平台的工具链构建C/C++。
创建 Kotlin Multiplatfrom 工程
如果Android Studio没有 Kotlin Multiplatfrom 工程模版,则需要先安装 Kotlin Multiplatfrom插件。
安装完成后,使用 Kotlin Multiplatfrom 工程模版创建一个工程(创建过程中,需要选择iOS的包管理方式,选择cocoapods)
镜像
因为环大陆的网络墙问题,可能需要配置下镜像仓库
1、找到工程根目录下的settings.gradle.kts
,添加下镜像仓库(下面配置的是阿里云的maven仓库)
// settings.gradle.kts
pluginManagement {
repositories {
maven{ setUrl("https://maven.aliyun.com/repository/public") }
maven{ setUrl("https://maven.aliyun.com/repository/central") }
maven{ setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
...
...
...
}
}
dependencyResolutionManagement {
repositories {
maven{ setUrl("https://maven.aliyun.com/repository/public") }
maven{ setUrl("https://maven.aliyun.com/repository/central") }
maven{ setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
...
...
...
}
}
2、找到根目录下gradle/wrapper/gradle-wrapper.properties
,替换为distributionUrl
#gradle/wrapper/gradle-wrapper.properties
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
bin是二进制工具,all包含二进制和源码,gradle工具构建时可能会下载源码。
修改shared模块
默认情况下,创建的工程会有一个shared模块
展开后,可以看到src目录下有androidMain、commonMain、iosMain
使用Android Studio在shared模块中添加C/C++(鼠标右键选中shared模块,Add C/C++ to Module),这样就添加了Android Native相关代码,目录如下:
shared
|_src
|_commonMain (通用方法)
|_kotlin
|_com.example.kmpapptest(包名)
|_Platform.kt
|_androidMain (android平台相关的方法)
|_kotlin
|_com.example.kmpapptest(包名)
|_Platform.android.kt
|_iosMain (ios平台相关的方法)
|_kotlin
|_com.example.kmpapptest(包名)
|_Platform.ios.kt
|_main (C/C++方法)
|_cpp
|_CMakeLists.txt (Android so构建配置)
|_main.cpp
因为我们使用的Android Studio的Add C/C++ to Module
菜单创建的C/C++目录,所以在shared模块的build.gradle.kts里会自动添加Android平台的JNI构建任务
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags += ""
}
}
...
}
...
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
...
}
expect/actual
kotlin关键字 expect
用来声明方法,actul
标记实现方法。
我们在commonMain 中的Platform.kt
声明(expect)一个 get_native_message()
方法
// shared/src/commonMain/kotlin/com/example/kmpapptest/Platform.kt
package com.example.kmpapptest
expect fun get_native_message(): string
在androidMain和iOSmain各自实现(actual)这个get_native_message
方法
// shared/src/androidMain/kotlin/com/example/kmpapptest/Platform.kt
package com.example.kmpapptest
actual fun get_native_message(): String{
return AndroidNativeLib.stringFromJNI()
}
class AndroidNativeLib {
companion object {
external fun stringFromJNI() : String
init {
System.loadLibrary("android-native-lib")
}
}
}
// shared/src/iosMain/kotlin/com/example/kmpapptest/Platform.kt
package com.example.kmpapptest
import kotlinx.cinterop.toKString
@kotlinx.cinterop.ExperimentalForeignApi
actual fun get_native_message(): String{
return iosNativeLib.get_message()?.toKString().toString()
}
Kotlin通过JNI在Android环境调用C/C++方法
再来回顾下 AndroidNativeLib
的stringFromJNI
方法
package com.example.kmpapptest
class AndroidNativeLib {
companion object {
external fun stringFromJNI() : String
init {
System.loadLibrary("android-native-lib")
}
}
}
在C/C++侧,对应的方法名为Java_com_example_kmpapptest_AndroidNativeLib_00024Companion_stringFromJNI
kotlin方法名和C/C++方法的对应规则:
1、Java_包名_类名_方法名
2、Java_包名_方法名
3、Java_包名_类名_00024Companion_方法名
"00024Companion" 是 "$Companion" 的转译字符串
在shared//src/main/cpp/main.cpp
中添加对应方法
//shared/src/main/cpp/main.cpp
#include <jni.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_kmpapptest_NativeLib_00024Companion_stringFromJNI(JNIEnv * env , jobject thiz) {
return env->NewStringUTF("test_kotlin_call_android_native");
}
修改shared/src/main/cpp/CMakeLists.txt
...
project("android-native-lib") # 这里的名字要和AndroidNativeLib类加载的lib库名对应
...
到这里,可以运行 androidApp
看下效果
Kotlin通过Cinterop在iOS环境调用C/C++方法
在shared/main/
创建cinterop/CMakeLists.txt
,cinterop/ios_cmake.sh
,cinterop/ios.cpp
,cinterop/ios.h
,cinterop/ios.def
shared
|_src
|_main (C/C++方法)
|_cinterop
|_CMakeLists.txt (ios lib 构建配置)
|_ios_cmake.sh (cmake构建具体命令)
|_ios.h (方法声明)
|_ios.cpp (方法实现)
|_ios.def (暴露给kotlin的方法定义文件)
方法声明和实现
//shared/src/main/cinterop/ios.h
#ifndef TEST_SHARED_IOS_H
#define TEST_SHARED_IOS_H
#if defined(__cplusplus)
extern "C" {
#endif
const char *get_message(void);
#if defined(__cplusplus)
}
#endif
#endif //TEST_SHARED_IOS_H
//shared/src/main/cinterop/ios.cpp
#include "ios.h"
extern "C" const char *get_message(void)
{
return "cinterop_test_kotlin_call_ios_native";
}
CMakeLists.txt 和 ios_cmake.sh
编辑shared/src/main/cinterop/CMakeLists.txt
# shared/src/main/cinterop/CMakeLists.txt
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# 编译模块名 产物为libnative-lib
project("native-lib")
# 设置系统环境
set(CMAKE_SYSTEM_NAME iOS)
# 设置cpu arch
set(CMAKE_OSX_ARCHITECTURES arm64 x86_64)
# 指定头文件目录
include_directories(${CMAKE_SOURCE_DIR})
# 收集所有 源文件
file(GLOB SOURCES
${CMAKE_SOURCE_DIR}/*.cpp
)
add_library(${CMAKE_PROJECT_NAME} STATIC
# List C/C++ source files with relative paths to this CMakeLists.txt.
${SOURCES})
编辑shared/src/main/cinterop/ios_cmake.sh
# 产物输出目录 (外面传入参数)
build_output_dir=$1
# 根据CMakeLists.txt生产构建配置信息,如makefile
cmake . -B $build_output_dir
cd $build_output_dir
#执行构建
cmake --build .
可以手动执行ios_cmake.sh
看下效果,执行后记得清理输出物
cd ios_cmake.sh目录
./ios_cmake.sh ./buildout
.def文件
暴露给kotlin的方法定义文件
内容如下:
# shared/src/main/cinterop/ios.def
# 暴露给kotlin的包名
package = iosNativeLib
# 暴露给kotlin的头文件,kotlin侧可以看到里面的方法名
# 这里的路径是相对于.def文件的路径
headers = ios.h
# 构建过程中,链接库的搜索目录
# 这里的路径是相对于projectdir(gradle的这个设定是不是bug?)
# 也可以使用绝对路径,从工程管理角度不建议用绝对路径
libraryPaths = build/cmake
# 需要链接的库文件,这里需要链接上面的iOS nativle lib
# 查找libraryPaths目录下的库文件
staticLibraries = libnative-lib.a
修改Gradle脚本
上面已经完成了ios native lib的构建,以及native lib需要暴露的方法配置,现需要使用Gradle脚本将这些工作串起来。
// shared/build.gradle.kts
fun org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget.addCInterop_sharedCC() {
val main by compilations.getting // target name : main or test
val iosNativeLib by main.cinterops.creating {
// .def 定位文件
defFile("src/main/cinterop/ios.def")
// kotlin侧需要搜索的头文件目录
includeDirs("src/main/cinterop")
}
}
kotlin {
...
iosX64() {
addCInterop_sharedCC()
}
iosArm64() {
addCInterop_sharedCC()
}
iosSimulatorArm64() {
addCInterop_sharedCC()
}
...
}
...
// 创建ios_buildCmake任务
val ios_buildCmake by tasks.registering(Exec::class) {
val ios_cmakebuildDir = File(projectDir ,"build/cmake")
val ios_cmakeSourceDir = File(projectDir,"src/main/cinterop")
description = "Build native C/C++ with CMake"
workingDir = ios_cmakeSourceDir
commandLine = listOf("sh", "ios_cmake.sh" , ios_cmakebuildDir.absolutePath)
}
# 设置cinterop的ios构建任务依赖ios_buildCmake任务
tasks.matching { it.name.startsWith("cinterop") && it.name.contains("Ios") }.all {
dependsOn(ios_buildCmake)
}
至此,可以执行iOSApp看看效果
总结
在无法摆脱C/C++生态的情况下,使用Kotlin Multiplatfrom实现跨平台,维护成本比较高,不如直接使用C/C++。
发表回复
要发表评论,您必须先登录。