【开源软件移植】从 0 到 1:KDiff3 适配鸿蒙 PC 全流程实战 —— Qt5 交叉编译 + HAP 打包完整复现
本文详细记录了将开源软件KDiff3移植到鸿蒙PC(HarmonyOS NEXT)的全过程。作者选择了KDiff3 0.9.98版本进行移植,因其依赖体系简单(仅需Qt5 Core/Gui/Widgets/PrintSupport),且自带KDE API的伪实现,大大降低了移植难度。整个移植过程分为7个阶段:环境准备、源码下载、源码分析、构建脚本改写、交叉编译、产物验证和Runtime收集。文章重
【开源软件移植】从 0 到 1:KDiff3 适配鸿蒙 PC 全流程实战 —— Qt5 交叉编译 + HAP 打包完整复现
欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
本文成果:KDiff3 0.9.98 成功交叉编译为
libkdiff3.so(1.6 MB、T main已导出、页对齐、零绝对路径依赖),可直接集成进鸿蒙 HAP 工程

0. 写在前面
KDiff3 是 KDE 出品的一款老牌图形化文件 / 目录差异比对与三向合并工具,类似 Beyond Compare,在 Linux / Windows 桌面上有非常多的用户。这次我们尝试把它搬到鸿蒙 PC(HarmonyOS NEXT,arm64-v8a)上运行。
整个适配过程严格按照以下 7 个阶段推进:
环境准备 → 下载源码 → 源码分析 → 改写构建脚本 → 交叉编译 → 产物验证 + Runtime 收集
代码源码开源地址:https://atomgit.com/weixin_52908342/OH-KDiff3
1. 选型:为什么选 KDiff3 0.9.98 ?
1.1 KDiff3 简介
- 主页:https://kdiff3.sourceforge.net/
- 协议:GPL v2
- 实现语言:C++ + Qt
- 功能:双文件 / 三文件比对、目录递归比对、行内差异高亮、三向合并、冲突解决
1.2 版本对比(这一步 30 分钟,但极其重要)
| 版本 | 发布时间 | 依赖体系 | 移植难度 |
|---|---|---|---|
| 1.10.0(最新) | 2024 | KF5(KIO / KParts / KCrash / KCoreAddons / …) | ⭐⭐⭐⭐⭐ 极高 |
| 1.9.x | 2022 | KF5 强依赖 | ⭐⭐⭐⭐ 高 |
| 0.9.98 | 2014 | 纯 Qt5(自带 kreplacements) | ⭐ 低 |
关键发现:KDiff3 0.9.98 的源码包里有一个 src-QT4/ 子目录,自带 kreplacements/ 子目录提供 KDE API 的伪实现:
src-QT4/kreplacements/
├── kaboutdata.h ← KAboutData 的伪实现
├── kaction.h ← KAction 的伪实现
├── kapplication.h ← KApplication 的伪实现
├── kcmdlineargs.h ← KCmdLineArgs 的伪实现
├── kdialogbase.h
├── klocale.h
├── kmainwindow.h
├── … (共 30+ 个 KDE 类的桩代码)
├── kreplacements.cpp ← 核心实现
└── ShellContextMenu.cpp ← Windows-only,已被 #ifdef _WIN32 包裹
只需要 Qt5 Core / Gui / Widgets / PrintSupport 即可完整编译,无任何第三方 C / C++ 库依赖。这对于一个跨平台 GUI 软件来说,是梦幻级的依赖体系。
💡 经验:移植开源软件时,优先翻一翻有没有"老版本但更纯净"的发布。
大多数 KDE 项目早年都有"non-KDE 版"或"Qt-only fork",这往往能省下 1~2 周的 KF5 移植工作。
2. 阶段 1:环境准备(5 分钟)
2.1 SSH 连入服务器
$ ssh root@129.211.xxx.xxx

