本文记录 clikt 接入 OpenHarmony 的完整过程,覆盖 KMP 工程盘点、ohosArm64
目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、命令行解析示例、
HAP 构建、签名和真机验收。

Clikt 本身是 Kotlin Multiplatform 命令行解析库,不是 Compose Multiplatform UI
组件库。因此本次适配复用 KMP 侧的真实解析器和测试模型,使用 ArkUI 作为
OpenHarmony 宿主页面,验证同一份 Kotlin 代码可以从 JVM 进入 OpenHarmony ARM64
运行时,而不是在 ArkTS 页面中重新实现一套解析规则。

项目地址: AtomGit/oh-tpc/clikt

上游仓库: ajalt/clikt

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做开源鸿蒙平台 KMP/CMP 适配

Clikt 的核心能力是把命令行参数解析为类型安全的 Kotlin 值,并统一处理默认值、
选项、flag、校验和错误退出码。原仓库面向 JVM、JS、Wasm、Apple、Linux 和 Windows
等平台,OpenHarmony 侧需要增加真正的 Kotlin/Native 目标,才能证明解析器进入了
OpenHarmony 运行时。

如果只把页面重写成 ArkTS,再在页面里手工判断 --name、--count 和 --verbose,
页面虽然可以显示几个字符串,却无法证明共享 Kotlin 解析器、Native 动态库和 N-API
桥接已经连通。本次适配需要解决以下问题:

障碍具体问题
目标缺失KMP 模块默认没有 OpenHarmony 目标,必须加入 ohosArm64() 才能生成 KLIB 和动态库。
工具链不一致Kotlin/Native、Gradle、OpenHarmony LLVM、Native SDK 和 DevEco Hvigor 必须使用匹配版本。
平台边界ArkTS 不能直接持有 Kotlin data class,需要使用 C ABI、C++ N-API 和 JSON。
参数生命周期输入框内容、快捷预设和解析结果必须保持一致,避免页面展示的参数与实际解析参数不同。
错误可见性Clikt 的校验错误、退出码和 Native 异常都要返回页面,不能只在 Native 日志里失败。
内存释放Kotlin/Native 返回的 C 字符串必须由明确的 CliktFree 入口释放。
交付链路复杂Native 动态库、CMake、HAP、签名、设备安装和 ARM64 依赖需要分别验证。

因此,本项目把适配边界放在三个地方:Kotlin/Native 目标配置、C ABI/N-API 桥接层和
ArkUI 示例页面。参数解析和错误语义仍由 Clikt 维护,ArkTS 只负责输入、按钮交互、
结果展示和页面状态。

1.2 Clikt 示例提供的能力

example/shared 中的公共示例使用 Clikt 核心 API 定义了一个简单命令:

  • --name / -n:字符串选项,默认值为 OpenHarmony;
  • --count / -c:整数选项,默认值为 1,有效范围为 1..5;
  • --verbose / -v:flag 选项,用于切换详细输出;
  • CommandLineParser.tokenize:把输入框中的命令行文本拆成参数列表;
  • CliktError:将参数错误转换为可展示的错误文本和退出码;
  • runChecks:在 JVM 示例和 OpenHarmony Native 页面中复用相同的验收检查。

解析结果使用以下结构:

字段含义
id当前结果的来源标识,例如 command 或 preset-1
argv实际参与解析的参数文本
output成功时的命令输出,失败时的 Clikt 错误信息
success是否解析和执行成功
exitCode成功为 0,参数错误通常为 1

页面中的三个按钮职责不同:

按钮行为示例
默认参数写入预设并立即解析--name OpenHarmony --count 1
详细模式写入带 flag 的预设并立即解析--name Clikt --verbose
解析命令只解析输入框当前内容用户可以自行编辑任意参数

1.3 实现适配矩阵

维度适配方式
代码复用公共命令、Clikt 参数声明、tokenize、错误处理和检查逻辑由 Kotlin 共享。
平台目标为核心 clikt 和示例工程加入 ohosArm64,生成 libclikt.so。
桥接稳定使用少量 C ABI 函数和 UTF-8 JSON,避免把 Kotlin 对象地址交给 ArkTS。
UI 完整页面支持输入参数、默认预设、详细预设、手动解析、成功结果和错误结果。
可测试JVM 测试、Native 链接、ELF 依赖、HAP 构建、设备安装分别验收。
签名安全源码只保留签名入口,证书、profile、p12 和密码由开发者本机配置。
仓库规范项目、文章、截图和示例链接统一使用 AtomGit。

