【开源软件移植】从 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

需要保证三件事:

  1. $QT_OHOS_ROOT 指向 Qt-OHOS 5.12.12 交叉编译版本
  2. $OHOS_SDK_ROOT 指向 OpenHarmony NDK
  3. 存在一份 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 个关键信息:

  1. 20 个 cpp 文件,无 .qrc / .ts 资源
  2. 包含路径. ./kreplacements
  3. 依赖 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 删掉?

  1. 维护成本:要保留一份 patch 文件,未来升级源码很麻烦
  2. 风险:尽管 register 在现代 C++ 里实际是空操作,但删除关键字仍是侵入性修改
  3. 改 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 件事:

  1. 创建 HAP 工程Kdiff3Ohos/(参考 QtOhosDemoDiffPdfOhos 模板)
  2. 集成 .so:把 kdiff3-ohos-libs/ 全部内容塞到 entry/libs/arm64-v8a/
  3. 改 ArkTS 入口QtAppConstants.etsAPP_LIBRARY_NAME = 'libkdiff3.so'
  4. 签名 + 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 壳模板(本仓库里的 QtOhosDemoDiffPdfOhos),整个目录复制一份,改名、清缓存、替依赖:

在这里插入图片描述

# 在 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/.cxxentry/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.so
  • platforms/libqohos.sostyles/libqohosstyle.soimageformats/×9

13.3 签名:一行被反复忽略的 signingConfig: "default"

这是整个真机部署里最容易吞掉半小时的坑。流程如下:

  1. DevEco Studio → FileProject StructureSigning Configs

  2. 勾选 ✅ Automatically generate signature

  3. 用华为账号登录(首次会跳浏览器)

  4. IDE 自动生成调试证书 + Profile,并写回 build-profile.json5signingConfigs 数组:

    "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 后重生成证书。

  5. 关键一步:在 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

  1. 物理连接:USB 连接鸿蒙 PC,首次需在设备端弹窗中允许 USB 调试;

  2. 检查 hdc 识别

    $ hdc list targets
    24580B6B...    Connected
    
  3. Build → Clean Project(强烈建议,清掉可能残留的 unsigned 产物缓存);

  4. Sync and Refresh Project,让 IDE 重新读取 build-profile.json5

  5. 工具栏选中 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 应用迭代工作了。

在这里插入图片描述

Logo

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

更多推荐