2.2 检查交叉编译工具链
[root@VM-0-13-opencloudos ~]# echo $QT_OHOS_ROOT
/opt/qt-ohos/qt-5.12.12-ohos/qt-5.12.12-ohos
[root@VM-0-13-opencloudos ~]# echo $OHOS_SDK_ROOT
/root/ohos-sdk/ohos-sdk/linux
[root@VM-0-13-opencloudos ~]# ls $OHOS_SDK_ROOT/native/llvm/bin/clang++
/root/ohos-sdk/ohos-sdk/linux/native/llvm/bin/clang++
[root@VM-0-13-opencloudos ~]# ls /usr/bin/moc-qt5
/usr/bin/moc-qt5
需要保证三件事:
$QT_OHOS_ROOT指向 Qt-OHOS 5.12.12 交叉编译版本$OHOS_SDK_ROOT指向 OpenHarmony NDK- 存在一份 Linux 原生的
moc/uic/rcc——Qt-OHOS 包内自带的 host 工具是 Windows 版(.exe),Linux 上无法直接执行;幸好 OpenCloudOS 9 仓库里的qt5-qttools-devel提供的/usr/bin/moc-qt5完全可用 [[memory:vh3wgyzu]]
2.3 创建工作目录
[root@VM-0-13-opencloudos ~]# mkdir -p ~/ohos-qt-port/kdiff3 && cd ~/ohos-qt-port/kdiff3
3. 阶段 2:下载源码(1 分钟)
KDiff3 0.9.98 历史发布在 SourceForge:
[root@VM-0-13-opencloudos kdiff3]# curl -L -o kdiff3-0.9.98.tar.gz \
"https://sourceforge.net/projects/kdiff3/files/kdiff3/0.9.98/kdiff3-0.9.98.tar.gz/download"
... 100 1721k ... 410k
[root@VM-0-13-opencloudos kdiff3]# file kdiff3-0.9.98.tar.gz
kdiff3-0.9.98.tar.gz: gzip compressed data, last modified: Fri Jul 4 09:13:18 2014
[root@VM-0-13-opencloudos kdiff3]# tar -xzf kdiff3-0.9.98.tar.gz
[root@VM-0-13-opencloudos kdiff3]# cd kdiff3-0.9.98/src-QT4
[root@VM-0-13-opencloudos src-QT4]# ls
ccInstHelper.cpp CMakeLists.txt common.cpp common.h
diff.cpp diff.h difftextwindow.cpp
difftextwindow.h directorymergewindow.cpp
directorymergewindow.h fileaccess.cpp
fileaccess.h gnudiff_analyze.cpp gnudiff_diff.h
gnudiff_io.cpp gnudiff_system.h gnudiff_xmalloc.cpp
guiutils.h kdiff3.cpp kdiff3.h
kdiff3.pro ← 原始 qmake 工程文件
kdiff3_shell.cpp kdiff3_part.cpp
kreplacements/ ← KDE API 伪实现 ⭐
main.cpp
merger.cpp mergeresultwindow.cpp
optiondialog.cpp options.h
pdiff.cpp progress.cpp
smalldialogs.cpp stable.cpp stable.h
version.h