本项目没有把 Compose Multiplatform UI 强行移植到 ArkUI。KMP 负责共享 Clikt
解析能力,ArkUI 负责 OpenHarmony 宿主页面;这种边界可以让其他 KMP/CMP 应用复用
Clikt 核心 API,并自行决定页面实现方式。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点 KMP 模块、解析示例和 OpenHarmony 工程边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:解析与序列化   ── 建立 CliktExample、错误状态和稳定 JSON 契约
第 4 阶段:原生桥接       ── Kotlin/Native C ABI、C++ N-API、类型检查和内存释放
第 5 阶段:ArkUI 页面      ── 输入框、预设按钮、手动解析、状态提示和错误展示
第 6 阶段:示例与验证     ── HAP 构建、签名、设备安装、按钮交互和效果图

每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证 Clikt 解析,再把同一份
commonMain 代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,
最后安装签名 HAP 观察设备页面上的解析结果。

三、逐步实现过程

第 1 阶段:项目初始化

1.1 盘点公共 API 和工程边界

项目采用清晰的 KMP、Native 和 DevEco 分层:

clikt/                     Clikt 核心多平台库和 ohosArm64 目标
clikt-mordant/             Mordant 输出扩展
clikt-mordant-markdown/    Markdown 输出扩展
example/shared/            公共命令示例和 JVM 验收测试
example/nativeApp/         ohosArm64 Kotlin/Native 动态库
example/ohosApp/           DevEco Stage 工程、C++ N-API 和 ArkUI 页面
scripts/                   Native、HAP、签名工程和依赖检查脚本
docs/openharmony/          OpenHarmony 验收记录和真机效果图

example 是独立 Gradle 工程,不把 DevEco 工程直接当作根工程的 Kotlin 子模块。
这样可以分别执行 Gradle 和 Hvigor,也可以把 ohosApp 复制到另一个目录后在
DevEco Studio 中配置签名。

1.2 固定工具链和版本矩阵

本次适配使用以下版本约定:

项目配置用途
Kotlin Multiplatform2.2.21-1.0.0JVM、Kotlin/Native 和 KLIB
Gradle8.14.3根工程和 example 工程构建
JDK21Gradle、Kotlin 编译和 Native 任务
OpenHarmony 目标ohosArm64ARM64 真机动态库
DevEco product6.0.0(20)工程兼容和目标版本
DevEco Studio6.0.0 ReleaseIDE 版本,负责工程同步、HAP 构建与签名
ABIarm64-v8aHAP 原生库架构

执行 Gradle 脚本前先选择 JDK 21:

export JAVA_HOME="/path/to/jdk-21"
java -version

OpenHarmony SDK 版本满足编译条件,只说明工程可以生成 HAP;应用是否能安装和运行,
还要由 DevEco 和 ARM64 设备单独验证。

1.3 创建 OpenHarmony 示例目录

示例页面围绕“当前输入、解析结果、退出码、检查状态和最近一次解析”组织:

标题区          Clikt 工作台 / KMP NATIVE
输入区          命令行输入框、解析次数和快捷示例
操作区          默认参数 / 详细模式 / 解析命令
结果区          SUCCESS 或 EXIT、输出文本和实际 argv
检查区          公共检查通过数量和 Native 错误文本

快捷按钮用于快速准备可重复的输入,解析按钮用于验证任意手动编辑的命令行。每次
解析都会更新状态文本和刷新次数,避免“参数没有变化所以看起来没有响应”。

第 2 阶段:目标与依赖打通

2.1 在核心模块加入 ohosArm64

clikt/build.gradle.kts 保留所有原有目标,并增加 OpenHarmony ARM64:

kotlin {
    jvm()
    js { nodejs() }
    wasmJs { nodejs() }
    wasmWasi { nodejs() }

    linuxX64()
    linuxArm64()
    ohosArm64()
    mingwX64()
    macosX64()
    macosArm64()
    iosArm64()
    iosX64()
    iosSimulatorArm64()
}

