【开发者开发鸿蒙pc的故事】我只是想把一张截图从 Mac 传到鸿蒙 PC——于是开始移植 NitroShare
我只是想把一张截图从 Mac 传到鸿蒙 PC——于是开始移植 NitroShare
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
一个开发者周末的傍晚故事。
没有什么"宏大叙事",就是一件被生活逼出来的小事。
一张截图卡了我 20 分钟
5 月 18 日,周日傍晚。
我在咖啡馆,桌上摊着两台电脑——Mac 在写 IronLog 的下一个版本,鸿蒙 PC 在跑测试。
中间有那么一刻,我在 Mac 上截了个图,想发到鸿蒙 PC 上看看那边的字体渲染。
就这么一个动作。
我先去想 AirDrop——鸿蒙 PC 不支持。
想微信文件传输——得在鸿蒙上登微信,但鸿蒙微信还没正式版桌面。
想浏览器 QQ 网页传——传是能传,但要扫码、要打开手机微信、要批准、要找接收文件夹……
插 U 盘——身边没带。
最后我用了最蠢的办法:把图发到自己的邮箱,登 Mac 邮箱,在鸿蒙 PC 上开浏览器再登一次邮箱,下载。
整个过程 20 多分钟。中间我喝完了一杯美式。
我盯着屏幕想——就这么一件事。
用 AirDrop 多少年了?
用 LocalSend 在 Mac 和 Win 之间互传多少年了?
这种"在自己的设备之间传个东西"的小动作,我已经 5 年没认真想过了。现在突然又要花 20 分钟想这件事——
这就是"换平台"的真实代价。
那天傍晚我把这事记在备忘录里,叫"鸿蒙 PC 缺一个 AirDrop"。
3 天以后,5 月 21 日,我开始动手。

为什么是 NitroShare
不是 LocalSend,也不是 SnapDrop。是 NitroShare。
理由很现实:
LocalSend 是 Flutter 写的。Flutter 在鸿蒙 PC 上的支持还不成熟,硬上是另一个 1-2 个月的工程。我手里没这个时间。
SnapDrop 是纯网页的(基于 WebRTC + WebSocket)。它要一个公网中转服务器,对于"两台同局域网设备直连"反而绕远路。
NitroShare 是个我没怎么用过的小工具——但它有三个对我特别合适的属性:
| 属性 | NitroShare 的样子 |
|---|---|
| 技术栈 | Qt5(C++) ✅ 我已经做过 4 个 Qt 项目了 |
| 体积 | 1.4 MB 源码 / 70 个 .cpp+.h ✅ 小得像一个练习题 |
| 构建 | CMake(前 4 个都是 qmake)✅ 顺便补上仓库这块空白 |
| 依赖 | Qt5Network/Widgets/Svg,无 KF5、无 sqlite、无 OpenGL ✅ |
| GitHub | 1.6k star、Nathan Osman 单人维护 ✅ |
我看了一眼 GitHub——上次提交是 2022 年。NitroShare 几乎停更了。但这不影响——0.3.4 稳定版功能完整,对一个"传文件"工具来说完整就够了。
我点了下载。
35 分钟的"过分顺利"
这是整个故事里最反讽的一段。
5 月 21 日晚上 9 点,我在腾讯云 OpenCloudOS 服务器上开干。Qt-OHOS 工具链早就配好了(在 IronLog 那次配齐),不用再装。
21:00 下载源码、解压、recon
21:08 写 CMake toolchain.cmake
21:15 第一次 configure - 报错(QSslConfiguration 找不到,Qt-OHOS 没编 OpenSSL)
21:18 剥离 SSL(在 CMakeLists 里把 ssl.cpp 排除,加 QT_NO_SSL 宏)
21:22 第二次 configure - 通过
21:28 ninja build - 一次链接通过
21:30 ls -lh 看 libnitroshare.so:587 KB
21:33 做 HAP 壳(套模板,把 libnitroshare.so 塞进 libs/arm64-v8a/)
21:35 4 项自检 ✅
从源码下载到 HAP 完成——35 分钟。