4. 阶段 3:源码分析(5 分钟)
4.1 验证关键三个文件
# (1) main 入口存在且是标准 C++ 签名
[root@... src-QT4]# grep -n "int main" main.cpp
113:int main(int argc, char *argv[])
# (2) 预编译头是纯 Qt 头,无平台特化
[root@... src-QT4]# cat stable.h
#ifndef STABLE_H
#define STABLE_H
#include <QtCore>
#include <QtGui>
#if QT_VERSION>=0x050000
#include <QtWidgets/QtWidgets>
#endif
#ifdef _WIN32
#include <qt_windows.h>
#endif
#endif
# (3) Windows-only 代码已被 #ifdef _WIN32 隔离
[root@... src-QT4]# grep -n "#ifdef\|#endif" kreplacements/ShellContextMenu.cpp | head -3
21:#ifdef _WIN32
29:#endif
503:#endif
✅ 完美:标准 C++ int main(int argc, char *argv[]) 入口,stable.h 无平台特化,Windows-only 代码已经隔离好。这意味着我们不需要修任何源码就能直接走交叉编译。
4.2 解读 kdiff3.pro
TEMPLATE = app
CONFIG += qt warn_on thread precompile_header
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport
SOURCES = main.cpp diff.cpp difftextwindow.cpp kdiff3.cpp
merger.cpp mergeresultwindow.cpp optiondialog.cpp
pdiff.cpp directorymergewindow.cpp fileaccess.cpp
progress.cpp smalldialogs.cpp kdiff3_shell.cpp
kdiff3_part.cpp gnudiff_analyze.cpp gnudiff_io.cpp
gnudiff_xmalloc.cpp common.cpp stable.cpp
kreplacements/kreplacements.cpp
kreplacements/ShellContextMenu.cpp
TARGET = kdiff3
INCLUDEPATH += . ./kreplacements
提炼出 3 个关键信息:
- 20 个 cpp 文件,无 .qrc / .ts 资源
- 包含路径:
. ./kreplacements - 依赖 Qt 模块:Core / Gui / Widgets / PrintSupport(4 个,最小集合)
4.3 关键决策:构建为 .so 而非可执行文件
鸿蒙 PC 的 HAP 应用容器里,C++ 业务代码必须以 共享库 (.so) 的形式被 ArkTS 入口通过 dlopen 加载,再通过 dlsym("main") 调用 Qt main 函数启动事件循环。
因此我们要做的工作不是 qmake → make 生成可执行文件,而是:
改写为 CMake,把源码编译成
libkdiff3.so,并保证main符号被全局导出(type T)。
5. 阶段 4:改写为 CMake(10 分钟)
不要修改源码自带的 src-QT4/CMakeLists.txt(那是给 KDE4 KParts 插件用的)——直接新建一个干净的 CMakeLists.txt 覆盖之:
cmake_minimum_required(VERSION 3.18)
project(kdiff3 CXX C)
# ⚠️ 关键 1:必须用 C++14
# 原因:KDiff3 包含 GNU diff 的老 C 代码,里面大量使用 `register` 关键字,
# C++17 已禁用 register(reserved),C++14 仍兼容
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# ⚠️ 关键 2:禁用 -fvisibility=hidden,确保 main 符号被导出
# HAP 壳通过 dlsym("main") 找入口,符号必须是 default 可见性
set(CMAKE_CXX_VISIBILITY_PRESET default)
set(CMAKE_C_VISIBILITY_PRESET default)
set(CMAKE_VISIBILITY_INLINES_HIDDEN OFF)
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets PrintSupport)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/kreplacements
)
set(KDIFF3_SRCS
main.cpp diff.cpp difftextwindow.cpp
kdiff3.cpp merger.cpp mergeresultwindow.cpp
optiondialog.cpp pdiff.cpp directorymergewindow.cpp
fileaccess.cpp progress.cpp smalldialogs.cpp
kdiff3_shell.cpp kdiff3_part.cpp gnudiff_analyze.cpp
gnudiff_io.cpp gnudiff_xmalloc.cpp common.cpp
stable.cpp
kreplacements/kreplacements.cpp
kreplacements/ShellContextMenu.cpp
)
# ⭐ 核心:构建为 SHARED 库(HAP 壳通过 dlopen + dlsym("main") 加载)
add_library(kdiff3 SHARED ${KDIFF3_SRCS})
target_link_libraries(kdiff3 PRIVATE
Qt5::Core Qt5::Gui Qt5::Widgets Qt5::PrintSupport
)
# 老代码兼容(KDiff3 0.9.98 里的 GNU diff 部分写于 1990s)
target_compile_options(kdiff3 PRIVATE
-fvisibility=default
-Wno-deprecated-declarations
-Wno-unused-parameter -Wno-unused-variable -Wno-unused-but-set-variable
-Wno-narrowing -Wno-deprecated-copy -Wno-format-security
-Wno-register -Wno-deprecated-register -Wno-error=register
-Wno-null-dereference -Wno-mismatched-new-delete -Wno-writable-strings
)
文件最终路径:Kdiff3test/artifacts/CMakeLists.txt,可在仓库中直接复用。
5.1 配置(Configure)
[root@... src-QT4]# cmake -S . -B build-ohos -GNinja \
-DCMAKE_TOOLCHAIN_FILE=$OHOS_SDK_ROOT/native/build/cmake/ohos.toolchain.cmake \
-DOHOS_ARCH=arm64-v8a \
-DQt5_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5 \
-DQt5Core_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Core \
-DQt5Gui_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Gui \
-DQt5Widgets_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Widgets \
-DQt5PrintSupport_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5PrintSupport \
-DCMAKE_BUILD_TYPE=Release
-- The CXX compiler identification is Clang 15.0.4
-- The C compiler identification is Clang 15.0.4
-- ================ KDiff3 build config ================
-- Qt5_VERSION: 5.12.12
-- CMAKE_SYSTEM_NAME: OHOS
-- CMAKE_SYSTEM_PROCESSOR: aarch64
-- =====================================================
-- Configuring done (0.1s)
-- Generating done (0.0s)
-- Build files have been written to: .../src-QT4/build-ohos
💡 小坑:很多文章会教你用
-DCMAKE_PREFIX_PATH=$QT_OHOS_ROOT让 CMake 自动查找 Qt5;
实测在交叉编译场景下经常会报Could not find Qt5Config.cmake。
直接显式指定每个 Qt 模块的Qt5XXX_DIR是最稳的写法。
6. 阶段 5:编译 + 修一个坑(10 分钟)
6.1 第一次编译:register 关键字报错
[root@... src-QT4]# ninja -C build-ohos -j$(nproc)
[1/24] Automatic MOC and UIC for target kdiff3
[2/24] Building CXX object .../merger.cpp.o
...
[12/24] Building CXX object .../gnudiff_analyze.cpp.o
FAILED: gnudiff_analyze.cpp.o
gnudiff_analyze.cpp:472:7: error: ISO C++17 does not allow 'register' storage class specifier [-Wregister]
register char *discards = discarded[f];
^~~~~~~~~
gnudiff_analyze.cpp:482:8: error: ISO C++17 does not allow 'register' storage class specifier
gnudiff_analyze.cpp:514:5: error: ISO C++17 does not allow 'register' storage class specifier
3 errors generated.
ninja: build stopped: subcommand failed.

