欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/

项目开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_LANDrop

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

环境搭建参考文章:https://blog.csdn.net/weixin_52908342/article/details/161343743

这篇文章记录的是 LANDrop 在 OpenHarmony / HarmonyOS PC 环境中的一次完整适配过程。和很多 Electron 项目不同,LANDrop 原始开源工程本体是 C++ / Qt Widgets 桌面应用,核心能力包括局域网设备发现、加密握手、文件发送、文件接收、传输进度、托盘入口和设置项。

本次适配的目标不是把另一个 Qt 项目复制进来,也不是把界面用 ArkUI 重写一遍,而是借鉴已有 Qt for Harmony 的工程壳思路:在鸿蒙侧新建一个 HAP native 外壳,把原有 LANDrop C++ / Qt Widgets 代码编译成 libentry.so,再通过 Qt for OpenHarmony 的 QPA 插件运行在鸿蒙 PC 窗口里。

最终当前状态:

  • 鸿蒙 PC 端 LANDrop 可以启动并显示完整主界面。
  • 首页、设置页、文件选择、文本发送、刷新设备等按钮可以响应。
  • 鸿蒙端和 Mac 端处于同一 Wi-Fi 时可以互相发现。
  • 鸿蒙端可以向 Mac LANDrop 发送文件。
  • Mac LANDrop 也可以向鸿蒙端发送文件。
  • Mac 向鸿蒙发送时曾经出现的 reply was never sentError: write EPIPE、鸿蒙端闪退问题已经修复。

在这里插入图片描述

一、项目基线:LANDrop 不是一个普通页面应用

本次使用的工程目录为:

~/XM/LANDrop-master

主要目录结构如下:

LANDrop-master/
├── LANDrop/              # 原始 LANDrop C++ / Qt Widgets 源码
│   ├── main.cpp
│   ├── landropwindow.cpp
│   ├── discoveryservice.cpp
│   ├── crypto.cpp
│   ├── filetransfersession.cpp
│   ├── filetransfersender.cpp
│   ├── filetransferreceiver.cpp
│   └── ...
└── harmony_pc/           # 本次新增的鸿蒙 PC HAP 工程
    ├── AppScope/
    ├── build-profile.json5
    ├── entry/
    │   ├── build-profile.json5
    │   └── src/main/
    │       ├── cpp/CMakeLists.txt
    │       ├── ets/
    │       └── module.json5
    └── third_party/
        ├── libsodium/
        └── secp256k1/

这里要先明确一个容易误判的点:Mac 上现在下载到的 LANDrop 成品是 2.7.2 版本,界面和协议都更接近新版 Electron / Flutter 体系;而 GitHub 上公开的 C++ / Qt 源码更接近早期 0.4.x 时代。也就是说,本次适配如果只把 Qt 代码编译进 HAP,最多只能做到“鸿蒙端自己跑起来”。要想让它和 Mac 成品 LANDrop 互通,还必须补齐新版 LANDrop v2 的发现和传输协议。

所以整个适配可以分成五条线:

  1. 建立鸿蒙 PC HAP 工程,让 Qt Widgets 程序可以启动。
  2. 调整主界面和设置页,让鸿蒙窗口里能直接操作。
  3. 兼容 LANDrop v2 设备发现协议,让 Mac 和鸿蒙互相看见。
  4. 兼容 LANDrop v2 加密握手和消息协议,让文件可以真正传输。
  5. 修复 Mac 发鸿蒙时的接收路径、错误弹窗和闪退问题。

二、总体方案:借鉴 Qt for Harmony 工程壳,不复制其他项目

之前做过 Phototonic 的 Qt 图片查看器适配,它给了一个很重要的方向:Qt Widgets 程序迁移到鸿蒙 PC 时,可以保留 C++ / Qt 业务代码,通过鸿蒙 HAP native 工程承载 Qt 应用。

但这次并不是把 Phototonic 复制到 LANDrop。实际做法是:

  • harmony_pc/ 是 LANDrop 自己的鸿蒙工程。
  • CMakeLists.txt 编译的是 LANDrop/ 目录下 LANDrop 自身的 C++ 源码。
  • UI、发现、传输、加密、文件保存逻辑都继续落在 LANDrop 自己的类里。
  • 只复用“Qt for Harmony + HAP + XComponent + QPA 插件”这一类工程组织思路。