适配的关键是让公共 commonMain 代码在 ohosArm64 上可编译,而不是复制一份只
能在 ArkTS 中运行的解析实现。Mordant 模块仍然保持原有目标矩阵,因为公开 Mordant
制品没有对应的 OpenHarmony 变体。

2.2 独立 example 工程消费 Clikt

example/shared 使用平台无关的公共源码,并通过项目属性消费本地 Clikt 制品:

kotlin {
    explicitApi()
    jvm()
    ohosArm64()

    sourceSets {
        commonMain.dependencies {
            api("com.github.ajalt.clikt:clikt-core:${property("cliktVersion")}")
        }
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
    }
}

根脚本先发布适配后的本地 Maven 制品,示例工程再使用相同版本执行 JVM 测试和
Native 链接。这样可以确认“被消费的 artifact”与刚刚修改的源码一致。

2.3 构建 Native 动态库

example/nativeApp 生成一个受 linker map 约束的 shared library:

kotlin {
    ohosArm64 {
        binaries.sharedLib {
            baseName = "clikt"
            linkerOpts("--entry=0", "--version-script=${project.file(
                "src/ohosArm64Main/linker/shared-library.map"
            )}")
            linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
        }
    }
}

prepareOhos 在链接成功后复制两个构建产物:

entry/libs/arm64-v8a/libclikt.so
entry/src/main/cpp/include/libclikt_api.h

动态库、生成头文件和 HAP 属于构建产物,仓库会忽略它们;每台开发机都可以从源码
重新生成与本机 SDK 匹配的文件。

第 3 阶段:解析与序列化

3.1 为什么需要统一 JSON 契约

ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin 对象,因此边界使用 UTF-8 JSON:

TextInput command line
        ↓ string
CliktClient.ets
        ↓ N-API run(commandLine)
C++ N-API entry
        ↓ C string
Kotlin/Native CliktRun
        ↓ CliktExamples.execute
CliktExample JSON
        ↓
ArkUI result card

页面只消费 JSON,不复制 option().default()、.int()、.flag() 和 .check() 的
业务规则。这样 JVM 测试和设备页面都使用同一份 Clikt 解析逻辑。

3.2 公共 DemoCommand

示例命令位于 example/shared/src/commonMain:

private class DemoCommand : CoreCliktCommand(name = "clikt-demo") {
    private val name by option("--name", "-n").default("OpenHarmony")
    private val count by option("--count", "-c")
        .int()
        .default(1)
        .check("count must be between 1 and 5") { it in 1..5 }
    private val verbose by option("--verbose", "-v").flag()

    var output: String = ""
        private set

    override fun run() {
        val greeting = List(count) { "Hello, $name!" }.joinToString(" ")
        output = if (verbose) "[verbose] $greeting" else greeting
    }
}

CliktExamples.execute 先调用 CommandLineParser.tokenize,再把参数列表交给
command.parse(argv)。成功结果带 exitCode = 0;CliktError 会被转换为失败结果,
保留错误消息和状态码。

3.3 结果 JSON

正常解析结果保持稳定字段:

{
  "id": "command",
  "argv": "--name HarmonyOS --count 2",
  "output": "Hello, HarmonyOS! Hello, HarmonyOS!",
  "success": true,
  "exitCode": 0
}

Native 异常则返回:

{
  "error": "Native Clikt error"
}

ArkTS 侧会把缺少正常字段的响应转换为结构完整的失败结果,避免页面渲染时访问
undefined 字段。

3.4 公共检查

CliktExamples.runChecks() 覆盖七项检查:

  1. 默认选项可以解析;
  2. 自定义名称和次数可以解析;
  3. 整数选项控制问候语重复次数;
  4. verbose flag 会改变输出前缀;
  5. count = 0 会被校验拒绝;
  6. 公共 API 不依赖平台类型;
  7. OpenHarmony 桥接只传递不可变 JSON 值。

页面将结果展示为 7/7 公共检查通过,这个数字来自 Native 返回,而不是页面写死。

第 4 阶段:原生桥接(技术难点)

4.1 Kotlin/Native 对象不能直接交给 ArkTS

Kotlin/Native 的 CliktExample 属于 Kotlin 运行时对象。ArkTS 只能接收 JavaScript
值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript 对象。

最终采用三层桥接:

ArkTS
  │ JSON string
  ▼
