【开源软件移植】NitroShare 适配鸿蒙 PC 全流程实战 — Qt-OHOS × 手把手移植教程
本文详细介绍了将开源文件传输工具NitroShare移植到鸿蒙PC平台的全过程。主要内容包括: 选择NitroShare的原因:作为Qt实现的局域网文件传输工具,具有代码精简(1.4MB)、依赖简单(Qt5Network/Widgets/Svg)的特点,适合作为鸿蒙移植案例。 准备工作: 完成Qt-OHOS环境搭建 创建DevEco HAP壳工程 配置动态库加载路径 签名处理.so文件 移植过程中
【开源软件移植】NitroShare 适配鸿蒙 PC 全流程实战 — Qt-OHOS × 手把手移植教程
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
注意(本部分必看):
开始本文工作前需要完成:
从0创建项目指南,新手先看这篇,都写到这篇文章内了,重复的步骤不过多重复写,后续工作都在用HAP 壳工程:
https://blog.csdn.net/weixin_52908342/article/details/161343743
准备 OpenHarmony SDK
准备 Qt for HarmonyOS
复现最小 Qt Widgets Demo
准备 DevEco HAP 壳工程
完成上面步骤,即可跟着本文进行手把手教学适配NitroShare!
NitroShare 适配鸿蒙 PC 全流程实战,博主苦战了2天半,踩了很多坑。输出了这篇手把手教学文章,小白可以快速上手从0复现。



源码开源仓库:https://atomgit.com/weixin_52908342/OH-NitroShare
0. 写在前面:为什么是 NitroShare?
NitroShare 是一个由 Nathan Osman 开发的局域网文件秒传工具——可以理解为 “开源版 AirDrop / LocalSend 的 Qt 实现”。它在 GitHub 有 1.6K star,体积只有 1.4 MB 源码、70 个 .cpp/.h 文件,是这个系列里最小的 Qt 桌面应用候选。
挑它的 4 个理由:
| 维度 | NitroShare 的特殊性 |
|---|---|
| 构建系统 | CMake(前 4 篇都是 qmake,这是本系列首次讲 CMake) |
| 依赖矩阵 | 只用 Qt5Network/Widgets/Svg,无 KF5、无 sqlite、无 OpenGL |
| 网络栈 | 暴露了 Qt-OHOS 5.12.12 没编 OpenSSL 的特性,要剥 QSslConfiguration(新课题) |
| 运行期 | 暴露了 鸿蒙沙箱不允许 bind 任意 TCP/UDP 端口——listen(40818) 必失败,引出 |
跟前 4 篇形成的对照矩阵:
| 篇 | 应用 | 构建 | 依赖难点 | 运行期难点 | 产物体积 | 篇幅时间 |
|---|---|---|---|---|---|---|
| 1 | glogg | qmake | 几无 | — | 7.0 MB | 21 min |
| 2 | KDiff3 | qmake | KF5 剥离 | — | 9.5 MB | ~1 h |
| 3 | DiffPDF | qmake | poppler-qt5 | — | 2.1 MB | ~1.5 h |
| 4 | QElectroTech | qmake | KF5 + sqlite + moc | — | 17 MB | 2 h |
| 5 | NitroShare | CMake | QT_NO_SSL | 鸿蒙沙箱 listen 失败 + 模态错误框假性闪退 | 587 KB → 891 KB | 35 min + 真机调试 ~10 min |