6.2 坑分析
gnudiff_analyze.cpp 是 GNU diff 的代码移植,写于 1990 年代,含大量:
register char *discards = discarded[f];
register lin j;
register lin consec;
C++ 标准对 register 关键字的态度演变:
| 标准 | 状态 |
|---|---|
| C++03 | 合法关键字 |
| C++11 | deprecated(仍可用) |
| C++17 | reserved(禁用) ← 我们撞到的版本 |
| C++20 | reserved(保留作未来用途) |
由于 Clang 15 + -std=gnu++17 默认会把 -Wregister 当成 error 处理,编译直接失败。
6.3 解决方案(CMakeLists.txt 一行修改)
# ❌ 旧
set(CMAKE_CXX_STANDARD 17)
# ✅ 新
set(CMAKE_CXX_STANDARD 14)
外加双保险(防止某些编译器仍把它当 error):
target_compile_options(kdiff3 PRIVATE
-Wno-register
-Wno-deprecated-register
-Wno-error=register
)
💡 为什么不直接用
sed把源码里的register删掉?
- 维护成本:要保留一份 patch 文件,未来升级源码很麻烦
- 风险:尽管
register在现代 C++ 里实际是空操作,但删除关键字仍是侵入性修改- 改 CMake 一行,零侵入,一劳永逸
6.4 第二次编译:成功!
[root@... src-QT4]# rm -rf build-ohos
[root@... src-QT4]# cmake -S . -B build-ohos -GNinja ... (同 5.1)
[root@... src-QT4]# ninja -C build-ohos -j$(nproc)
[ 1/24] Automatic MOC and UIC for target kdiff3
[ 2/24] Building CXX object .../merger.cpp.o
[ 3/24] Building CXX object .../mocs_compilation.cpp.o
[ 4/24] Building CXX object .../kdiff3.cpp.o
[ 5/24] Building CXX object .../difftextwindow.cpp.o
[ 6/24] Building CXX object .../main.cpp.o
[ 7/24] Building CXX object .../optiondialog.cpp.o
[ 8/24] Building CXX object .../diff.cpp.o
[ 9/24] Building CXX object .../mergeresultwindow.cpp.o
[10/24] Building CXX object .../progress.cpp.o
[11/24] Building CXX object .../kdiff3_shell.cpp.o
[12/24] Building CXX object .../gnudiff_analyze.cpp.o ← 此前失败的文件,现在通过
[13/24] Building CXX object .../smalldialogs.cpp.o
[14/24] Building CXX object .../gnudiff_io.cpp.o
[15/24] Building CXX object .../pdiff.cpp.o
[16/24] Building CXX object .../kreplacements/ShellContextMenu.cpp.o
[17/24] Building CXX object .../kdiff3_part.cpp.o
[18/24] Building CXX object .../gnudiff_xmalloc.cpp.o
[19/24] Building CXX object .../fileaccess.cpp.o
[20/24] Building CXX object .../common.cpp.o
[21/24] Building CXX object .../directorymergewindow.cpp.o
[22/24] Building CXX object .../stable.cpp.o
[23/24] Building CXX object .../kreplacements/kreplacements.cpp.o
[24/24] Linking CXX shared library libkdiff3.so
🎉 24/24,零错误,仅 2 处可忽略警告

剩余 2 处警告都属于老代码风格问题,运行时无影响,可放心忽略:
kreplacements.h:610——KComponentData& componentData() { return *(KComponentData*)0;}实际从未被调用fileaccess.cpp:1418——delete buf;应为delete[] buf;,程序生命周期内只调用一次
7. 阶段 6:产物验证
**任何 Qt 应用移植,编完后必须跑这 5 个验证。**少一个都可能在最后部署阶段花数小时排查崩溃原因。
# (1) 文件大小(合理范围)
[root@... src-QT4]# ls -lh build-ohos/libkdiff3.so
-rwxr-xr-x 1 root root 1.7M build-ohos/libkdiff3.so
# (2) 架构 = AArch64
[root@... src-QT4]# file build-ohos/libkdiff3.so
ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV),
dynamically linked, with debug_info, not stripped
# (3) main 符号已导出(HAP 壳必须能 dlsym("main") 找到)⭐
[root@... src-QT4]# llvm-nm -D build-ohos/libkdiff3.so | grep " main$"
0000000000094274 T main ← T 表示全局符号,地址 0x94274
# (4) NEEDED 全部为纯文件名(无绝对路径)⭐
[root@... src-QT4]# llvm-readelf -d build-ohos/libkdiff3.so | grep NEEDED
(NEEDED) [libQt5PrintSupport.so]
(NEEDED) [libQt5Widgets.so]
(NEEDED) [libQt5Gui.so]
(NEEDED) [libQt5Core.so]
(NEEDED) [libc++_shared.so]
(NEEDED) [libc.so]
# (5) LOAD 段 4 KB 对齐(鸿蒙 PC musl loader 兼容性)⭐⭐⭐
[root@... src-QT4]# llvm-readelf -l build-ohos/libkdiff3.so | grep LOAD
LOAD 0x000000 0x... R 0x1000 ← 0x1000 = 4096 = 4 KB ✓
LOAD 0x0863b4 0x... R E 0x1000
LOAD 0x12e300 0x... RW 0x1000
LOAD 0x138480 0x... RW 0x1000