C++ N-API entry
  │ const char*
  ▼
Kotlin/Native C ABI
  │ CliktExamples
  ▼
UTF-8 JSON + explicit free
4.2 方案对比
方案优点缺点选用
直接导出 Kotlin 对象代码少ABI、生命周期和类型不可控❌
只导出解析后的字符串实现简单页面无法展示参数、错误和退出码❌
C ABI + JSON边界清晰、易扩展、易调试有一次序列化开销✅
在 ArkTS 重写完整解析器页面调用简单KMP 和 ArkTS 逻辑容易分叉❌
4.3 Kotlin/Native 导出函数

NativeBridge.kt 对外导出四个 C ABI 符号:

@CName("CliktCatalog")
public fun catalogNative(): CPointer<ByteVar> = response { ... }

@CName("CliktRun")
public fun runNative(commandLine: CPointer<ByteVar>?): CPointer<ByteVar> = response {
    CliktExamples.execute(commandLine?.toKString().orEmpty()).toJson()
}

@CName("CliktRunChecks")
public fun checksNative(): CPointer<ByteVar> = response { ... }

@CName("CliktFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
    if (pointer != null) nativeHeap.free(pointer.rawValue)
}

返回值使用 Native heap 分配的、以 0 结尾的 C 字符串。每一块返回缓冲区都由
CliktFree 释放。

4.4 C++ N-API 方法分发

C++ 注册三个 ArkTS 方法:

napi_property_descriptor methods[] = {
    {"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
        napi_default, nullptr},
    {"run", nullptr, Run, nullptr, nullptr, nullptr,
        napi_default, nullptr},
    {"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
        napi_default, nullptr},
};

run 先读取 ArkTS 字符串长度,再写入可变 std::string 缓冲区,最后调用
CliktRun。ReadNative 创建 ArkTS 字符串后立即调用 CliktFree,保证成功和异常
路径都不泄漏 Native 缓冲区。

CMake 将 Kotlin/Native 动态库作为 imported library:

add_library(clikt SHARED IMPORTED)
set_target_properties(clikt PROPERTIES
  IMPORTED_LOCATION
  "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libclikt.so")

add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE clikt libace_napi.z.so)
4.5 N-API 生命周期
ArkTS run(commandLine)
        │
        ▼
napi_get_value_string_utf8
        │
        ▼
CliktRun(commandLine)
        │
        ▼
napi_create_string_utf8
        │
        ▼
CliktFree(nativeBuffer)
        │
        ▼
return JS string

C++ 负责完成跨语言字符串转换和 Native 释放,ArkTS 页面不需要知道 Kotlin/Native
的堆实现。

第 5 阶段:ArkUI 页面

5.1 页面状态

Index.ets 保存输入参数、解析结果、检查状态、错误文本和解析次数:

@State private commandLine: string = '--name OpenHarmony --count 1';
@State private selected: CliktExample = emptyExample();
@State private status: string = '正在连接 Native Clikt';
@State private errorText: string = '';
@State private parseCount: number = 0;
@State private lastAction: string = '等待操作';

页面加载时会自动执行一次 refresh()。输入框的 onChange 只更新命令行文本,真正
点击“解析命令”时才调用 Native run,避免每次键入字符都触发完整解析。

5.2 三个按钮的实现
private usePreset(commandLine: string): void {
  this.refresh(commandLine);
}

Button('默认参数')
  .onClick(() => this.usePreset('--name OpenHarmony --count 1'))

Button('详细模式')
  .onClick(() => this.usePreset('--name Clikt --verbose'))

Button('解析命令')
  .onClick(() => this.refresh(this.commandLine))

预设按钮将参数直接传给 refresh,不会在状态尚未刷新时读取旧的输入框值。手动解析
按钮始终使用当前输入内容,并在结果卡片中显示“解析完成 · 已刷新第 N 次”。

5.3 成功与失败展示

成功时页面显示 SUCCESS、Clikt 输出和实际 argv;失败时显示 EXIT 1、错误文本
和公共检查状态。Native 返回格式错误或 JSON 解析异常也会被 CliktClient.ets
转换为失败结果,不会让页面因为字段缺失直接崩溃。

第 6 阶段:示例与验证

