【开源软件移植】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.exeWindows 二进制.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 老手都未必全踩过):

  1. CMAKE_*_FLAGS_INIT 而非 CMAKE_*_FLAGS:前者只在 toolchain 加载时初始化一次,后者会被项目 CMakeLists 覆盖。
  2. --target=aarch64-linux-ohos 必须同时出现在编译和链接阶段,否则链接器走 host gcc。
  3. CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTHfind_package(Qt5...) 既能扫 OHOS sysroot 里的 Qt5Config.cmake,又能扫 host /usr。
  4. Qt5_MOC_EXECUTABLECACHE ... FORCE 三连:FORCE 必须有,否则 Qt5LinguistToolsConfig 在 find_package 时会被自动重置成 .exe 后缀。
  5. 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. 收尾感受

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

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

更多推荐