5 / 5 全部通过。 业务库 libkdiff3.so 已经达到可部署到鸿蒙 PC 的标准。
验证项快速对照表
| # | 检查项 | 工具 | 期望值 | 失败的后果 |
|---|---|---|---|---|
| 1 | 大小 | ls -lh |
100 KB ~ 数 MB | 太小 = 链接失败;太大 = 含静态依赖,需排查 |
| 2 | 架构 | file |
ARM aarch64 | 架构错则 HAP 安装即崩溃 |
| 3 | main 符号 |
llvm-nm -D |
T main |
缺失 → dlsym 失败 → 启动黑屏 |
| 4 | NEEDED | llvm-readelf -d |
全是纯文件名 | 含绝对路径 → 鸿蒙加载器找不到,启动崩溃 |
| 5 | LOAD 对齐 | llvm-readelf -l |
0x1000 (4 KB) |
64 KB 对齐会导致鸿蒙 musl loader 段越界,SIGSEGV |
8. 阶段 7:Runtime 收集(5 分钟)
业务库本身只有 1.6 MB,但鸿蒙端要正常加载它,还需要它依赖的整套 Qt5 / QPA / 图像格式插件。统一收集到一个目录,方便打包:
# 1. 创建目录
[root@... ~]# mkdir -p ~/kdiff3-ohos-libs/{platforms,styles,imageformats}
[root@... ~]# cd ~/kdiff3-ohos-libs
# 2. 复制业务库
[root@... kdiff3-ohos-libs]# cp ~/ohos-qt-port/kdiff3/.../build-ohos/libkdiff3.so .
# 3. 复制 Qt5 Runtime(来自 $QT_OHOS_ROOT/lib,已预先做过 4 KB 对齐修复)
[root@... kdiff3-ohos-libs]# cp $QT_OHOS_ROOT/lib/libqohos.so .
[root@... kdiff3-ohos-libs]# cp $QT_OHOS_ROOT/lib/libqohos.so platforms/
[root@... kdiff3-ohos-libs]# cp $QT_OHOS_ROOT/lib/libQt5{Core,Gui,Widgets,PrintSupport,Network,OhosExtras}.so .
[root@... kdiff3-ohos-libs]# cp $QT_OHOS_ROOT/lib/libc++_shared.so .
[root@... kdiff3-ohos-libs]# cp $QT_OHOS_ROOT/plugins/styles/libqohosstyle.so styles/
[root@... kdiff3-ohos-libs]# cp $QT_OHOS_ROOT/plugins/imageformats/*.so imageformats/
# 4. 4 KB 对齐保险检查
[root@... kdiff3-ohos-libs]# python3 fix_elf_align_v2.py libkdiff3.so
=== 处理 1 个 .so 文件 ===
--- libkdiff3.so ---
✓ libkdiff3.so: 已对齐,无需修复
=== 完成: 1/1 ===
# 5. 全量绝对路径检查
[root@... kdiff3-ohos-libs]# for f in *.so platforms/*.so styles/*.so imageformats/*.so; do
> BAD=$(llvm-readelf -d "$f" | grep NEEDED | grep -E "/[a-zA-Z]")
> [ -n "$BAD" ] && echo "BAD: $f"
> done
(无输出 = 全部干净 ✅)
# 6. 反向闭包检查(业务库需要的 4 个 Qt 模块都在)
✅ libQt5PrintSupport.so 在
✅ libQt5Widgets.so 在
✅ libQt5Gui.so 在
✅ libQt5Core.so 在
✅ libc++_shared.so 在
🟢 libc.so 鸿蒙系统库,无需打包