6.1 example 工程结构
example/
├── shared/
│   ├── src/commonMain/.../CliktExamples.kt
│   └── src/commonTest/.../CliktExamplesTest.kt
├── nativeApp/
│   ├── src/ohosArm64Main/.../NativeBridge.kt
│   └── src/ohosArm64Main/linker/shared-library.map
└── ohosApp/
    ├── AppScope/
    ├── entry/src/main/cpp/
    │   ├── CMakeLists.txt
    │   └── napi_init.cpp
    └── entry/src/main/ets/
        ├── pages/Index.ets
        └── clikt/CliktClient.ets

shared 验证公共 Clikt 模型,nativeApp 产生 Native 动态库,ohosApp 负责 ArkUI
页面和 N-API 加载。三者边界清晰,任何一层失败都能单独定位。

6.2 原生模块注册

napi_init.cpp 通过 napi_module_register 注册 entry 模块,类型声明位于:

example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts

ArkTS 使用:

import cliktNative from 'libentry.so';
6.3 Native 动态库准备

执行:

export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh

脚本依次执行根模块测试、示例测试、linkDebugSharedOhosArm64 和 prepareOhos。
成功后生成:

example/ohosApp/entry/libs/arm64-v8a/libclikt.so
example/ohosApp/entry/src/main/cpp/include/libclikt_api.h

还可以检查 ARM64 ELF 的强依赖:

python3 scripts/check-native-deps.py \
  /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
  example/ohosApp/entry/libs/arm64-v8a
6.4 构建、签名和安装

未配置签名时,可以在 DevEco Studio 构建本地调试 HAP;真机安装需要有效签名。配置
签名后执行:

python3 scripts/prepare-signing-project.py /absolute/path/clikt-signing
DEVECO_HOME=/Applications/DevEco-Studio.app/Contents \
  scripts/build-hap.sh /absolute/path/clikt-signing

产物位于:

entry/build/default/outputs/default/entry-default-signed.hap

安装并启动:

HDCP=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
"$HDCP" install -r \
  /absolute/path/clikt-signing/entry/build/default/outputs/default/entry-default-signed.hap
"$HDCP" shell aa start \
  -a EntryAbility -b com.github.ajalt.clikt.sample -m entry

签名证书、profile、p12 和密码只保存在本机,不要提交到 AtomGit。

四、完整代码对照

4.1 整体架构

TextInput / preset button
    │ command-line string
    ▼
CliktClient.ets
    │ libentry.so
    ▼
C++ N-API
    │ C ABI
    ▼
libclikt.so
    │ Kotlin/Native
    ▼
CliktExamples.execute
    │ CoreCliktCommand.parse
    ▼
CliktExample -> JSON -> ArkUI result card

4.2 文件清单

文件职责
clikt/src/commonMain/...Clikt 核心解析 API
example/shared/.../CliktExamples.kt公共命令示例、JSON 前的结果模型和自检
example/shared/.../CliktExamplesTest.ktJVM 解析和错误验收
example/nativeApp/.../NativeBridge.ktC ABI、JSON 返回和内存释放
example/ohosApp/.../CliktClient.etsN-API 调用、JSON 解析和结果兜底
example/ohosApp/.../Index.ets输入、预设、解析按钮和结果页面
example/ohosApp/.../napi_init.cppN-API 导出、参数读取和 C 字符串转换
scripts/build-openharmony.sh测试、Native 链接和产物复制
scripts/build-hap.sh已配置签名工程的 HAP 构建
docs/openharmony/VALIDATION.md自动检查和真机验收记录

4.3 关键 API 对照

层次API作用
KotlinCommandLineParser.tokenize将命令行文本拆成参数列表
KotlinCoreCliktCommand.parse使用 Clikt 解析选项和 flag
KotlinCliktError返回错误文本和退出码
NativeCliktRun返回一条命令的 JSON 结果
NativeCliktRunChecks返回公共检查 JSON
N-APIrun向 ArkTS 暴露解析方法
ArkTSrefresh更新页面输入、结果和状态

4.4 ArkTS 与 Kotlin 的边界

ArkTS 只负责输入和界面生命周期:

const result: CliktExample = run(this.commandLine);
this.selected = result;

Kotlin 负责参数语义、默认值、类型转换、范围校验和输出:

val command = DemoCommand()
command.parse(argv)
CliktExample("command", argv.joinToString(" "), command.output, true, 0)