新手必看前置步骤
从0创建项目指南,新手先看这篇:
https://blog.csdn.net/weixin_52908342/article/details/161343743
QT官方鸿蒙版开源地址:https://wiki.qt.io/Qt5.12.12_Open_Source_Release_for_HarmonyOS_zh
QT官方文档地址:https://wiki.qt.io/Qt_for_OpenHarmony/zh
环境要求
(HarmonyOS/OpenHarmony)鸿蒙版本 API20+
Qt Creator安装 安装电脑版Qt5.12或以上版本(5.14、5.15),获得QtCreator的IDE。
华为 DevEco Studio 安装 如果您想开发Qt for HarmonyOS应用程序,除了使用Qt Creator之外,还需要依赖DevEco Studio。
准备 DevEco HAP 壳工程
这一步在 DevEco Studio 里做。
创建工程
在 DevEco Studio 中:
File
New
Create Project
选择一个最简单的 Stage 模板。工程名可以叫:
QtOhosDemo
目标结构大致类似:
QtOhosDemo/
entry/
src/main/
ets/
cpp/
libs/
如果你使用的是 Qt for HarmonyOS 官方模板,里面通常已经有加载 Qt runtime 的代码。
设置加载库名
找到类似文件:
entry/src/main/ets/common/QtAppConstants.ets
设置:
export const APP_LIBRARY_NAME = 'libqt_ohos_demo.so';
如果你的模板没有这个文件,就搜索:
APP_LIBRARY_NAME
loadLibrary
libqohos
目标是让 HAP 启动时加载:
libqt_ohos_demo.so
放入动态库
创建目录:
entry/libs/arm64-v8a/
把下面文件放进去:
libqt_ohos_demo.so
libqohos.so
libQt6Core.so 或 libQt5Core.so
libQt6Gui.so 或 libQt5Gui.so
libQt6Widgets.so 或 libQt5Widgets.so
libqt_ohos_demo.so 来自:
~/qt-ohos-demo/build-ohos/libqt_ohos_demo.so
Qt runtime 的 .so 来自你的 Qt for HarmonyOS 安装目录。
签名 .so
在构建主机上签名:
export SIGN_TOOL=$OHOS_SDK_ROOT/toolchains/lib/binary-sign-tool
cd /path/to/QtOhosDemo/entry/libs/arm64-v8a
$SIGN_TOOL sign -inFile libqt_ohos_demo.so -outFile libqt_ohos_demo.so -selfSign 1
如果你自己拷贝了其他三方库 .so,也一起签名。
运行
在 DevEco Studio 中:
Sync Project
Build Hap
Run
成功标志:
鸿蒙 PC 上出现一个窗口,显示 Hello Qt on HarmonyOS PC

如果这里成功,说明:
Qt runtime 可以加载
HAP 壳工程可以运行
你的业务 .so 可以被鸿蒙应用加载
到这里就可以开始本章的NitroShare适配工作了。
1. 阶段 1:环境
服务器上的 Qt-OHOS 工具链早就在 /opt/qt-ohos/qt-5.12.12-ohos/ 里就绪,OHOS LLVM 在 /root/ohos-sdk/ohos-sdk/linux/native/,本次完全不需要新装环境:
[root@VM-0-13-opencloudos ~]# ls /opt/qt-ohos/qt-5.12.12-ohos/qt-5.12.12-ohos/lib/cmake/Qt5/
Qt5Config.cmake Qt5ConfigVersion.cmake Qt5ModuleLocation.cmake
[root@VM-0-13-opencloudos ~]# ls /opt/qt-ohos/qt-5.12.12-ohos/qt-5.12.12-ohos/lib/libQt5{Core,Gui,Widgets,Network,Svg}.so
libQt5Core.so libQt5Gui.so libQt5Network.so libQt5Svg.so libQt5Widgets.so
[root@VM-0-13-opencloudos ~]# which moc-qt5 uic-qt5 rcc-qt5 lupdate-qt5 lrelease-qt5
/usr/bin/moc-qt5 /usr/bin/uic-qt5 /usr/bin/rcc-qt5 /usr/bin/lupdate-qt5 /usr/bin/lrelease-qt5
[root@VM-0-13-opencloudos ~]# ls /root/ohos-sdk/ohos-sdk/linux/native/llvm/bin/aarch64-unknown-linux-ohos-clang*
aarch64-unknown-linux-ohos-clang aarch64-unknown-linux-ohos-clang++
[root@VM-0-13-opencloudos ~]# cmake --version | head -1
cmake version 3.26.5
⚠️ 关键点:Qt-OHOS 5.12.12 包里
bin/目录下的moc.exe / uic.exe / rcc.exe是 Windows 二进制(.exe后缀,且来自 windows-backup);本系列前 4 篇用 qmake 时通过把 mkspec 的host_bins.path指向 Linux 系统包的/usr/bin/moc-qt5解决。CMake 路径不同——见 §3 toolchain 那一行set(Qt5_MOC_EXECUTABLE /usr/bin/moc-qt5 CACHE FILEPATH ... FORCE)。