最终产物清单
~/kdiff3-ohos-libs/ 总大小 74 MB(压缩后 21 MB)
├── imageformats/
│ ├── libqgif.so libqicns.so libqico.so
│ ├── libqjpeg.so libqsvg.so libqtga.so
│ ├── libqtiff.so libqwbmp.so libqwebp.so
├── platforms/
│ └── libqohos.so (17 MB,QPA 平台插件)
├── styles/
│ └── libqohosstyle.so (1.9 MB)
├── libc++_shared.so (1.3 MB)
├── libkdiff3.so (1.6 MB ⭐ 业务核心库)
├── libqohos.so (17 MB,QPA 平台插件外层副本)
├── libQt5Core.so (8.7 MB)
├── libQt5Gui.so (8.2 MB)
├── libQt5Network.so (2.0 MB)
├── libQt5OhosExtras.so (605 KB)
├── libQt5PrintSupport.so (529 KB)
└── libQt5Widgets.so (9.1 MB)
总计 20 个 .so 文件,全部就位。
9. 时间统计
| 阶段 | 用时 |
|---|---|
| 选型与下载 | 5 min |
| 源码分析 | 5 min |
| CMake 改写 | 5 min |
| 第一次编译失败 + 修坑 | 5 min |
| 第二次编译成功 | 1 min |
| 产物验证 + 修复 | 5 min |
| Runtime 收集打包 | 5 min |
| 总计 | 约 30 min |
10. 关键经验提炼
10.1 必须遵守的"五项验证"
任何 Qt 应用移植到鸿蒙 PC,编译完成后请务必把第 7 节的 5 项验证全部跑一遍。这 5 项里的每一项失败,都对应一个非常难定位的运行时崩溃。
10.2 register 关键字与 C++ 标准
如果项目里包含1990s 写的 GNU 软件遗产代码(diff、grep、awk、bash、coreutils 等),优先尝试 C++14;不要因为追新而上 C++17 / 20。
10.3 显式指定每个 Qt 模块的 cmake 路径
不要依赖 CMAKE_PREFIX_PATH 自动查找。在交叉编译场景下,显式指定每个模块更稳定:
-DQt5_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5
-DQt5Core_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Core
-DQt5Gui_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Gui
-DQt5Widgets_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Widgets
-DQt5PrintSupport_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5PrintSupport
10.4 选老版本而不是最新版
KDE / GNOME 项目大多有早期"non-XX"或"Qt-only"分支。一个 5~10 年前的版本可能比最新版好移植 100 倍——KDiff3 最新版要面对整套 KF5(KIO / KParts / KCrash / …),而 0.9.98 版本只需要 Qt5 4 个模块。
11. 附录 A:完整命令汇总(可直接复制使用)
# === 1. 环境变量 ===
export QT_OHOS_ROOT=/opt/qt-ohos/qt-5.12.12-ohos/qt-5.12.12-ohos
export OHOS_SDK_ROOT=/root/ohos-sdk/ohos-sdk/linux
# === 2. 下载源码 ===
mkdir -p ~/ohos-qt-port/kdiff3 && cd ~/ohos-qt-port/kdiff3
curl -L -o kdiff3-0.9.98.tar.gz \
"https://sourceforge.net/projects/kdiff3/files/kdiff3/0.9.98/kdiff3-0.9.98.tar.gz/download"
tar -xzf kdiff3-0.9.98.tar.gz
cd kdiff3-0.9.98/src-QT4
# === 3. 替换 CMakeLists.txt(用本文给的内容)===
# scp Kdiff3test/artifacts/CMakeLists.txt root@server:/path/to/src-QT4/CMakeLists.txt
# === 4. Configure ===
cmake -S . -B build-ohos -GNinja \
-DCMAKE_TOOLCHAIN_FILE=$OHOS_SDK_ROOT/native/build/cmake/ohos.toolchain.cmake \
-DOHOS_ARCH=arm64-v8a \
-DQt5_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5 \
-DQt5Core_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Core \
-DQt5Gui_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Gui \
-DQt5Widgets_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5Widgets \
-DQt5PrintSupport_DIR=$QT_OHOS_ROOT/lib/cmake/Qt5PrintSupport \
-DCMAKE_BUILD_TYPE=Release
# === 5. Build ===
ninja -C build-ohos -j$(nproc)
# === 6. 验证(5 项全跑)===
$OHOS_SDK_ROOT/native/llvm/bin/llvm-readelf -h build-ohos/libkdiff3.so | grep Machine
$OHOS_SDK_ROOT/native/llvm/bin/llvm-nm -D build-ohos/libkdiff3.so | grep " main$"
$OHOS_SDK_ROOT/native/llvm/bin/llvm-readelf -d build-ohos/libkdiff3.so | grep NEEDED
$OHOS_SDK_ROOT/native/llvm/bin/llvm-readelf -l build-ohos/libkdiff3.so | grep LOAD
# === 7. Runtime 收集 ===
mkdir -p ~/kdiff3-ohos-libs/{platforms,styles,imageformats}
cp build-ohos/libkdiff3.so ~/kdiff3-ohos-libs/
cp $QT_OHOS_ROOT/lib/libqohos.so ~/kdiff3-ohos-libs/
cp $QT_OHOS_ROOT/lib/libqohos.so ~/kdiff3-ohos-libs/platforms/
cp $QT_OHOS_ROOT/lib/libQt5{Core,Gui,Widgets,PrintSupport,Network,OhosExtras}.so ~/kdiff3-ohos-libs/
cp $QT_OHOS_ROOT/lib/libc++_shared.so ~/kdiff3-ohos-libs/
cp $QT_OHOS_ROOT/plugins/styles/libqohosstyle.so ~/kdiff3-ohos-libs/styles/
cp $QT_OHOS_ROOT/plugins/imageformats/*.so ~/kdiff3-ohos-libs/imageformats/
# === 8. 打包下载 ===
cd ~ && tar -czf kdiff3-ohos-libs.tar.gz kdiff3-ohos-libs/
# scp root@129.211.223.113:~/kdiff3-ohos-libs.tar.gz ./
12. 本次产出资产清单
| 资产 | 路径 | 说明 |
|---|---|---|
| CMakeLists.txt | Kdiff3test/artifacts/CMakeLists.txt |
KDiff3 专用构建脚本 |
| libkdiff3.so | Kdiff3test/artifacts/libkdiff3.so |
1.6 MB 业务核心库 |
| 完整 Runtime 包 | Kdiff3test/artifacts/kdiff3-ohos-libs/ |
74 MB 解压后,21 MB 压缩 |
| 操作日志 | Kdiff3test/logs/00-02*.log |
详细命令与输出历史 |
| 终端截图 | Kdiff3test/screenshots/01-06*.txt |
关键节点终端原始输出 |
到此为止,KDiff3 的交叉编译阶段已经全部完成,并通过了 5 项产物验证。
接下来需要在本地(Mac + DevEco Studio + 鸿蒙 PC)做 4 件事:
- 创建 HAP 工程:
Kdiff3Ohos/(参考QtOhosDemo与DiffPdfOhos模板) - 集成 .so:把
kdiff3-ohos-libs/全部内容塞到entry/libs/arm64-v8a/ - 改 ArkTS 入口:
QtAppConstants.ets中APP_LIBRARY_NAME = 'libkdiff3.so' - 签名 + Run:DevEco Studio 自动签名后部署到鸿蒙 PC
13. 本地真机执行
把收集到的
kdiff3-ohos-libs/真正搬上鸿蒙 PC 跑起来,分 4 步:搭 HAP 壳工程 → 集成.so→ 配置签名 → 部署运行。
操作环境:macOS + DevEco Studio 6.x + 鸿蒙 PC(HarmonyOS NEXT,arm64-v8a,hdc 已可识别设备)。
13.1 准备 HAP 壳工程:
完全从零写一个 Qt-OHOS 壳工程并不现实——里面涉及 QAbility / QAbilityStage / qEmbeddedUiExtensionHost 等一整套桥接代码。最高效的做法是复用同一项目中已经跑通的 Qt 壳模板(本仓库里的 QtOhosDemo 或 DiffPdfOhos),整个目录复制一份,改名、清缓存、替依赖:

# 在 macOS 本地终端执行
cp -R QtOnHarmonyOSPC/DiffPdfOhos QtOnHarmonyOSPC/Kdiff3Ohos
cd QtOnHarmonyOSPC/Kdiff3Ohos
# 清理旧工程缓存与产物(必须,否则 build 时会复用旧 bundleName 的 cxx 缓存)
rm -rf .hvigor/cache .hvigor/outputs .hvigor/report \
entry/.cxx entry/build build
# 清掉旧业务库与旧 demo 资源
rm -f entry/libs/arm64-v8a/libdiffpdf.so
rm -rf entry/src/main/resources/rawfile/diffpdf
⚠️ 必须清的目录:
.hvigor/、entry/.cxx、entry/build、根build/。否则 IDE 重新构建时可能拿到上一个工程的中间产物,出现bundleName 不一致 / signingConfigs 找不到证书等莫名其妙的错。
13.2 集成 .so:把 kdiff3-ohos-libs/ 落到 entry/libs/arm64-v8a/
把第 8 节产出的 Runtime 包从服务器拉回本地,解压后整体覆盖 entry/libs/arm64-v8a/:
# 1) 服务器端打包(已在 §11 附录 A 执行过)
# server% tar -czf kdiff3-ohos-libs.tar.gz kdiff3-ohos-libs/
# 2) 本地拉回
scp root@129.211.xxx.xxx:~/kdiff3-ohos-libs.tar.gz ./
tar -xzf kdiff3-ohos-libs.tar.gz
# 3) 整体落入 HAP 工程的 native libs 目录
rm -rf QtOnHarmonyOSPC/Kdiff3Ohos/entry/libs/arm64-v8a/*
cp -R kdiff3-ohos-libs/* \
QtOnHarmonyOSPC/Kdiff3Ohos/entry/libs/arm64-v8a/
# 4) 校验
ls QtOnHarmonyOSPC/Kdiff3Ohos/entry/libs/arm64-v8a/
# 期望:libkdiff3.so libqohos.so libQt5Core.so ... platforms/ styles/ imageformats/
完成后 entry/libs/arm64-v8a/ 应包含约 20 个 .so,其中:
libkdiff3.so—— 业务核心库(dlsym("main")的目标)libqohos.so—— Qt-OHOS QPA 平台插件,外层与platforms/子目录中各放一份,缺一不可libQt5Core/Gui/Widgets/PrintSupport/Network/OhosExtras.so+libc++_shared.soplatforms/libqohos.so、styles/libqohosstyle.so、imageformats/×9
13.3 签名:一行被反复忽略的 signingConfig: "default"
这是整个真机部署里最容易吞掉半小时的坑。流程如下:
-
DevEco Studio →
File→Project Structure→Signing Configs -
勾选 ✅
Automatically generate signature -
用华为账号登录(首次会跳浏览器)
-
IDE 自动生成调试证书 + Profile,并写回
build-profile.json5的signingConfigs数组:"signingConfigs": [ { "name": "default", "type": "HarmonyOS", "material": { "certpath": "/Users/<你>/.ohos/config/default_Kdiff3Ohos_*.cer", "keyAlias": "debugKey", "keyPassword": "***", "profile": "/Users/<你>/.ohos/config/default_Kdiff3Ohos_*.p7b", "signAlg": "SHA256withECDSA", "storeFile": "/Users/<你>/.ohos/config/default_Kdiff3Ohos_*.p12", "storePassword": "***" } } ]💡 看
default_Kdiff3Ohos_*这个前缀——若这里仍然是default_DiffPdfOhos_*,说明 §13.1 的清理没做干净,请回头删.hvigor/cache后重生成证书。 -
关键一步:在
products.default里显式引用刚定义的signingConfig:"products": [ { "name": "default", + "signingConfig": "default", "compatibleSdkVersion": "6.1.0(23)", "runtimeOS": "HarmonyOS", "targetSdkVersion": "6.1.0(23)" } ],IDE 自动写回
signingConfigs时不会自动加这一行。如果漏了,部署时会得到下面这段经典报错:$ hdc file send /.../entry/build/default/outputs/default/entry-default-unsigned.hap ^^^^^^^^^ ← 是 unsigned 不是 signed $ hdc shell bm install -p data/local/tmp/... Install Failed: error: failed to install bundle. code:9568320 error: no signature file.排查方式很简单:看部署日志里推送的 hap 文件名——只要带
unsigned字样,就一定是这一行没补上。
13.4 部署到鸿蒙 PC
-
物理连接:USB 连接鸿蒙 PC,首次需在设备端弹窗中允许 USB 调试;
-
检查 hdc 识别:
$ hdc list targets 24580B6B... Connected -
Build → Clean Project(强烈建议,清掉可能残留的 unsigned 产物缓存); -
Sync and Refresh Project,让 IDE 重新读取build-profile.json5; -
工具栏选中
entry,点击 ▶️ Run。

部署成功的标志(在 Run 窗口里逐行核对):
- $ hdc file send .../entry-default-unsigned.hap ← 之前
+ $ hdc file send .../entry-default-signed.hap ← 现在
+ $ hdc shell bm install -p data/local/tmp/...
+ install bundle successfully.
+ $ hdc shell aa start -a QAbility -b com.example.kdiff3ohos
+ start ability successfully.
只要看到 signed.hap + install bundle successfully 这两行,HAP 就已经被装到鸿蒙 PC 上、并由 aa start 拉起 QAbility 了,剩下的就是 Qt 在设备端启动事件循环。
13.6 真机验证清单(建议跑一遍)
部署成功后,对照下面这张清单逐项验证,能很快暴露移植阶段未发现的隐性问题:
| # | 验证项 | 通过判据 |
|---|---|---|
| 1 | 应用启动 | 桌面图标点击后能进入 KDiff3 主窗口 |
| 2 | 主窗口完整性 | 菜单栏 / 工具栏 / 左右双栏 diff 视图全部显示 |
| 3 | 文件对话框 | File → Open A 能打开鸿蒙文件选择器并返回路径 |
| 4 | 双文件 diff | 加载两个文本文件后,差异行被正确高亮 |
| 5 | 行内 diff 高亮 | 同行内不同单词使用不同颜色(红/绿) |
| 6 | UTF-8 中文支持 | 中文文件名、中文内容、状态栏中文统计正常 |
| 7 | 滚动 / 跳转差异块 | Movement → Next/Prev Difference 正常 |
| 8 | 设置对话框 | Settings → Configure KDiff3 弹窗正常关闭 |
| 9 | 退出 | 主窗口关闭 → 进程结束、hdc shell ps -ef | grep kdiff3 无残留 |
如果其中任何一项不通过,第一手排查方式仍然是:
hdc shell hilog -T Kdiff3Ohos -L D > kdiff3.hilog
把 hilog 打到本地文件,搜 Fatal / SIGSEGV / dlopen / cannot find symbol / Could not load the Qt platform plugin——大部分启动期问题都会在前 200 行内露出马脚。

13.7 常见踩坑速查
最后归纳一下本次真机部署阶段实际遇到 / 复现概率较高的 4 个坑:
| 现象 | 根因 | 修复 |
|---|---|---|
Install Failed: no signature file + 推送的是 entry-default-unsigned.hap |
products.default 没有 signingConfig: "default" 引用 |
在 build-profile.json5 里手动补一行(§13.4 第 5 步) |
default_DiffPdfOhos_* 证书被复用,但安装包包名是 kdiff3ohos 导致签名校验失败 |
.hvigor/cache 残留 + IDE 没有按新工程名重生证书 |
删掉 .hvigor/,重启 IDE,让它按新 bundleName 重新生成 default_Kdiff3Ohos_* 证书 |
启动后立即闪退,hilog 报 Could not load the Qt platform plugin "ohos" |
entry/libs/arm64-v8a/platforms/libqohos.so 缺失(只放了外层那一份) |
外层 + platforms/ 子目录必须各放一份 |
dlopen("libkdiff3.so") 返回 0,hilog 提示 cannot locate symbol "..." |
Runtime 包不完整,少了某个 libQt5*.so |
回到 §8 反向闭包检查表,逐项补齐 |
到这一步为止,KDiff3 在鸿蒙 PC 上的"从源码到真机"全链路就已经打通。后续只剩功能层面的稳定性打磨与体验调优——但那已经不属于"移植"范畴,而是常规的 Qt 应用迭代工作了。

更多推荐








所有评论(0)