两者之间只传输字符串和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验的动态
结构。

五、关键决策说明

决策 1:把 ohosArm64 加入核心构建约定

只有真正链接 ohosArm64 动态库,才能证明共享 Clikt 代码可以进入 OpenHarmony
运行时。只添加一个 ArkTS 页面不能代表 KMP 库已经完成适配。

决策 2:独立消费者必须通过构建产物消费

example 单独解析 Clikt,ohosApp 单独接收 .so 和头文件,避免根工程编译通过
却无法在 DevEco 中打包。

决策 3:JSON 作为跨语言数据契约

JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加可信字段而
不暴露内部对象布局。

决策 4:桥接层只开放四个 C ABI 入口

目录、解析、自检和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期
和兼容风险。

决策 5:快捷预设和手动解析分开

预设按钮用于快速验证固定参数,解析按钮用于验证用户输入。两者都调用同一个
refresh,不会出现页面逻辑和库逻辑各自维护一套解析器的情况。

决策 6:把库验证和设备验证分开

JVM 测试验证 Clikt 规则,Native 链接验证 ABI,Hvigor 验证 HAP,真机验证安装和
ArkUI 页面。每一层都有明确的失败边界。

六、测试与验证

6.1 测试环境

本次真机验证使用:

  • macOS;
  • JDK 21;
  • Kotlin Multiplatform 2.2.21-1.0.0;
  • DevEco Studio 及 OpenHarmony ARM64 Native SDK;
  • DevEco 自带 Node.js 和 Hvigor;
  • 已签名 entry-default-signed.hap;
  • USB 连接的 HarmonyOS ARM64 真机;
  • hdc 设备序列号 FMR0223825079397。

6.2 静态检查与单元测试

./gradlew :clikt:jvmTest :test:jvmTest
(cd example && ./gradlew :shared:jvmTest)

测试覆盖默认选项、自定义名称、重复次数、flag、整数范围错误和公共检查列表。

6.3 原生桥接和 HAP 验证

./scripts/build-openharmony.sh
python3 scripts/check-native-deps.py \
  /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
  example/ohosApp/entry/libs/arm64-v8a

本地 DevEco 构建命令:

PATH=/Applications/DevEco-Studio.app/Contents/tools/node/bin:$PATH \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
  assembleApp --no-daemon

验证结果:

Hvigor BUILD SUCCESSFUL
entry-default-signed.hap generated
ARM64 native library packaged

6.4 功能验证用例

用例 1:默认页面和自检

启动应用后页面显示默认命令 --name OpenHarmony --count 1、SUCCESS 和公共检查
通过数量。初始结果由 Native run 生成。

用例 2:默认参数按钮

点击“默认参数”,输入框恢复默认命令并重新解析,结果为:

Hello, OpenHarmony!
用例 3:详细模式按钮

点击“详细模式”,结果为:

[verbose] Hello, Clikt!

这个用例同时验证 flag 参数已经由 Clikt 处理,而不是页面手工拼接前缀。

用例 4:自定义参数和解析按钮

输入:

--name OpenHarmony --count 2

点击“解析命令”后,页面显示两次问候语、实际 argv 和“解析完成 · 已刷新第 N 次”。

用例 5:无效参数

输入 --count 0,页面显示 EXIT 1 和 count must be between 1 and 5。Native
错误不会导致 ArkUI 页面崩溃。

用例 6:重复解析

连续点击“解析命令”,结果内容不变但刷新次数递增,证明按钮事件被执行且页面有明确
反馈。这个场景解决了初始默认参数与再次解析结果相同、用户误以为按钮无响应的问题。

6.5 验证结论

公共 JVM 测试、Kotlin/Native ARM64 链接、ELF 依赖检查、Hvigor HAP 构建、签名安装
和真机页面交互均已完成。真机页面可以执行默认、详细和手动参数解析,说明从 Clikt
核心到 ArkTS 展示的链路可用。

七、运行效果

7.1 真机截图

在这里插入图片描述

截图中可以看到:

  • 页面标题为“Clikt 工作台”;
  • 副标题说明这是命令行解析示例;
  • 顶部标识显示 KMP NATIVE;
  • 输入框当前为 --name OpenHarmony --count 2;
  • 快捷按钮提供“默认参数”和“详细模式”;
  • 主按钮为“解析命令”;
  • 结果卡片显示两次 Hello, OpenHarmony!;
  • 状态为 SUCCESS,公共检查为 7/7 公共检查通过;
  • 解析结果下方显示实际参与解析的参数。