2. 阶段 2:下载源码 + 项目侧写
[root@VM-0-13-opencloudos ~]# mkdir -p /root/NitroShareGOGO && cd /root/NitroShareGOGO
[root@VM-0-13-opencloudos NitroShareGOGO]# curl -sL -o nitroshare.tar.gz \
https://codeload.github.com/nitroshare/nitroshare-desktop/tar.gz/refs/tags/0.3.4
[root@VM-0-13-opencloudos NitroShareGOGO]# tar xzf nitroshare.tar.gz
[root@VM-0-13-opencloudos NitroShareGOGO]# du -sh nitroshare-desktop-0.3.4/
1.4M nitroshare-desktop-0.3.4/
源码侧写:
[recon] 子目录:cmake/ shell/ src/ src/api src/application src/bundle
src/data src/device src/dist src/icon src/mdns src/settings
src/transfer src/util
[recon] .cpp: 32 .h: 38 总计 70 个源文件
[recon] 构建系统:CMake (3 个 CMakeLists.txt)
[recon] 顶层 find_package:Qt5LinguistTools / Qt5Network / Qt5Widgets / Qt5Svg
[recon] 可选 find_package:qhttpengine / qmdnsengine(HTTP API + mDNS 发现)
[recon] 平台分支:if(WIN32) shell extension + WinExtras
if(APPLE) MacExtras
Linux: pkg_check_modules(APPINDICATOR gtk+-2.0 ...)