鸿蒙侧核心构建文件是:

harmony_pc/entry/src/main/cpp/CMakeLists.txt

这个文件里做了几件关键事情:

  • 查找 Qt for Harmony SDK。
  • 查找并链接 libsodium
  • 构建 third_party/secp256k1 静态库。
  • LANDrop/*.cpp.ui.qrc 纳入 entry 动态库。
  • 定义 OPENHARMONY,用于处理鸿蒙平台差异。
  • 链接 Qt5::CoreQt5::GuiQt5::NetworkQt5::WidgetsQt5::OhExtrasQt5::QOpenHarmonyPlatformIntegrationPlugin

最终产物是:

libentry.so

这个 .so 随 HAP 一起打包,鸿蒙 Ability 启动后由 Qt for OpenHarmony 平台插件接管 Qt 应用的窗口和事件。

在这里插入图片描述

三、第一关:从白屏到 Qt 主窗口

最开始鸿蒙端启动后出现过白屏。白屏的原因不是 LANDrop 业务逻辑没有运行,而是桌面 Qt 程序的窗口模型和鸿蒙 Ability / XComponent 模型没有完全接上。

传统桌面 Qt 应用通常从 main.cpp 创建 QApplication,再创建主窗口。鸿蒙 PC 侧则要先由 ArkTS 的 Ability 创建窗口,再让 Qt for OpenHarmony 平台插件把 Qt 窗口绘制到鸿蒙窗口上。这个链路里只要 Qt 平台插件、native library、Ability 启动顺序或窗口尺寸有一环不对,用户看到的就可能是白屏。

本次处理后,主入口保持 LANDrop 的 Qt 应用模型,同时在鸿蒙侧通过 HAP 工程启动。关键点包括:

  • 保留 LANDrop/main.cpp 作为 Qt 程序入口。
  • 在 CMake 中把 main.cpp 和相关 Widgets 源码一起编进 libentry.so
  • 使用 Qt for OpenHarmony 的 QPA 插件承接窗口绘制。
  • OPENHARMONY 分支里处理桌面托盘和窗口显示差异。
  • 将原先偏托盘型的桌面入口调整为鸿蒙 PC 上更直观的主窗口入口。

LANDrop 原来在桌面端更偏“托盘 + 弹窗”的使用方式。鸿蒙 PC 适配时,如果仍然完全依赖托盘入口,会出现用户打开应用后不知道从哪里发送文件、设置按钮无响应感强、窗口状态不符合 PC 应用预期的问题。因此这次对 LANDrop/landropwindow.cppLANDrop/landropwindow.h 做了更面向鸿蒙 PC 的主窗口整理:

  • 首页展示设备发现结果。
  • 提供发送文件和发送文本入口。
  • 提供刷新设备入口。
  • 设置页展示设备名称、下载目录、是否可发现等配置。
  • 发送和设置相关按钮直接绑定 Qt signal / slot。

这一步解决的是“应用能看见、能点、能进入主要流程”的问题。

四、第二关:构建、安装和启动

鸿蒙工程位于:

~/XM/LANDrop-master/harmony_pc

构建命令如下:

cd ~/XM/LANDrop-master/harmony_pc

DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
HOS_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleHap --no-daemon --no-type-check --stacktrace

安装 HAP:

/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc install -r \
~/XM/LANDrop-master/harmony_pc/entry/build/default/outputs/default/entry-default-signed.hap

启动应用:

/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa start \
-a EntryAbility -b app.landrop.harmony -m entry

调试时常用日志命令:

/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell hilog -x

这里最关键的是构建链路要能同时找到 Qt、libsodium 和 secp256k1。CMake 中已经做了检查,如果 libsodiumsecp256k1 不存在,会直接中断并给出明确错误。这样比运行时才发现加密库缺失更容易定位。

在这里插入图片描述

五、第三关:为什么 Mac 和鸿蒙一开始互相发现不到

LANDrop 的核心体验是同一局域网内自动发现设备。最开始遇到的问题是:Mac 上开着 LANDrop,鸿蒙上也开着 LANDrop,两个设备在同一个 Wi-Fi 下,但互相搜索不到。

这个问题不能只从“网络通不通”看。LANDrop 设备发现有自己的协议格式和端口约定。早期开源 Qt 版本和 Mac 端 2.7.2 成品之间有明显差异:

  • 发现 UDP 端口:52637
  • 发现组播地址:239.192.52.63
  • Mac 端传输服务端口:常见为 53268
  • 鸿蒙端服务端口:运行时动态分配,例如 44921

早期协议里主要字段是:

{
  "device_name": "xxx",
  "device_type": "xxx",
  "port": 44921
}

而 Mac LANDrop v2 还需要类似这些字段:

{
  "device_info": {
    "name": "localhost",
    "type": "openharmony"
  },
  "device_name": "localhost",
  "device_type": "openharmony",
  "discoverable": true,
  "port": 44921,
  "public_key": "base64-public-key",
  "request": false,
  "supported_versions": ["v2"]
}

因此本次改了 LANDrop/discoveryservice.cppLANDrop/discoveryservice.h

  • 固定使用 LANDrop 发现端口 52637
  • 加入组播地址 239.192.52.63
  • 同时发送 broadcast 和 multicast。
  • 收到 request=true 时主动回复本机信息。
  • 保留旧字段 device_namedevice_typeport,兼容旧客户端。
  • 增加 v2 字段 device_infosupported_versionspublic_keydiscoverable,兼容 Mac 端 LANDrop 2.7.2。

修完后,鸿蒙和 Mac 能互相出现在设备列表中,这才进入真正的文件传输阶段。

在这里插入图片描述

六、第四关:v2 加密握手和消息协议

设备能互相发现之后,并不代表文件一定能传。LANDrop 传输前还有加密握手和消息协商。

早期开源 Qt 版本的思路更接近:

  • 原始 Curve25519 密钥交换。
  • 发送简单 JSON 文件列表。
  • 接收方回一个简单响应。
  • 文件数据按原有会话加密发送。

Mac LANDrop 2.7.2 的传输协议则已经变成 v2 链路,大致包括:

  • 2 字节 big-endian 长度前缀记录。
  • secp256k1 身份公私钥。
  • libsodium crypto_kx 临时会话密钥交换。
  • 对临时 ECDHE 公钥做 blake2b-256 摘要。
  • 使用 ECDSA 签名验证身份。
  • 使用 crypto_aead_chacha20poly1305_ietf 加密后续消息。
  • 消息层使用 v2 JSON envelope。

本次主要补齐了这些文件:

LANDrop/crypto.h
LANDrop/crypto.cpp
LANDrop/filetransfersession.h
LANDrop/filetransfersession.cpp
LANDrop/filetransfersender.h
LANDrop/filetransfersender.cpp
LANDrop/filetransferreceiver.h
LANDrop/filetransferreceiver.cpp

同时在鸿蒙工程里加入了:

harmony_pc/third_party/secp256k1
harmony_pc/third_party/libsodium

secp256k1 用静态库方式参与构建,CMake 中开启了这些定义:

SECP256K1_BUILD=1
USE_BASIC_CONFIG=1
USE_NUM_NONE=1
USE_FIELD_INV_BUILTIN=1
USE_SCALAR_INV_BUILTIN=1
USE_FIELD_10X26=1
USE_SCALAR_8X32=1

传输消息层重点兼容了这些类型:

supported_message_types
device_info
file_send_request
file_send_request_reply
ack
text_send

鸿蒙作为发送端时,流程大致是:

  1. 连接对方 TCP 端口。
  2. 完成 v2 加密握手。
  3. 发送 supported_message_types
  4. 交换 device_info
  5. 发送 file_send_request
  6. 等待 file_send_request_reply
  7. 对方接受后开始发送文件数据。

鸿蒙作为接收端时,流程大致是:

  1. 等待 Mac 连接鸿蒙端动态端口。
  2. 完成 v2 加密握手。
  3. 回复 Mac 的 supported_message_types
  4. 回复 Mac 的 device_info
  5. 解析 Mac 发来的 file_send_request
  6. 弹出接收确认窗口。
  7. 用户接受后发送 file_send_request_reply
  8. 创建本地文件并写入收到的数据。

这一步完成后,鸿蒙向 Mac 发送文件先跑通。

在这里插入图片描述

七、第五关:Mac 发鸿蒙时的 EPIPE 和鸿蒙闪退

最后一个问题也是最关键的问题:鸿蒙向 Mac 发送可以成功,但 Mac 向鸿蒙发送时,Mac 端报错,鸿蒙端甚至会闪退。

当时看到的典型现象有两类:

reply was never sent

以及:

Error: write EPIPE

鸿蒙侧崩溃日志中还能看到和 QDialog::setVisible(bool) 相关的栈,触发点在接收会话错误处理附近。这个现象表面上像是网络断了,实际上有三层原因叠在一起:

第一,Mac 作为发送端时,要求鸿蒙接收端按 v2 协议及时回复 file_send_request_reply。如果鸿蒙端在准备下载目录、弹窗确认或错误处理阶段提前异常,Mac 就会认为接收方没有按协议回复。

第二,鸿蒙端文件保存路径不能直接照搬桌面系统。桌面版默认下载目录在 macOS、Windows、Linux 上通常可写,但 OpenHarmony 上必须选择应用可写路径,并且在真正回复“接受”之前确认目录可创建、可写入。

第三,Qt for OpenHarmony 下嵌套 QMessageBox / QDialog 的时序要更小心。错误回调里如果同步弹出错误框、隐藏确认框、reject 当前传输窗口,可能触发窗口状态重入,导致闪退。

最终修复分几处落地:

7.1 下载目录选择改为鸿蒙可写路径

LANDrop/settings.cpp 中为 OPENHARMONY 增加了更稳妥的默认下载目录策略:

  • 优先尝试 QStandardPaths::DownloadLocation
  • 不可写时回退到 AppDataLocation
  • 再不行回退到 GenericDataLocation、home 或 temp。
  • 每次返回前用 .landrop-write-test 做写入探测。

这样可以避免接收文件时才发现目录不可写。

7.2 接收方在回复 accept=true 前先检查目录

LANDrop/filetransferreceiver.cpp 中的 FileTransferReceiver::respond() 做了调整:

  • 如果用户点击接受,先调用 prepareDownloadPath()
  • 目录不可创建或不可写时,向 Mac 回复 accept=false
  • 只有目录准备成功后才回复 accept=true 并进入 TRANSFERRING

这一步很关键。因为对 Mac 端来说,“接收方是否回复”是协议状态机的一部分。不能让鸿蒙端自己出错后直接断开,否则 Mac 端就会看到 EPIPE 或类似的发送失败。

7.3 文件名做安全化处理

FileTransferReceiver::safeOutputPath() 对 Mac 发来的文件名做了处理:

  • 把反斜杠统一成 /
  • 使用 QDir::cleanPath() 规整路径。
  • 去掉 ../ 这类上跳路径。
  • 绝对路径只取文件名。
  • 空文件名回退为 Received File

这样可以避免远端文件名包含目录穿越或不适合当前平台的路径格式。

7.4 弹窗和错误处理改为延迟执行

LANDrop/filetransferdialog.cpp 中把接收确认和错误处理放到 QTimer::singleShot(0, ...) 里延后到事件循环下一轮执行。并且在 OPENHARMONY 下避免在错误路径中再同步弹出 QMessageBox::critical

这样可以减少 Qt for OpenHarmony 下窗口状态重入导致的崩溃。最终表现就是:Mac 发鸿蒙时,鸿蒙端可以正常弹出接收确认,点击接受后正常写入文件,不再闪退。

在这里插入图片描述

八、这次适配改动的核心文件

本次适配集中在下面这些文件中:

harmony_pc/entry/src/main/cpp/CMakeLists.txt
harmony_pc/entry/src/main/ets/entryability/EntryAbility.ets
harmony_pc/entry/src/main/ets/pages/Index.ets
harmony_pc/entry/src/main/module.json5
harmony_pc/third_party/libsodium
harmony_pc/third_party/secp256k1

LANDrop/main.cpp
LANDrop/landropwindow.h
LANDrop/landropwindow.cpp
LANDrop/discoveryservice.h
LANDrop/discoveryservice.cpp
LANDrop/crypto.h
LANDrop/crypto.cpp
LANDrop/filetransfersession.h
LANDrop/filetransfersession.cpp
LANDrop/filetransfersender.h
LANDrop/filetransfersender.cpp
LANDrop/filetransferreceiver.h
LANDrop/filetransferreceiver.cpp
LANDrop/filetransferdialog.h
LANDrop/filetransferdialog.cpp
LANDrop/sendtodialog.h
LANDrop/sendtodialog.cpp
LANDrop/settings.h
LANDrop/settings.cpp
LANDrop/settingsdialog.h
LANDrop/settingsdialog.cpp

这里面最重要的不是某一个单点修改,而是把 UI、发现、加密、传输和文件写入这几条链路串起来。只修 UI,应用只是“能看”;只修发现,设备只是“能搜到”;只修握手,文件仍可能发不出去;只修发送端,Mac 发鸿蒙仍会崩。真正可用必须做到双向闭环。

九、为什么鸿蒙端 UI 和 Mac 端 UI 不完全一样

适配过程中还遇到一个直观问题:Mac 下载的 LANDrop 成品界面和鸿蒙端界面不完全一致,特别是设置页和部分按钮行为。

这个差异的原因在于两边基线不同:

  • Mac 成品是 LANDrop 2.7.2,界面和协议都属于新版。
  • 本次鸿蒙适配基于公开的 C++ / Qt Widgets 工程。
  • Qt Widgets 和新版 Mac 客户端的 UI 组件体系不同,不可能天然长得完全一样。

所以这次做法不是强行逐像素复刻 Mac 端 UI,而是优先保证鸿蒙端具备同等核心功能:

  • 能看到本机设备名。
  • 能设置下载目录。
  • 能开关可发现状态。
  • 能刷新和查看局域网设备。
  • 能选择文件发送。
  • 能接收 Mac 发来的文件。
  • 设置页按钮要有实际响应。

也就是说,UI 的目标是“鸿蒙 PC 上可用、功能完整、操作路径清晰”,同时尽量向 Mac 端使用习惯靠近。真正影响互通的不是按钮样式,而是 LANDrop v2 的发现和传输协议。

十、调试经验总结

这次适配可以总结成几个经验:

第一,Qt Widgets 迁移鸿蒙 PC,先解决窗口承载。只要主窗口还是白屏,后面的业务逻辑很难判断。

第二,局域网发现要同时看端口、组播、广播和 JSON 字段。两个设备在同一个 Wi-Fi 下,不代表协议一定能互相识别。

第三,跨版本互通不能只看开源仓库源码。LANDrop 官方 GitHub 仓库公开源码和当前 Mac 成品之间存在版本差异,因此要用 Mac 端真实行为反推 v2 协议。

第四,加密协议适配要完整。只补一个 public key 字段不够,后续的长度前缀记录、ECDHE、签名验证、AEAD 加密和 v2 envelope 都要对上。

第五,接收文件比发送文件更容易暴露平台差异。下载目录、文件名、写权限、错误弹窗和窗口生命周期都可能导致接收端失败。

第六,鸿蒙 PC 上的 Qt 弹窗时序要谨慎。遇到传输错误时,延迟到事件循环下一轮处理,通常比在回调里同步套弹窗更稳定。

十一、最终验证清单

最终验证时按下面顺序走一遍:

  1. 构建 HAP 成功。
  2. 安装 HAP 成功。
  3. 鸿蒙端 LANDrop 启动后不是白屏。
  4. 首页按钮、设置页按钮可以点击。
  5. Mac 和鸿蒙处于同一 Wi-Fi 后互相发现。
  6. 鸿蒙向 Mac 发送小文件成功。
  7. Mac 向鸿蒙发送小文件成功。
  8. 鸿蒙端接收完成后下载目录中能看到文件。
  9. 反复发送几次不闪退。
  10. 终端日志中不再出现 reply was never sentError: write EPIPE 这一类失败现象。

到这里,LANDrop 从一个桌面 Qt 局域网传输工具,已经基本完成了鸿蒙 PC 侧的可用适配。它不像普通页面应用那样只要解决窗口和资源路径,也不像纯 UI 工具那样只需要把界面跑起来。LANDrop 真正的难点在于“跨端互通”:鸿蒙端要能被 Mac 发现,也要能理解 Mac 的 v2 加密传输协议;既能作为发送端,也能作为接收端。双向传输都跑通之后,这次适配才算真正闭环。

Logo

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

更多推荐