7.2 命令速查

# 根工程测试和 OpenHarmony Native 准备
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh

# 单独运行共享测试
(cd example && ./gradlew :shared:jvmTest)

# 设备依赖检查
python3 scripts/check-native-deps.py \
  /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
  example/ohosApp/entry/libs/arm64-v8a

# 在已配置签名的工程中构建 HAP
./scripts/build-hap.sh /absolute/path/clikt-signing

# 安装和启动
hdc install -r \
  /absolute/path/clikt-signing/entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.github.ajalt.clikt.sample -m entry

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配:必须把共享 Clikt 代码真正编译成 ohosArm64
    动态库。
  2. N-API 不负责业务判断:C++ 只做参数检查、字符串转换和释放,参数语义放在
    Kotlin Clikt 命令中。
  3. Native 字符串必须及时释放:每次 CliktRun、CliktCatalog 和
    CliktRunChecks 返回后都要配对调用 CliktFree。
  4. 解析按钮需要可见反馈:默认参数已经在页面启动时解析,再次点击若只显示相同
    文本,用户会误以为没有响应,因此页面增加了刷新次数和状态提示。
  5. JDK、Node 和 DevEco 工具链要分开确认:Gradle 使用 JDK 21,Hvigor 使用
    DevEco 自带 Node,避免系统 Node 版本导致 Native 构建异常。
  6. 签名材料不能进入源码:证书、profile、p12 和密码必须放在独立的本机工程中。

8.2 已知问题

  • 当前示例只构建 ohosArm64 和 arm64-v8a,不覆盖 OpenHarmony x86 或其他 ABI;
  • Clikt 是命令行解析库,ArkUI 页面是验收宿主,不提供 Compose Multiplatform UI 组件;
  • Native 和 HAP 构建依赖本机 OpenHarmony SDK、DevEco 工具链和签名配置;
  • 页面采用同步 Native 调用,复杂或长时间运行的命令应在业务层增加异步调度;
  • 文章中的签名配置只适用于本地开发机,不能直接复制到其他环境。

8.3 未来优化方向

  • 增加 Flow<CliktExample> 或异步执行接口,支持更复杂的命令任务;
  • 为 Compose Multiplatform 宿主补充共享状态卡片示例,保持 ArkUI 与 CMP 的模型一致;
  • 增加命令行引号、转义和多行输入的交互测试;
  • 增加设备能力、签名状态和 Native 库版本的诊断页面;
  • 在持续集成中加入 ohosArm64 链接、ELF 依赖检查和 HAP 未签名构建任务;
  • 将 AtomGit 文档、示例截图和版本发布说明纳入同一套发布流程。

九、总结

9.1 核心难点回顾

本次适配真正需要处理的不是一个按钮,而是一条完整跨端链路:

命令行文本
    → ArkUI 输入和页面状态
    → C++ N-API
    → Kotlin/Native C ABI
    → KMP Clikt 核心解析器
    → JSON
    → ArkUI 结果页面

9.2 封装层次

  • KMP 层:定义选项、默认值、类型转换、flag、范围检查和错误语义;
  • Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理输入框、快捷预设、解析动作、结果状态和错误提示;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 先让共享 Clikt 模型在 JVM 和 Native 通过,再接入 ArkUI;
  2. 用 JSON 和少量 C ABI 代替跨语言对象传递;
  3. 把自动测试、HAP 构建和真机交互分别记录,避免把“编译成功”误认为“设备运行链路可用”。

9.4 适配成果

当前 Clikt OpenHarmony 适配已完成:

  • clikt 核心模块新增 ohosArm64 目标;
  • example/shared 复用真实 Clikt parser、默认选项、flag 和范围校验;
  • example/nativeApp 生成 ARM64 libclikt.so 和 C ABI 头文件;
  • Kotlin/Native + C ABI + C++ N-API JSON 桥接;
  • ArkUI 页面支持输入、默认参数、详细模式和手动解析;
  • 解析成功、退出码、错误文本和公共检查状态可视化;
  • HAP 构建、签名安装和 Mate 60 Pro 真机验证;
  • AtomGit 项目文档、OpenHarmony 验收记录和真机效果图;
  • 仓库中的代码托管地址、项目文档和发布链接已统一使用 AtomGit。