3. 阶段 3:写 OHOS toolchain.cmake(CMake 系列首篇核心)
CMake 工程的鸿蒙 PC 交叉编译,最干净的做法是一份独立 toolchain 文件——这是 qmake 系列没有的概念。把所有平台 / 编译器 / sysroot / Qt 路径 / host 工具的指定都放在一个 43 行的文件里:
# /root/NitroShareGOGO/ohos-toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# OHOS LLVM 工具链
set(OHOS_NATIVE "/root/ohos-sdk/ohos-sdk/linux/native")
set(OHOS_SYSROOT "${OHOS_NATIVE}/sysroot")
set(CMAKE_C_COMPILER "${OHOS_NATIVE}/llvm/bin/aarch64-unknown-linux-ohos-clang")
set(CMAKE_CXX_COMPILER "${OHOS_NATIVE}/llvm/bin/aarch64-unknown-linux-ohos-clang++")
set(CMAKE_AR "${OHOS_NATIVE}/llvm/bin/llvm-ar")
set(CMAKE_RANLIB "${OHOS_NATIVE}/llvm/bin/llvm-ranlib")
set(CMAKE_LINKER "${OHOS_NATIVE}/llvm/bin/ld.lld")
# Qt-OHOS 5.12.12 已交叉编译好的 sysroot
set(QT_OHOS_PREFIX "/opt/qt-ohos/qt-5.12.12-ohos/qt-5.12.12-ohos")
list(PREPEND CMAKE_PREFIX_PATH "${QT_OHOS_PREFIX}")
# 编译/链接旗标 —— -fPIC + 走 OHOS sysroot
set(_OHOS_FLAGS "--target=aarch64-linux-ohos --sysroot=${OHOS_SYSROOT} -fPIC")
set(CMAKE_C_FLAGS_INIT "${_OHOS_FLAGS}")
set(CMAKE_CXX_FLAGS_INIT "${_OHOS_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS_INIT "--target=aarch64-linux-ohos --sysroot=${OHOS_SYSROOT}")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "--target=aarch64-linux-ohos --sysroot=${OHOS_SYSROOT}")
# CMake 找 lib/header 时,target 走 sysroot;package 走 host
set(CMAKE_FIND_ROOT_PATH ${OHOS_SYSROOT} ${QT_OHOS_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
# ★ 关键:Qt5 host 工具的 .exe 探测会失败(Windows 包),
# 强制走 Linux 系统 Qt5 开发包提供的 *-qt5
set(Qt5_MOC_EXECUTABLE /usr/bin/moc-qt5 CACHE FILEPATH "host moc" FORCE)
set(Qt5_UIC_EXECUTABLE /usr/bin/uic-qt5 CACHE FILEPATH "host uic" FORCE)
set(Qt5_RCC_EXECUTABLE /usr/bin/rcc-qt5 CACHE FILEPATH "host rcc" FORCE)
set(Qt5_LUPDATE_EXECUTABLE /usr/bin/lupdate-qt5 CACHE FILEPATH "host lupdate" FORCE)
set(Qt5_LRELEASE_EXECUTABLE /usr/bin/lrelease-qt5 CACHE FILEPATH "host lrelease" FORCE)
💡 5 个隐藏要点(CMake 老手都未必全踩过):
CMAKE_*_FLAGS_INIT而非CMAKE_*_FLAGS:前者只在 toolchain 加载时初始化一次,后者会被项目 CMakeLists 覆盖。--target=aarch64-linux-ohos必须同时出现在编译和链接阶段,否则链接器走 host gcc。CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH让find_package(Qt5...)既能扫 OHOS sysroot 里的 Qt5Config.cmake,又能扫 host /usr。Qt5_MOC_EXECUTABLE等CACHE ... FORCE三连:FORCE 必须有,否则 Qt5LinguistToolsConfig 在 find_package 时会被自动重置成.exe后缀。- CMake 会智能继承 toolchain 里的所有
_INIT变量到子目录的 add_subdirectory,不需要在每个 CMakeLists 重复指定。
4. 阶段 4:CMake 工程剥离 —— 顶层 92→39 行 / src 288→102 行
NitroShare 上游 CMakeLists 包了大量 Win32 / Apple / Linux-Gnome 平台分支以及 qhttpengine、qmdnsengine 两个可选依赖。鸿蒙 PC 上全部不需要,剥到只剩 Qt5 四件套:
4.1 顶层 CMakeLists(92 → 39 行)
--- CMakeLists.txt.bak
+++ CMakeLists.txt
-find_package(Qt5LinguistTools 5.1 REQUIRED)
-find_package(Qt5Network 5.1 REQUIRED)
-find_package(Qt5Widgets 5.1 REQUIRED)
-find_package(Qt5Svg 5.1 REQUIRED)
-
-if(WIN32)
- find_package(Qt5WinExtras)
-endif()
-if(APPLE)
- find_package(Qt5MacExtras)
-endif()
-find_package(qhttpengine NAMES qhttpengine QHttpEngine QUIET)
-find_package(qmdnsengine QUIET)
-if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
- find_package(PkgConfig)
- pkg_check_modules(APPINDICATOR gtk+-2.0 appindicator-0.1 libnotify)
-endif()
+find_package(Qt5LinguistTools 5.1 REQUIRED)
+find_package(Qt5Network 5.1 REQUIRED)
+find_package(Qt5Widgets 5.1 REQUIRED)
+find_package(Qt5Svg 5.1 REQUIRED)
+# 平台分支 / 可选依赖 全部剥离
4.2 src/CMakeLists.txt(288 → 102 行)+ ★ app→library
最核心的改造一行:把 add_executable(... WIN32 MACOSX_BUNDLE) 改成 add_library(... SHARED),这样产物从 ELF executable 变成可被 dlopen 的 ELF shared object,鸿蒙 HAP 加载器才能识别。
-add_executable(nitroshare WIN32 MACOSX_BUNDLE ${SRC} ${UI} ${QRC})
+add_library(nitroshare SHARED ${SRC} ${UI} ${QRC})
+add_dependencies(nitroshare qm)
+set_target_properties(nitroshare PROPERTIES
+ OUTPUT_NAME "nitroshare"
+ PREFIX "lib"
+)
target_link_libraries(nitroshare Qt5::Widgets Qt5::Network Qt5::Svg)
-if(Qt5WinExtras_FOUND) ... endif()
-if(Qt5MacExtras_FOUND) ... endif()
-if(qhttpengine_FOUND) ... endif()
-if(qmdnsengine_FOUND) ... endif()
-if(APPINDICATOR_FOUND) ... endif()
-include(GNUInstallDirs)
-install(TARGETS nitroshare ...)
🛑 首坑:第一次试时我顺手加了
SOVERSION ""和VERSION "",结果 SONAME 变成libnitroshare.so.(带个孤零零的点),鸿蒙加载器直接拒绝。正确做法是干脆不设这两个属性,让 CMake 默认产libnitroshare.so。

5. 阶段 5:第一次 cmake configure —— 一次过 ⭐
[root@VM-0-13-opencloudos build]# cmake -G "Unix Makefiles" \
-DCMAKE_TOOLCHAIN_FILE=/root/NitroShareGOGO/ohos-toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release ..
-- The C compiler identification is Clang 15.0.4
-- The CXX compiler identification is Clang 15.0.4
-- Detecting C compiler ABI info - done
-- Check for working C compiler:
/root/ohos-sdk/ohos-sdk/linux/native/llvm/bin/aarch64-unknown-linux-ohos-clang - skipped
-- Check for working CXX compiler:
/root/ohos-sdk/ohos-sdk/linux/native/llvm/bin/aarch64-unknown-linux-ohos-clang++ - skipped
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /root/NitroShareGOGO/.../build
0.2 秒 Configuring done——比前 4 篇 qmake 任意一篇都快。CMake + 干净 toolchain 的优势在这里看得很清楚。
6. 阶段 6:试编译 —— 一次大坑(QT_NO_SSL)+ 一次小坑(QStyle)
6.1 第 1 次 build:error: unknown type name 'QSslConfiguration'(共 7 个 error)
src/transfer/transfer.h:58:14: error: unknown type name 'QSslConfiguration'
Transfer(QSslConfiguration *configuration, TransferModel::Direction direction);
^
src/transfer/transferreceiver.h:40:22: error: unknown type name 'QSslConfiguration'
src/transfer/transfersender.h:42:20: error: unknown type name 'QSslConfiguration'
src/transfer/transfermodel_p.h:54:5: error: unknown type name 'QSslConfiguration'
... 7 errors generated.
奇怪——#include <QSslConfiguration> 明明有写,头文件也确实存在 qsslconfiguration.h。直接打开看:
// /opt/qt-ohos/.../include/QtNetwork/qsslconfiguration.h
60: #include <QtNetwork/qssl.h>
65: #ifndef QT_NO_SSL // ← ★ 整个类被这个守卫包了
66: QT_BEGIN_NAMESPACE
...
82: class Q_NETWORK_EXPORT QSslConfiguration
83: { ... };
210: #endif // QT_NO_SSL
root cause:Qt-OHOS 5.12.12 这一版编译时 configure -no-openssl,所以 QT_NO_SSL 被定义,整段 SSL 类都被预处理掉了。
两条路:
- ❌ 给 Qt-OHOS 加 OpenSSL 重编 → 几个小时
- ✅ NitroShare 源码层面剥 SSL——SSL 在 NitroShare 是可选路径:
Transfer::Transfer(QSslConfiguration *configuration, ...)当configuration == nullptr时直接走明文QTcpSocket。剥 SSL 就是把签名里的QSslConfiguration *改成void *,永远传 nullptr。
写一个小脚本 patch_ssl_strip.sh,30 秒内一次性改 8 个文件:
[root@... src]# bash patch_ssl_strip.sh
transfer.cpp patched
transfermodel.cpp patched
[before patch] QSsl 出现 39 次(8 个 .h/.cpp)
QSslSocket(11) QSslConfiguration(11) QSslCertificate(9)
QSslKey(7) QSslError(5) QSsl(4)
[after patch] transfer/ 下 QSsl 残留 0 ✓

6.2 第 2 次 build:incomplete type 'QStyle' named in nested name specifier
src/application/splashdialog.cpp:40:17:
error: incomplete type 'QStyle' named in nested name specifier
setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, ...));
^~~~~~~~
qapplication.h:58:7: note: forward declaration of 'QStyle'
class QStyle;
^
简单——#include <QApplication> 只前向声明了 QStyle,要用静态方法得 #include <QStyle>。一行 sed:
sed -i '0,/^#include <QApplication>/s//#include <QApplication>\n#include <QStyle>/' \
src/application/splashdialog.cpp
6.3 第 3 次 build —— 一次过
[ 17%] Built target nitroshare_autogen ← AUTOMOC 走 /usr/bin/moc-qt5 OK
[ 30%] Building application/splashdialog.cpp.o
[ 32%] Building transfer/acceptdialog.cpp.o
[ 38%] Building main.cpp.o
[ 40%] Building qrc_resource.cpp.o
[ 42%] Building qrc_resource_qm.cpp.o
1 warning generated. ← 仅 "delete void*" 一个无害 warning
[ 44%] Linking CXX shared library ../out/libnitroshare.so
[100%] Built target nitroshare
0 errors,1 warning(无影响),8 分钟内 3 次 build 通关。

7. 阶段 7:5 项最终验证 ⭐⭐⭐
每篇都要的"生死门"。NitroShare 这套 5 项验证如下:
[1] file:
libnitroshare.so: ELF 64-bit LSB shared object, ARM aarch64,
version 1 (SYSV), dynamically linked, with debug_info, not stripped
[2] size:
-rwxr-xr-x 1 root root 587K May 24 23:47 libnitroshare.so
[3] T main:
000000000007035c T main ← ✅ 鸿蒙 dlopen+dlsym("main") 能找到
[4] SONAME + NEEDED:
(NEEDED) libQt5Network.so
(NEEDED) libQt5Svg.so
(NEEDED) libQt5Widgets.so
(NEEDED) libQt5Gui.so
(NEEDED) libQt5Core.so
(NEEDED) libc++_shared.so
(NEEDED) libc.so
(SONAME) libnitroshare.so
[5] LOAD 段 4KB 对齐:
LOAD 0x0000000000000000 ... R 0x1000 ← 4KB ✓
LOAD 0x000000000004b3d4 ... R E 0x1000
LOAD 0x0000000000072900 ... RW 0x1000
LOAD 0x0000000000076400 ... RW 0x1000
5 项全绿,**libnitroshare.so = 587 KB*

8. 阶段 8:HAP 工程从 0 搭建 + 4 项自检全过

复用 GloggOhos 那套壳,做 4 件事:
# 1) 整壳复制
cp -R DemoOhos NitroShareOhos
# 2) 替换 .so(删 glogg 的,放 nitroshare 的;补 Svg)
rm NitroShareOhos/entry/libs/arm64-v8a/libglogg.so
cp NitroShareGOGO/artifacts/nitroshare-ohos-libs/libnitroshare.so \
NitroShareOhos/entry/libs/arm64-v8a/
cp QElectroTechOhos/entry/libs/arm64-v8a/libQt5Svg.so \
NitroShareOhos/entry/libs/arm64-v8a/
# 3) 删 glogg 专用 rawfile 资源
rm -rf NitroShareOhos/entry/src/main/resources/rawfile/glogg
# 4) 改 4 个文件 7 处文案
# - AppScope/app.json5 bundleName → com.example.nitroshareohos
# - AppScope/.../element/string.json "glogg" → "NitroShare"
# - entry/.../element/string.json 3 处 glogg 文案 → NitroShare
# - entry/.../common/QtAppConstants.ets libglogg.so→libnitroshare.so + LOG_TAG
4 项自检:
[check 1] bundleName: com.example.nitroshareohos ✅
[check 2] glogg/Glogg/GLOGG 残留: 0 ✅
[check 3] libnitroshare.so 已就位: 587 KB ✅
[check 4] APP_LIBRARY_NAME: libnitroshare.so + NitroShareOhos ✅
entry/libs/arm64-v8a/ 最终就位的 12 个 .so:
libQt5Concurrent.so 41 K libQt5Svg.so 2.4 M
libQt5Core.so 8.7 M libQt5Widgets.so 9.1 M
libQt5Gui.so 8.2 M libQt5Xml.so 364 K
libQt5Network.so 2.0 M libc++_shared.so 1.2 M
libQt5OhosExtras.so 605 K libnitroshare.so 587 K ← 应用主体
libQt5PrintSupport.so 529 K libqohos.so 17 M ← Qt-OHOS 平台胶水
总 HAP 体积约 51 MB(其中 50 MB 全是 Qt5 + libqohos,应用本身只有 587 KB——这才是 CMake + 极简依赖的真正威力)。

9. 阶段 9:真机调试 —— 鸿蒙沙箱网络限制 + 模态错误框假性闪退
这一章是整个系列里最有再用价值的一节——它不属于编译期,而是发生在 HAP 装机首跑那一刻。它给所有"带网络服务的 Qt 桌面应用 → 鸿蒙 PC"的后续移植,留了一份可直接照抄的模板。
9.1 症状
HAP 4 项自检全过、▶ Run 也成功推到机器,但 splash 一闪即逝、应用立即退出。仔细抓帧才看到中间一闪而过的模态错误框:

点 OK 后整个 app 直接退出——外观上完全是"启动闪退",但实际上 app 是被自己弹出的对话框关掉的。
9.2 根因链——5 步推到 setQuitOnLastWindowClosed
挖一遍源码,整条链路如下:
main.cpp:84 Application *nitroshare = new Application;
↓
application.cpp:54+ 构造函数里 connect(&mTransferServer, &TransferServer::error,
this, &Application::notifyError)
application.cpp:90 mTransferServer.start();
↓
TransferServer::start() → mServer.listen(QHostAddress::Any, 40818)
↓
✗ 鸿蒙沙箱拒绝 bind 任意 TCP 端口
↓
emit error("Unable to listen on port 40818")
↓
application.cpp::notifyError(const QString &message)
{
QMessageBox::critical(nullptr, tr("Error"), message); ← ★ 元凶
}
↓
模态对话框抢焦点,splash 还没建起来 / 被压在底下
↓
用户点 OK → 关闭 QMessageBox
↓
QApplication::setQuitOnLastWindowClosed(true) 默认行为触发
↓
app.exec() 返回 → 进程退出
关键洞察:错误信号本身只是"端口被拒"这种可恢复故障,但 NitroShare 的 notifyError 把它弹成了模态框——再叠加 Qt 默认的"最后一个窗口关闭就退应用"——一个网络小故障升级成了"启动闪退"。
9.3 为什么不修 listen 本身
直觉的反应是"那我加个 ohos.permission.INTERNET 让它能 listen 不就行了?"——不行。鸿蒙 PC 的应用沙箱对任意端口的 TCP/UDP listen 有平台层限制:
| 路径 | 鸿蒙的态度 |
|---|---|
走鸿蒙原生 @ohos.net.socket(ArkTS) |
✅ 允许 |
| 走 musl libc 的 raw socket(Qt 走的就是这条路) | ❌ 拒绝 |
加 ohos.permission.INTERNET |
对 ArkTS 有效,对 Qt 走的 musl 路径不解决问题 |
也就是说,这是平台层面的限制,不是 NitroShare 的 bug。正确的工程姿态是 —— 让 listen 失败时优雅降级,而不是去硬怼平台。
9.4 修复 ⭐ 5 行代码 + 1 行 #include
打开 src/application/application.cpp,做两处改动:
#include <QApplication>
+#include <QDebug>
#include <QMessageBox>
...
void Application::notifyError(const QString &message)
{
- QMessageBox::critical(nullptr, tr("Error"), message);
+ // OHOS PC: do NOT pop up a modal QMessageBox here.
+ //
+ // On HarmonyOS PC the application sandbox forbids binding several
+ // reserved ports (e.g. TCP 40818 / UDP 40816), so TransferServer::start()
+ // legitimately fails and emits this error during normal startup.
+ // A modal dialog at this point grabs the focus, blocks the splash, and
+ // (because setQuitOnLastWindowClosed(true)) makes the app quit when the
+ // user dismisses it -> looks exactly like a startup crash.
+ //
+ // Demote the error to a warning log; the app keeps running with file
+ // transfer disabled, which is fine for a single-device demo.
+ qWarning() << "NitroShare:" << message;
}
效果:
- ✅ 端口起不来时不弹模态框,只在 hilog 里看到
W qWarning: NitroShare: "Unable to listen on port 40818" - ✅ Splash / 主界面正常显示,
setQuitOnLastWindowClosed不会被错误框关闭事件意外触发 - ✅ 文件传输功能"静默禁用"——单机 demo 场景下这恰好是想要的行为;如果未来真有两台设备测试,再讨论是否要打通鸿蒙原生 socket 桥接
9.5 重编 + 同步 + 4 项验证
[root@VM ~]# cd /root/NitroShareGOGO/nitroshare-desktop-0.3.4/build
[root@VM build]# rm -f src/CMakeFiles/nitroshare.dir/application/application.cpp.o out/libnitroshare.so
[root@VM build]# make -j4 nitroshare 2>&1 | tail -3
[ 96%] Building CXX object src/CMakeFiles/nitroshare.dir/application/application.cpp.o
[ 98%] Linking CXX shared library ../out/libnitroshare.so
[100%] Built target nitroshare
[root@VM build]# ls -lh out/libnitroshare.so
-rwxr-xr-x 1 root root 891K out/libnitroshare.so ← 587K → 891K(带额外 debug_info)
[root@VM build]# llvm-nm -D out/libnitroshare.so | grep ' T main'
00000000000b9a84 T main ← ✅ 入口仍在
# 拉回本地 + 同步两份 .so(本地仓库 + HAP 工程)
$ scp root@SERVER:/root/.../out/libnitroshare.so NitroShareOhos/entry/libs/arm64-v8a/
$ cp NitroShareOhos/entry/libs/arm64-v8a/libnitroshare.so NitroShareGOGO/artifacts/nitroshare-ohos-libs/
$ md5 -q NitroShareOhos/.../libnitroshare.so NitroShareGOGO/artifacts/.../libnitroshare.so
85c09e47c8b...
85c09e47c8b... ← ✅ 两侧 md5 完全一致

9.6 通用价值——一份"鸿蒙沙箱网络限制"模板
这一节给后续移植留了一份可直接照抄的模板,适用对象是:
任何在启动期会主动 bind/listen TCP/UDP 端口的 Qt 桌面应用——例如 KDE Connect、Syncthing GUI、qBittorrent、qmmp、多数局域网工具。
抓出三个特征指纹:
| 指纹 | 应用代码典型形态 |
|---|---|
① 入口构造里 server.listen(...) |
Application::Application() / MainWindow::MainWindow() 里直接 listen |
| ② 错误通过信号回报上层 | connect(&server, &XxxServer::error, this, &App::notifyXxx) |
| ③ notifyXxx 里弹模态框 | QMessageBox::critical/warning(...) |
只要看到这三个指纹同时出现,就可以直接照抄本节的修法:把 QMessageBox::critical 改成 qWarning() <<——5 行代码 + 1 行 #include <QDebug>,全部搞定。

10. 收尾感受
- 可执行→共享库 —— qmake
TEMPLATE = lib + CONFIG += shared/ CMakeadd_library(... SHARED),导出T main,HAP 加载器dlopen + dlsym("main")入口。 - host 工具链 —— 系统
moc-qt5/uic-qt5/rcc-qt5(/usr/bin),强制覆盖 Qt-OHOS 包里的.exe。 - 依赖剥离 vs 内嵌 —— 能剥就剥(KF5、appindicator、可选 SSL);不能剥就内嵌(sqlite3 amalgamation)。
- CMake 走 toolchain file,qmake 走 mkspec patch —— 两条不同的路径,但目的一样:让 host 工具走 Linux Qt5、target 走 OHOS Qt5。
- 产物 5 项验证 + HAP 4 项自检 —— 不验产物盲目装 HAP,调到一半的时间都在排查。
- 启动期错误模态框一律降级日志(NitroShare 新增) —— 鸿蒙沙箱有大量隐性限制(端口 listen、文件路径、麦克风/相机),任何
QMessageBox::critical出现在构造期 / 启动期都极其危险,会被setQuitOnLastWindowClosed捎带退出整个 app。统统改成qWarning() <<。
更多推荐



所有评论(0)