我那一刻是真的兴奋的。这是我做过的 5 个 Qt 移植项目里最快的一次。
之前最快是 glogg,21 分钟。
KDiff3 花了 1 小时(KF5 剥离很头疼)。
DiffPDF 花了 1.5 小时(poppler-qt5 是只大野兽)。
QElectroTech 花了 2 小时(KF5 + sqlite + moc 跨版本三重打击)。NitroShare——35 分钟。
我把 HAP 包推到设备上,▶ Run。
splash 一闪。
应用关了。
装上、点开、闪退
第一次看到这一幕我以为是误触关闭键。
再 Run 一次。splash 一闪。又关了。
第三次我盯着屏幕——确实是闪退。
我心里凉了一下。
35 分钟的"过分顺利"是有代价的——它换来了一个完全没有提示信息的闪退。
我拿出 hdc,开 hilog,过滤进程名:
hdc shell hilog | grep -E "nitroshare|cppcrash"
——没有 cppcrash。这不是 native 崩溃。
——但也没有任何明显的 ERROR 日志。
我又试了一次。这次我学聪明了——手机录屏对着鸿蒙 PC 屏幕,慢动作回放。
回放里我看到了它:
应用启动
↓
splash 出现 (蓝色背景 + NitroShare logo)
↓
[这里有一个东西闪过] ← 帧 1/30 秒
↓
splash 消失
↓
应用退出
那个"闪过的东西"——我把那帧暂停下来仔细看——是个模态对话框。
字看不清。我把视频放大,调亮,截图——
┌─────────── Error ───────────┐
│ Unable to listen on port │
│ 40818 │
│ │
│ [ OK ] │
└──────────────────────────────┘