适配过程 FAQ

以下问题来自 ohosArm64 目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。

Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?

  • 确认 DevEco Studio 版本与工程 build-profile.json5 的 compatibleSdkVersion 一致;
  • 在 File > Settings > Build, Execution, Deployment > Ohpm 配置可用的 ohpm 仓库镜像或代理;
  • 删除 oh_modules、oh-package-lock.json5 与 .hvigor 缓存后重新 Sync;
  • 公司网络需要代理时,同时给 ohpm 与 hvigor 配置 http.proxy。

Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?

  • 先在 example 目录执行 ./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用 linkDebugSharedOhosX64);
  • 确认 libxxx.so 与 libc++_shared.so 已复制到 ohos/entry/libs/<abi>/;
  • 检查 entry/build-profile.json5 的 abiFilters 与设备 ABI 是否一致(真机 arm64-v8a,模拟器 x86_64);
  • 清理 entry/.cxx 与 entry/build 后重试。

Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?

  • 确认 CMake 的 add_library(<target> SHARED ...) 名称与 import ... from 'libxxx_napi.so' 完全一致;
  • 解包 HAP,确认 libs/<abi>/libxxx_napi.so 已打包;
  • 删除 entry/build、entry/.cxx 与 oh_modules 后重新构建;
  • 检查 module.json5 的 srcEntry 与页面路径是否正确。

Q4:真机安装报 signature verification failed 或 install failed?

  • 在 DevEco Studio 的 Signing Configs 配置证书、Profile 与密码;
  • bundleName 必须与签名 profile 中的 bundle name 一致;
  • 设备需加入 profile 的 deviceIds,调试证书有效期需覆盖当前时间;
  • 用 hdc list targets 确认设备在线,再执行 hdc install -r xxx-signed.hap。

Q5:应用启动白屏、闪退或 NAPI 调用崩溃?

  • 用 hdc hilog | grep -i "napi\|kotlin" 查看原生日志;
  • 确认 libc++_shared.so 与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致;
  • 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用 XxxFree 释放;
  • 在 DevEco 的 Native 调试配置中附加 lldb,定位空指针与越界访问。

Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?

  • 使用鸿蒙发行版 Kotlin(如 2.2.21-1.0.0),并在 settings.gradle.kts 配置 eazytec 仓库;
  • Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
  • 确认 kotlin { ohosArm64() } 已应用,且 local.properties 的 SDK 路径有效;
  • 清理 ~/.gradle/caches 中对应版本的 Kotlin/Native 缓存后重试。

Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?

  • 对齐 DevEco Studio、HarmonyOS SDK 与 build-profile.json5 中的 compatibleSdkVersion;
  • 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免 modelVersion 与 IDE 不匹配;
  • 命令行构建时设置 DEVECO_SDK_HOME 指向 DevEco 的 sdk 目录。

Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?

  • 使用 DevEco 自带的 node 与 hvigor:
    "$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...;
  • 设置 JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME;
  • 加上 --no-daemon 排除守护进程缓存影响,并清理 entry/build 与 .hvigor。

参考文档

这里写自定义目录标题

欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants 是一个文本转换工具,主要功能是将普通的 ASCII 标点符号自动转换为更美观的印刷体标点符号。例如:

原始符号转换后说明
"引号"“引号”直引号变弯引号
'单引号'‘单引号’直单引号变弯单引号
--–两个连字符变短破折号
---—三个连字符变长破折号
...…三个点变省略号

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML。

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n−1)!∀n∈N 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞​tz−1e−tdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

2014-01-07 2014-01-09 2014-01-11 2014-01-13 2014-01-15 2014-01-17 2014-01-19 2014-01-21 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML图表

可以使用UML图表进行渲染,例如下面产生的一个序列图:

王五 李四 张三 王五 李四 张三 李四想了很长时间, 文字太长了 不适合放在一行. 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 打量着王五... 很好... 王五, 你怎么样?
  • 关于 UML图表 语法,参考 这儿,

流程图

链接

长方形

圆

圆角长方形

菱形

  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart.js的流程图语法:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