我笑出声了。
顿悟:鸿蒙沙箱不让我 listen
那一刻我突然明白了。
NitroShare 启动时做的第一件事是 mServer.listen(QHostAddress::Any, 40818)——这是 NitroShare 自己的传输服务端口。它得 listen 才能接收别的设备发来的文件。
但鸿蒙 PC 的应用沙箱不允许应用 bind 任意 TCP/UDP 端口。
| 路径 | 鸿蒙的态度 |
|---|---|
鸿蒙原生 @ohos.net.socket(ArkTS) |
✅ 允许 |
| musl libc 的 raw socket(Qt 走的就是这条) | ❌ 拒绝 |
加 ohos.permission.INTERNET 权限 |
对 ArkTS 有效,对 Qt 走的 musl 路径不解决问题 |
也就是说——
这不是 NitroShare 的 bug。
这不是 Qt 的 bug。
这是鸿蒙桌面应用的平台级限制。
我盯着屏幕想了一会儿——
我做这个东西的初衷,是想在两台设备之间传文件。
但平台从根上就不让我 listen 端口。
那我做这个移植还有意义吗?
想了 5 分钟,我又想明白了——这件事的意义反而更大了。
因为:
- 即使 listen 失败,NitroShare 的"发送端"还是能用的——它向已知地址发文件不需要本地 listen
- 鸿蒙原生 socket API 走 ArkTS 是允许的——长期看是可以做一个 ArkTS 桥接的
- 现在最迫切的问题是"启动闪退"——一个网络小故障升级成了"启动闪退",这是工程问题,不是平台问题
让我看一下闪退是怎么来的。
5 步推到根因
我顺着 hilog 里能看到的零星线索,反推:
TransferServer::start()
↓
server.listen(Any, 40818)
↓
✗ 鸿蒙沙箱拒绝
↓
emit error("Unable to listen on port 40818")
↓
application.cpp::notifyError(const QString &message)
{
QMessageBox::critical(nullptr, tr("Error"), message); ← ★ 元凶
}
↓
模态对话框抢焦点
↓
用户(我)点 OK 关闭它
↓
QApplication::setQuitOnLastWindowClosed(true) 默认行为触发
↓
app.exec() 返回 → 进程退出
整条链最讽刺的地方:
- 鸿蒙拒绝 listen → 本来只是 “网络功能用不了” 这种可恢复故障
- 但 NitroShare 把它弹成了模态对话框
- 模态框被点掉时——splash 还没正式显示,主窗口还没建——它是 app 当时唯一的窗口
- 关掉它 →
setQuitOnLastWindowClosed默认触发 → 整个 app 退了
一个网络小故障升级成了启动闪退。
整条链长,但每一步都"看起来合理"——这是工程世界最坑的一种 bug。
5 行代码救活一切
凌晨 1 点 12 分。我打开 src/application/application.cpp,找到 notifyError。
#include <QApplication>
+#include <QDebug>
...
void Application::notifyError(const QString &message)
{
- QMessageBox::critical(nullptr, tr("Error"), message);
+ //
+ // On HarmonyOS PC the application sandbox forbids binding
+ // arbitrary ports. listen() failure here is a normal startup
+ // event, NOT a fatal error. A modal dialog grabs focus, blocks
+ // splash, and (via setQuitOnLastWindowClosed) makes the app quit
+ // when user dismisses it -> looks exactly like a crash.
+ //
+ // Demote to warning log; the app keeps running with file transfer
+ // disabled, which is fine for a single-device demo.
+ qWarning() << "NitroShare:" << message;
}
5 行有效代码(外加一行 #include 和一段注释)。
rm -f xxx.o && ninja——0.3 秒重编。
重新打 HAP,推到设备,▶ Run。
splash 出现。
主窗口出现。
应用没退。
我笑出声。
凌晨 1:14。从录屏发现模态框到看到主界面活下来——2 分钟。
我截了张图发到自己的备忘录里——「NitroShare on HarmonyOS PC 0.3.4 - 5 月 22 日 01:14 跑通」。

第二天早上的对话
第二天上午我在咖啡馆,前晚的同事也在。
我把鸿蒙 PC 摆出来打开 NitroShare:
“看,跑通了。”
他点开看了下:
“传文件能用吗?”
我说:“接收用不了——鸿蒙不让 listen。发送应该能用——但要测试得有第二台同款设备才能验。”
他想了一下:“那……不算完整功能?”
我说:“不算。”
他:“那你弄它干嘛。”
我喝了一口咖啡。想了几秒。
"嗯——
-
第一,技术答案出来了:‘鸿蒙 PC + Qt 写的局域网工具’ 能跑到什么程度,现在我知道边界在哪了。是平台层 listen 限制,不是 Qt 限制,不是 NitroShare 写得不好。这个边界本身就是社区想要的答案。
-
第二,5 行代码 + 模板已经留下了:以后任何 Qt 应用启动期
QMessageBox::critical引发的"假性闪退",都能照抄。KDE Connect / Syncthing GUI / qBittorrent / qmmp / 多数局域网工具——它们如果要做鸿蒙移植,都会撞上同一个坑。这一份 5 行 patch 模板,未来能省别人几小时排查。 -
第三,这个故事本身就是文章:‘一张截图传 20 分钟逼我移植 NitroShare’——这种’小痛点驱动技术决策’的故事,正是社区里现在缺的’有人味’的内容。"
他愣了一下,说:“你越来越像产品经理了。”
我笑:
"不是产品经理。
是用户。"

这件事让我想明白的几件事
1. 跨设备协作是平台软实力的试金石
鸿蒙 PC 单看应用质量、单看性能、单看界面,已经能用了。
但用户的世界从来不是单设备的。我每天至少在 3 台设备之间切换(Mac、鸿蒙 PC、手机)。“传一张截图要 20 分钟”——这就是单设备做得再好、跨设备做不好就会被吐槽的根本原因。
AirDrop 是 iPhone+Mac 黏度的底层粘合剂。它技术上没多神秘——但没有它你就是不舒服。
鸿蒙 PC 缺这个。NitroShare 这种社区项目不该是唯一答案——官方应该出一个跨鸿蒙设备的 AirDrop。
2. 平台限制不等于不能做
我一开始看到 listen 失败 三个字心都凉了。但想清楚之后发现:
- listen 失败 ≠ 应用没用——发送端还能工作
- 应用沙箱 ≠ 永远封死——ArkTS 那条路是允许的,未来可以做桥接
- 第一版 30% 功能 > 等到 100% 完美再发
很多人卡在"完美主义"上不动手。先让它跑起来、看到主窗口、写一篇博客记录下来——这就是下一个开发者要看的"路径已通"的证据。
3. 小工具反而是验证生态最锋利的探针
NitroShare 只有 1.4 MB 源码。但它撞出了鸿蒙 PC 应用沙箱网络限制这个平台层级的发现——比我之前做的 KDiff3(9.5 MB)、QElectroTech(17 MB)这种大块头都更有"探针价值"。
为什么?因为:
- 大项目的复杂性会遮蔽平台限制——你不知道是项目 bug 还是平台 bug
- 小项目的纯粹性反而把平台限制照得清清楚楚——587 KB 的二进制只做一件事,它撞墙就是平台撞墙
给后续移植者的建议:第 1 个不要选 VS Code 这种庞然大物。先选一个 1-2 MB 的小工具——它撞出来的边界对你后面规划更大项目有指导价值。
4. 5 行代码也能写一篇文章
我做完后写了一篇详细的踩坑文(《NitroShare 适配鸿蒙 PC 全流程实战》),其中第 9 节 “鸿蒙沙箱网络限制 + 模态错误框假性闪退”——就这 5 行代码 + 一段诊断逻辑——是整篇文章被最多人转的部分。
技术博客的价值不取决于代码量,取决于洞察的稀缺性。
5 行代码 + 一张错误框截图 + 一条 5 步推导链——比 5000 行重构代码更值钱。
对鸿蒙 PC 的三档期望
短期(半年内):
- 官方出一份"应用沙箱网络限制"白皮书 —— 哪些端口能 listen、哪些不能、ArkTS 桥接桌面 Qt 应用的官方姿势是什么。让开发者知道边界在哪,比"自己撞一遍"要省 80% 的时间。
中期(1-2 年内):
- 官方桌面 AirDrop / Quick Share——能在鸿蒙手机、鸿蒙 PC、鸿蒙平板之间秒传图片/文件。
- 鸿蒙原生 socket API 桥接成熟的 Qt/Electron 应用 —— 让局域网工具不用走 musl 路径,绕过沙箱限制。
长期(3-5 年):
- 跨厂商互联协议——鸿蒙 + iOS + Win + Android 互相 AirDrop。
第一档是技术问题(出个文档就行),第二档是工程问题(生态推一推),第三档是政治问题(不强求)。
写在最后
5 月 18 日傍晚那张被卡住 20 分钟的截图,5 月 21-22 日的两天移植——前后整个事件历时4 天。
期间产出:
- ✅
libnitroshare.so587 KB,HAP 891 KB - ✅ 1 篇 28KB 的完整踩坑文
- ✅ 10 张终端截图
- ✅ “鸿蒙沙箱网络限制 + 模态框假性闪退” 5 行代码降级模板
- ✅ 一份Qt 桌面应用在鸿蒙 PC 上的网络能力边界文档
但坦白说——
我到现在为止还没真正用 NitroShare 在两台设备之间传过一张图。
因为接收端 listen 不了,发送端没有第二台设备测试,它实际上还是个"半成品"。
下一次去咖啡馆的时候,我大概率还是会用邮箱传截图——因为这件事真的"还没解决"。
但有一件事变了:
我现在知道路在哪里。
我知道是平台限制不是项目问题。
我知道未来要打通的是 ArkTS 桥接而不是再纠结 Qt。
我知道 5 行代码就能让它别假性闪退。
我知道下一个想做 Qt 局域网工具的人,看了我的文章可以省 5 个小时。
这就够了。
适配这件事本质上不是为了"现在好用"——是为了**“路被人走过一遍,下一个人就不用再迷路”**。
给你
如果你也有一个"很小但每天都烦"的痛点——
比如 PDF 比较、文本对比、日志查看、传文件、键盘工具——
看看清单里有没有 Qt / Electron 的同类开源软件。选一个体积 1-2 MB 的小工具。
不要选 VS Code 这种庞然大物。一个周末。
一杯咖啡(或啤酒)。
一份服务器上的 Qt-OHOS 工具链。
一台真机。撞一次墙。
把撞墙的过程写成文章。
把 5 行代码或者 50 行代码留在仓库里。这就是参与生态最朴素的方式。
不需要等"完全准备好"。
没人完全准备好过。
我自己每次开始都觉得"再等等吧"。但是周日傍晚那张截图卡了我 20 分钟之后——
我就开始了。
附录:NitroShare 移植真实数据
如果你要复现,下面是实测数据:
| 项 | 数值 |
|---|---|
| NitroShare 版本 | 0.3.4 |
| 源码体积 | 1.4 MB / 70 个 .cpp+.h |
| 构建系统 | CMake 3.26.5 |
| 编译耗时 | 35 分钟(含配置 + 编译 + HAP 打包) |
| 真机调试耗时 | 10 分钟 |
| 编译产物 | libnitroshare.so 587 KB |
| HAP 包 | 891 KB |
| 关键修复 | 5 行(QMessageBox::critical → qWarning) |
后记:那张 5 月 18 日的截图,最终我用邮箱传了过去。
在鸿蒙 PC 上看那个字体渲染——其实挺好看的。
所以本质上这一切都是因为:那台鸿蒙 PC 的字体渲染让我想多看一眼。
工具应该让看一眼这件事变得容易。
更多推荐





所有评论(0)