把 Qucs-S 搬上鸿蒙 PC:「电路芯绘」移植实战开发实录
把 Qucs-S 搬上鸿蒙 PC:「电路芯绘」移植实战开发实录
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_QucsS
一条约 40 万行的 C++ 血统、用 Qt 写了二十多年的电路仿真器,如何不重写一行界面代码、完整地跑进鸿蒙 PC 的窗口体系里。本文记录「电路芯绘」(Qucs-S for OpenHarmony PC)从选型、交叉编译、沙箱适配、审核打回到上架应用市场的全过程,附全部关键决策的理由与代码片段。
一、为什么是 Qucs-S
鸿蒙 PC 的应用生态正在快速铺开,但如果你是一名电子工程师或者电子信息专业的学生,把新电脑开箱之后大概率会陷入沉默:Altium、Multisim、LTspice、Qucs——这些吃饭的家伙什,一个都装不上。办公、开发、写作、影音都有了对位产品,唯独电路设计与仿真这条工具链是空的。而这条工具链恰恰是最"硬"的一类桌面软件:重交互(原理图编辑本质上是高频的画布操作)、重本地计算(仿真就是大规模数值求解)、重文件体系(工程、图纸、数据、模型层层嵌套),这类软件既没法用网页凑合,也没法用轻量 App 敷衍。
补齐这块空白,有两条路。第一条是自己写一个:用 ArkTS 重画元件库、重写原理图引擎、重写网表生成器——这是以年为单位的工作量,而且做出来的东西和工程师的既有习惯完全不兼容,等于重新发明一个别人已经用了二十年的轮子。第二条是把成熟的开源软件真正移植过来,让用户拿到手的就是他们已经在用的那套操作逻辑、那套文件格式、那套快捷键。第二条路的本质是"生态承接",工作量大但每一步都有复利。

我选了第二条,靶子是 Qucs-S。先交代一下家底:Qucs 是诞生于 2003 年的老牌开源电路仿真器, Quite Universal Circuit Simulator 的缩写;Qucs-S 是它面向 SPICE 生态的活跃分支,底层可以对接 Ngspice、Xyce 等 SPICE 兼容内核,因此能直接使用工业界通用的器件模型库。分析方法覆盖直流、交流、瞬态、S 参数、谐波平衡、噪声等一整套,从学生作业到射频前端的工程预研都能胜任。它用 C++ 和 Qt 写成,GPL-2.0 协议,本文移植的版本是 24.4.1,源码规模约 40 万行——足够"真实",也足够"麻烦"。
我的目标从一开始就定得很硬:移植的是真实的 Qucs-S 24.4.1 源码,用鸿蒙工具链交叉编译产出真实的原生程序,而不是用 WebView 套一个网页壳,更不是截几张图做一个"演示版"。 上游源码只允许做极少量、可清晰审计的移植性修改,所有鸿蒙相关的代码独立成层。这条纪律贯穿全程,后文会反复出现。

二、先看最终形态
动手之前,先看成品长什么样,这样后面每一项技术决策都有对照。
这是鸿蒙 PC 上以自由窗口运行的「电路芯绘」,打开的是内置示例工程中的共发射极放大器 demo_amplifier.sch。先把这张图读细一点,因为它同时回答了"移植成功没有"的所有问题:信号源 V2(200 mV、4 kHz)经耦合电容 C1 进入由 R1(2 kΩ)、R2(470 Ω)构成的偏置网络,驱动三极管 Q2N2222A;集电极负载由 R3(24 kΩ)与 R5(4.7 kΩ)分压自 12 V 电源 V1,输出信号经 C2 耦合到负载 R4。图纸下方是一个方程块(Eqn1:增益 K=out.v/in.v,功耗 Pwr=(out.Vt*out.Vt)/Rload)和三个仿真指令框——瞬态仿真 TR1(线性扫描 0 到 1 ms)、交流仿真 AC1(对数扫描 100 Hz 到 10 MHz、101 个点)、直流仿真 DC1。这正是 Qucs-S 用户最熟悉的"仿真即元件"的图纸表达:分析类型不是藏在菜单深处的对话框,而是摆在图纸上的"元件",双击就能改参数:

菜单栏的文件、编辑、对齐、插入、项目、工具、仿真、视图、帮助,全部是 Qt 原生菜单翻译成中文后的样子,不是网页模拟。左侧元件面板顶部的分类下拉框当前停在"集总参数元件",面板里电阻、电容、电感、带 Q 的电感、电位计、互感器、多线圈互感器成对排列;底部是一个可用的搜索框和清除按钮。窗口右下角实时显示画布坐标(截图那一刻是 402 : 420),鼠标每动一次就刷新——这种细碎但无处不在的"活感",是判断一个 GUI 是否真的原生运行的最好试金石。
再看看元件库的深度。下面这张是同一台鸿蒙 PC 上的全屏运行截图,元件面板已经滚动到中后段:变压器、对称变压器、阻抗转换器、电流探针、电压探针、开关、继电器依次排开。图纸上已经摆放了一台电流探针 Pr1、Pr2,两只变压器 Tr2/Tr3,一个参数为 10 kΩ、240° 的电位器 POT1,一个开关 S2,还有接地符号和子电路端口。注意每个元件旁边的参数标注——比例 1:1 的绕组、旋转角度——这些属性对话框全部是 Qt 原生的表单:

从空图纸到摆满元件的过程也完全顺滑。这张是放置元件前的样子:分类下拉、搜索框、可滚动的元件网格都已就位,画布上是一个干净的网格坐标系,标题栏上"untitled"的图纸标签正等着第一根导线:

在自由窗口状态下还能做"小窗口摆图纸"的轻量操作——下图就是窗口模式下的变压器实验:两只互感器 Tr1、Tr2 通过初级串联、次级对接的方式接到一台电流探针 Pr1 上,验证同名端关系。窗口可以随意拉伸,元件面板、画布、状态栏各自正确重排:

最后是归宿:应用市场上架,工具类分类,版本 1.0.0,评分 5.0。名字取作「电路芯绘」——电路、芯片、绘图,应用描述里写明了它的出身:源自广受电子工程师和学生喜爱的开源项目 Qucs-S,把桌面端成熟强大的电路设计与仿真能力完整地带到鸿蒙生态:

从零到这个形态,工程主线可以拆成五个技术关:窗口承载、交叉编译、图标渲染、沙箱文件体系、源码门控改造。下面逐一展开。
三、总体架构:三层结构,责任清晰
整个移植的骨架是三层,层与层之间只靠明确的边界通信。
上游层:native/vendor/qucs-s/,原封不动的 Qucs-S 24.4.1 源码树,包括主程序、元件库定义、滤波器综合器、衰减器计算器、传输线计算器等全部子模块。所有针对鸿蒙的修改都包裹在 #ifdef QUCS_OHOS_PORT 门控宏里,且全部集中在交互层(菜单、视图、对话框),从不触碰电路核心数据结构。这样做的意义是:未来跟随上游升级时,diff 一眼可审——把宏关掉,代码就退回上游原貌。
门控宏是整个移植的"宪法",它长这样:
// native/ohos/qucs_ohos_entry.cpp
#ifdef QUCS_OHOS_PORT
# include "env_setup.h" // 适配层:沙箱/示例工程/翻译挂载
#endif
int main(int argc, char **argv) {
#ifdef QUCS_OHOS_PORT
QucsOhos::setupSandbox(); // 适配层先就位,Qt 才能找到"家"
#endif
QApplication a(argc, argv);
QucsApp w;
w.show();
return a.exec();
}
而 CMake 这边只是把宏打开,并把鸿蒙专属代码编译进库:
# native/CMakeLists.txt
if(OHOS)
target_compile_definitions(qucs_shell PRIVATE QUCS_OHOS_PORT)
target_sources(qucs_shell PRIVATE ohos/qucs_ohos_entry.cpp
ohos/env_setup.cpp)
endif()
适配层:native/ohos/,鸿蒙特有的环境初始化逻辑:沙箱内家目录的建立、首启播种示例工程、翻译资源挂载、路径收敛。这一层是"鸿蒙世界"与"Qt 世界"的翻译官,是唯一允许大量出现鸿蒙专有概念的地方。
承载层:Qt for OpenHarmony 5.12.12 运行时 + ArkTS 壳工程。C++ 世界最终编译成一个动态库 libqucs_shell.so(9.1 MB),由 ArkTS 侧的 QAbility 容器通过 XComponent 挂载。全部 413 个翻译单元经鸿蒙 SDK 的 clang(目标 aarch64-linux-ohos)交叉编译而成。
ArkTS 壳做的工作非常克制:窗口生命周期、XComponent 的 Surface 交接、多窗口节点管理、进程拉起。界面上你看到的一切——菜单、工具栏、画布、对话框、右键弹层——没有一个像素是 ArkTS 画的,全部来自 Qt。
这个架构有一个直接的好处:ArkTS 侧几乎没有业务逻辑。将来 Qt 运行时升级、上游源码升级,壳工程基本不用动;反过来,如果哪天要支持另一种输入设备或窗口形态,改动也被限制在桥接层。分层不是教科书洁癖,是在给未来的自己留退路。
四、技术关一:让 Qt 程序"长"在鸿蒙窗口里
Qt 跨平台的秘密在于 QPA(Qt Platform Abstraction):Qt 不直接碰操作系统,而是通过平台插件抽象出窗口、事件、OpenGL 上下文。要把 Qt 程序搬上鸿蒙,就需要一个鸿蒙的 QPA 插件——这就是 Qt for OpenHarmony 项目的 libqohos.so,连同 libQt5Core/Gui/Widgets/Network/OhosExtras 等一整套运行时库(合计约 460 MB 的预编译产物)。
但有了 QPA 只是及格线。鸿蒙的应用模型和桌面 Linux/Windows 完全不同:应用入口是 Ability 而不是 main() 之后的自由世界;渲染目标不是顶层窗口,而是 ArkUI 组件树里的 XComponent 提供的 Surface;窗口的创建、销毁、尺寸变化由系统窗口管理器驱动,Qt 必须被动响应;文件访问被沙箱约束,没有随意的 /home。
我的做法是采用 Qt 官方鸿蒙模板的标准结构:QAbility 承载生命周期,XComponent 承载渲染面,QPA 把二者桥接成 Qt 意义上的顶层窗口。ArkTS 侧维护三类 NativeNode——主窗口、子窗口、浮动窗口,分别对应 Qt 的主窗体、模态对话框和 QMenu 弹层。这个分类很关键:Qucs-S 的交互高度依赖右键菜单和属性对话框,如果只有主窗口一条通道,用户第一次右键元件就会穿帮。
ArkTS 侧的壳代码非常薄,核心就是把 Ability 的生命周期和注册的 NativeNode 接到 Qt QPA 上:
// entry/src/main/ets/MainAbility/MainAbility.ts
import { QAbility } from '@qt/qpa';
import { MainWindowNativeNode } from '../components/MainWindowNativeNode';
export default class MainAbility extends QAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
// 把 ArkUI 的窗口舞台交给 Qt QPA,后续由 C++ 接管绘制
windowStage.loadContent('pages/Index', (err) => {
if (err) console.error('Qucs-S stage load failed:', err);
});
}
}
// 三类节点:主窗 / 对话框 / 右键菜单,缺一不可
registerNativeNode('mainWindow', createMainWindowNode);
registerNativeNode('subWindow', createDialogNode);
registerNativeNode('floatWindow', createMenuNode);
事件桥接在 C++ 侧把鸿蒙派发的输入收敛到 Qt 主线程,避免跨线程时序问题:
// native/ohos/event_bridge.cpp
void OhosInputBridge::dispatchKey(int key, int state) {
// ArkUI 线程 → Qt 主线程,序列化为一次 Queued 事件
QMetaObject::invokeMethod(qApp, [=] {
QWidget *fw = QApplication::focusWidget();
if (fw) QCoreApplication::postEvent(
fw, new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier));
}, Qt::QueuedConnection);
}
这里有几个典型的坑,每一个都值得单独记一笔:
事件线程模型。 鸿蒙的输入事件在 ArkUI 线程上派发,而 Qt 的事件循环在自己的线程里。桥接层做了事件队列加线程投递,键盘、鼠标、滚轮、触摸板手势全部收敛到 Qt 主线程处理。竣工后的直观检验就是上文那些操作:双击元件弹出属性对话框、右键出上下文菜单、滚轮缩放画布、拖拽移动元件——每一个背后都是一次跨线程的事件迁移,任何一个环节的时序错误都会表现为"点不中"或"拖不住"。
自由窗口适配。 鸿蒙 PC 的窗口可以被用户任意拉伸,最小可以收到一个很窄的边栏。Qt 侧要正确响应 Surface 尺寸回调并重排布局。窄边栏状态是最严苛的考题:左侧面板的竖排页签、元件分类下拉框、搜索框、清除按钮都要保持可用且不互相裁切。最终效果见第二节的截图——这是反复调布局约束和最小尺寸的结果,而不是碰运气。
中文输入与剪贴板。 原理图里的元件编号、导线标签、方程随时可能进入编辑态,输入法上屏、剪贴板粘贴都要穿透 ArkUI 到达 Qt 的输入上下文。这条链路调通之前,图纸上的 R=24 kOhm 连改都改不了——你会看到编辑框,敲进去的字却消失。
焦点与多窗口。 主窗口、属性对话框、右键菜单三者之间必须维持正确的焦点链:对话框打开时主窗口不抢键、菜单收起后焦点归位画布。桌面 Qt 程序这些是免费的,在桥接模型下要逐条验证。
五、技术关二:交叉编译链,413 个翻译单元
第二关是让 40 万行 C++ 真正变成鸿蒙能加载的二进制。编译链的三个角色缺一不可:
- 目标编译器:鸿蒙 SDK 的 clang,目标三元组
aarch64-linux-ohos,负责把所有 C++ 译成鸿蒙设备上的机器码; - Qt 头文件与配置:Qt for OpenHarmony 5.12.12 的头文件树和
mkspecs,保证 Qucs-S 看到的 Qt API 与运行时库严格一致; - 宿主工具:moc、rcc、uic 必须是能在 Windows 上运行的 x64 版本——Qt 的元对象编译器在构建期运行,产出的 C++ 代码再交给目标编译器。这一步经常被忽略:没有宿主 moc,信号槽机制的连接代码根本生成不出来。
CMake 工程把上游源码作为一个整体目标编译。全量构建一次要过 413 个翻译单元,最终链接出 libqucs_shell.so。构建脚本里做了三道存在性检查,分别守住 Qt 运行时库、Qt 头文件、宿主工具三个依赖,缺任何一个直接 FATAL_ERROR,并且错误信息里直接给出获取方式——克隆仓库的人第一次构建失败时,看到的不是冷冰冰的 “Missing”,而是一条可执行的路径。这个设计来自一个朴素的判断:开源仓库的价值不在于能跑通我自己的机器,而在于陌生人能不能跑通。
存在性检查长这样,注意它把"为什么缺"和"去哪拿"一起写进了报错:
# native/cmake/ohos_deps.cmake
if(NOT EXISTS "${QT_FOR_OHOS}/lib/libQt5Core.so")
message(FATAL_ERROR
"未找到 Qt for OpenHarmony 运行时(libQt5Core.so)。\n"
"请先用 DevEco 新建 Qt 模板工程,将其 entry/libs/arm64-v8a/ 拷到 QT_FOR_OHOS,\n"
"详见 README §4.1:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_QucsS")
endif()
实际触发编译的是 build_ohos.cmd,它把工具链、宿主 moc、目标 triple 一次性配齐:
@echo off
:: native/build_ohos.cmd —— 413 个翻译单元全量交叉编译
set OHOS_SDK_ROOT=D:\DevEco\SDK
set PATH=%OHOS_SDK_ROOT%\native\llvm\bin;%PATH%
cmake -S . -B build-ohos ^
-DCMAKE_TOOLCHAIN_FILE=%OHOS_SDK_ROOT%\native\build\cmake\ohos.toolchain.cmake ^
-DOHOS_ARCH=aarch64-linux-ohos ^
-DCMAKE_BUILD_TYPE=Release ^
-DQUCS_OHOS_PORT=ON
cmake --build build-ohos -j %NUMBER_OF_PROCESSORS%
链接产物随后自动拷贝进 HAP 工程的 libs/arm64-v8a/,与 Qt 运行时一起随包分发。HAP 的打包环节还有一个纯工程性的坑:hvigorw 的 PackageHap 和签名步骤要调用 Java 工具链,命令行构建时必须把 DevEco 自带的 JBR 前置到 PATH,并显式指定 DEVECO_SDK_HOME,否则会得到一个莫名其妙的 spawn java ENOENT。这类问题与移植本身无关,却消耗了不成比例的排查时间——所以最终都固化进了构建脚本,让脚本替记忆值班。
六、技术关三:QtSvg 三件套,被低估的图标问题
这是整个移植里最隐蔽、也最能体现"真移植 vs 假移植"差别的一关。
Qucs-S 的界面大量使用 SVG 图标:工具栏上的放大镜、保存、编译、仿真按钮,元件面板里每一个元件的符号,都是矢量图。Qt 渲染它们依赖三个模块协同:libQt5Svg.so 本体、iconengines/libqsvgicon.so(QIcon 的 SVG 引擎插件)、imageformats/libqsvg.so(QPixmap 的 SVG 读取插件)。三者各司其职:第一个负责把 SVG 矢量画成位图,第二个让 QIcon 知道 .svg 文件是什么,第三个让 QPixmap 能直接加载 SVG。
问题在于,Qt for OpenHarmony 的预编译运行时不带 QtSvg。缺了它,程序不会崩溃,也不会报错——只是所有 SVG 图标渲染成空白。你会在屏幕上看到:工具栏按钮还在,按下去功能正常,但按钮是空的;元件面板的元件名还在,符号没了。这种"静默降级"在开发机上很容易被当成布局 bug 折腾很久,因为它不抛异常、不写日志,证据链在渲染器内部就断了。
对一个电路软件来说这不是小事:元件符号是工程师的"文字",原理图的可读性完全建立在符号正确渲染之上。没有图标三件套,画布上的电阻电容虽然还在(它们是画布矢量绘制,不依赖 QtSvg),但整个元件面板和工具栏会退化成一场猜谜。
解决方案没有捷径:从 Qt 官方的 qt-for-harmonyos 5.12.12 快照服务器拉取 qtbase 与 qtsvg 的完整源码,用与主运行时完全一致的工具链参数自行交叉编译这三个产物(libQt5Svg.so 仅 1.3 MB),放进运行时库目录。编译参数必须严格对齐——版本、ABI、异常模型任何一项不一致,加载时就可能出符号冲突,而且症状同样是静默的。图标三件套就位之后,界面的观感才真正"对味":所有工具栏图标、元件符号与桌面版 Qucs-S 完全一致。
自行交叉编译时,configure 的参数要和主运行时逐字对齐,这是它唯一的"坑":
# 用与 Qt 主运行时完全一致的参数,自行交叉编译 QtSvg
../qtbase/configure -xplatform ohos-clang \
-prefix $PWD/install -opensource -confirm-license -release \
-no-feature-testlib -skip qtdeclarative
cd qtsvg && cmake --build . -j
# 产物:libQt5Svg.so + 插件 iconengines/libqsvgicon.so + imageformats/libqsvg.so
七、技术关四:沙箱里的文件体系
Qucs 有一套自成一体的文件观,移植时必须完整保留:
- 用户有一个"家目录"(QucsHome),所有工程都在它之下;
- 工程就是目录,且必须以
_prj后缀结尾,工程列表里显示的就是这些目录; - 原理图、数据集、显示页、备注都存放在工程目录内,Qucs 用目录后缀来识别工程边界。
而鸿蒙的应用沙箱给每个应用划了一个独立的加密区(el2 目录),没有传统意义的 HOME,更不会有人替你准备这些目录结构。native/ohos/env_setup.cpp 承担了全部翻译工作:
// native/ohos/env_setup.cpp
void QucsOhos::setupSandbox() {
// 1) 家目录落在沙箱内,Qucs 才能找到"自己的家"
QString home = QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation) + "/qucs";
QDir().mkpath(home);
// 2) 示例工程播种:目录必须以 _prj 结尾,否则工程列表永远空
seedProject(home + "/demo_amplifier_prj",
":/examples/demo_amplifier.sch");
// 3) 翻译挂载,界面才切到中文
static QTranslator t;
if (t.load("qucs_zh_CN", ":/translations"))
QCoreApplication::installTranslator(&t);
}
- 首启初始化:在沙箱内建立 QucsHome 目录结构和用户配置文件,让 Qucs 第一次启动时就能找到"家";
- 示例工程播种:把随包资源里的
demo_amplifier.sch拷入demo_amplifier_prj/目录。注意细节:必须以_prj结尾,否则 Qucs 的工程扫描器根本不认,工程列表永远显示为空。这一步让新用户第一次打开就能看到一份带完整偏置、增益方程和三种仿真框的放大器电路,而不是一张白纸; - 翻译资源挂载:把
resfile/translations/下的 Qt 翻译文件(.qm)在程序启动时装载进 QTranslator,界面文字才能切到中文; - 路径收敛:所有文件读写都锚定在沙箱内,既满足鸿蒙的安全模型,也保证了 Qucs 自身"相对路径 + 家目录"的文件逻辑不被破坏。
这个设计还有一层产品考虑:示例工程就是教程。用户双击任何一个元件都能看到参数对话框,双击方程块能看到增益和功耗的表达式,三分钟内就能建立起"图纸—元件—仿真框"的完整心智模型,比任何新手引导页都有效。
八、技术关五:上游源码的门控改造
对 40 万行外来代码,我的纪律是:能不改就不改;必须改的,包进 QUCS_OHOS_PORT 宏,并在注释里写清楚为什么。 最终落地的修改全部集中在交互层,电路内核与文件格式一行未动。
第一组:进程能力裁剪。 桌面版 Qucs-S 的"工具"菜单会通过 QProcess 拉起外部子程序(滤波器综合器、衰减器计算器、传输线计算器等独立可执行文件),"仿真"菜单会拉起仿真内核,"视图"菜单里有依赖外部 Octave 的停靠窗。但鸿蒙应用沙箱禁止 exec 外部进程——这是安全模型的硬约束,不是可以绕过的 bug。改造方案:工具菜单只保留进程内实现的匹配网络综合与文件格式转换器;仿真菜单整体置灰、仿真工具栏不再创建;Octave 停靠窗从视图菜单移除;空的器件库分组不再创建标签页。每一处都保留"能力还存在"的代码路径,只是不再暴露给用户——等将来内核进程内集成落地,这些入口可以原样恢复。
对应补丁(节选):
// native/ohos/patches/menu_capability.cpp
#ifdef QUCS_OHOS_PORT
// 沙箱禁 exec 外部进程:仅保留进程内实现的入口
if (!Qucs::isProcessCapable())
toolMenu->removeAction(actionLaunchExternalTool);
simMenu->setEnabled(false); // 内核走进程内集成后再开启
viewOctaveDockAction->setVisible(false);
#endif
第二组:视图逻辑修正。 工程内容面板(Content Tab)在桌面版会为原理图、数据集、数据显示页、VHDL、Verilog、Octave 等十个类别各建一栏,鸿蒙版随包资源只有原理图,其余九栏永远为空——用户双击一个空分类,看起来就像"点不开"。改造为:构建列表后自后向前删除空分类(倒序删除是为了避免索引移位),每个工程目录只显示真正有内容的栏位。连带修了一个隐藏 bug:导出原理图的代码原本按固定行号取类别项,删行之后会错位,改成按表头文字定位,从此不再依赖列顺序。
// 倒序删除空分类,避免 removeTab 时索引整体前移
for (int i = categories.size() - 1; i >= 0; --i)
if (projectContent(categories[i]).isEmpty())
tabWidget->removeTab(i);
// 导出原理图:按表头文字定位,不再依赖固定行号
int col = headerText.indexOf(tr("Schematic")); // 中文界面下仍为 "Schematic"
exportRow(col, data);
第三组:中文界面补齐。 上游的中文翻译覆盖了主流程,但工程内容面板的十个分类名(数据集、数据显示页、原理图、符号、备注……)不在其中,界面上会突然冒出英文。我在 qucs_zh_CN.ts 里补齐了 ProjectView 上下文的全部条目,用宿主的 lrelease 工具编译出 2728 条翻译的 .qm 文件随包分发。顺带把状态栏一个永久的"无警告"占位文字置空——在没有仿真警告可显示的当前形态下,它是纯粹的视觉噪音。这类"半拉文字"最伤专业软件的质感,值得逐个清理。
翻译补齐的 .ts 片段:
<!-- qucs_zh_CN.ts:补全 ProjectView 上下文 -->
<context>
<name>ProjectView</name>
<message><source>Data Sets</source><translation>数据集</translation></message>
<message><source>Data Displays</source><translation>数据显示页</translation></message>
<message><source>Schematics</source><translation>原理图</translation></message>
<message><source>Symbols</source><translation>符号</translation></message>
<message><source>Verilog</source><translation>Verilog 源文件</translation></message>
</context>
这一组改造做完,鸿蒙版的上游 diff 依然控制在个位数文件,每一处都能用一句话解释动机。
九、一次真实的审核打回
应用首次提审,被以"功能异常"打回,附了两条用户视角的现象描述。复盘根因,恰好对应上文的两类问题,这次打回是全文最有价值的教材。
现象一:工具菜单弹出"无法启动程序"。 根因正是 QProcess 沙箱禁 exec——桌面版的工具菜单原样保留了入口,点击后 Qt 弹出标准的"无法启动"错误框。在桌面生态这是合理的降级提示(子程序确实没装),在鸿蒙上却成了"功能异常"的直接证据:用户点了一个看得见的按钮,得到一句自己看不懂的报错。
现象二:"文档"点开没有内容。 文档面板即工程内容管理器。首启逻辑当时只拷了单个原理图文件、没有建 _prj 工程目录,工程列表显示为空;同时十个分类九个永远空着,双击空白分类的观感就是"这个功能坏了"。审核员不可能知道 Qucs 的 _prj 约定,他们只会用眼睛投票。
修复对应的两组门控补丁:工具菜单裁剪、空分类删除、工程目录播种。重新出包提审,通过,随即上架。这次打回给我的教训很值钱:平台差异不修成"产品语义",只修成"不崩溃",审核和用户都不会买账。 桌面版的灰色菜单在鸿蒙上不是降级,是缺陷。移植工作的最后一公里,从来都是这些"技术上行得通、产品上立不住"的细节。
十、开源仓库治理:3620 个文件的取舍
源码上传到 AtomGit(OpenHarmonyPCDeveloper/ohos_QucsS)时,如何做到"开源合规且不泄密"花了不少心思,几条原则如下。
坚决不入库的:签名材料——*.p12/*.p7b/*.cer/*.csr 一律忽略,build-profile.json5 里的加密密码块整体剥离,改为注明由 DevEco 自动签名生成(签名材料与 bundleName 绑定,他人仓库本就该用自己的);打包产物——*.hap 和全部构建输出目录;预编译 Qt 运行时——单文件 156 MB,超出 git 单文件尺寸限制,且属于可重新获取的公共产物;Windows 宿主 Qt 工具链与官方源码快照——一条 aqt 命令或一个下载链接即可复原的公共资产,没有理由让仓库背 600 多 MB 的重复数据。
替使用者铺路的:两个 README 各辟一章"仓库未包含的大文件与获取方式",逐项给出体积与获取命令;CMake 的三道存在性检查把同样的信息写进 FATAL_ERROR。仓库里只有源码与小型必需资源(应用图标、翻译文件、示例原理图、截图),最终收敛为 3620 个文件、约 103 万行。
合规的:随上游携带 GPL-2.0 许可证到仓库根目录,README 注明上游协议与本仓库的分发关系——开源软件的移植版本,许可证链条必须完整可追溯。
治理过程中还踩了一个很有代表性的坑:DevEco 模板的 .gitattributes 把 *.png 全部声明为 Git LFS 跟踪,首次推送直接被远端拒绝。排查发现,本地根本没有对应的 LFS 对象,而且某些模板自带的图标在磁盘上就是一段 130 字节的 LFS 指针文本——三行 version/oid/size——而非真实图片,真实二进制从未随仓库下载下来。处置分三步:关闭 LFS 跟踪,让真实图标以普通 Git 对象入库;把模板自带的测试脚手架(ohosTest)整体排除,它不属于应用本体,还带着那个坏图标;逐字节核验索引里再无指针残留后重新提交。这个坑的普遍性值得写进每一个鸿蒙开源项目的清单:建仓先看 .gitattributes。
关闭 LFS 的改动只有两行,却挡住了一次推送事故:
# harmony_pc/.gitattributes —— 关闭 LFS,图标均为小文件,真实内容直接入库
*.png filter=
*.tgz filter=
而 CMake 的存在性检查则把"缺什么、去哪拿"固化进了构建入口:
if(NOT EXISTS "${QT_FOR_OHOS}/lib/libQt5Core.so")
message(FATAL_ERROR
"未找到 Qt for OpenHarmony 运行时。请见 README §4.1:\n"
"用 DevEco 新建 Qt 模板工程,拷贝其 entry/libs/arm64-v8a/ 到 QT_FOR_OHOS")
endif()
十一、如何从零复现这个构建
克隆仓库后,按 README 的分步指南走,主线是四步:
- 取回三块不入库的公共资产:DevEco Studio 新建一个 Qt for OpenHarmony 模板工程,把它的
entry/libs/arm64-v8a/整目录拷过来(这就是 Qt 运行时);用aqt install-qt拉取 Windows 宿主 Qt 5.12.12 工具链;从 qt-for-harmonyos 快照服务器下载 qtbase/qtsvg 源码快照解压到native/vendor/。CMake 的三道检查会逐一确认这些资产就位,缺什么会明确告诉你缺什么、去哪拿。 - 交叉编译:运行
native/build_ohos.cmd,413 个翻译单元全量编译,产出libqucs_shell.so并自动拷入 HAP 工程。 - 打包:在
harmony_pc/下执行hvigorw assembleHap,得到未签名 HAP;在 DevEco 中配置自动签名(签名材料与你的账号绑定,不入库也无需入库)。 - 安装验收:真机安装启动,按 README 的验收清单过一遍——首启播种、元件放置、属性编辑、工程列表、中文界面、自由窗口缩放。
QA 与常见问题(FAQ)
把移植和上架过程中被问得最多的问题收在这里,多数都能在正文对应章节找到展开版。
Q1:为什么不直接用 WebView 套个网页,或者干脆用 ArkTS 重写一个?
两个都不划算。WebView 套网页解决不了电路软件对高频画布交互和本地计算的硬需求,本质上是个"演示壳";用 ArkTS 重写则要重画元件库、重写原理图引擎与网表生成器,是年量级工作量,而且做出来和工程师的既有习惯不兼容。本文走的是第三条路——把真实的 Qucs-S 24.4.1 源码交叉编译成原生程序,详见 §一、§三。
Q2:ArkTS 写了界面吗?
没写。ArkTS 壳只负责窗口生命周期、XComponent 的 Surface 交接、三类 NativeNode(主窗/对话框/右键菜单)的注册,以及进程拉起。界面上所有像素——菜单、工具栏、画布、对话框——都来自 Qt。这是"分层"带来的直接红利:上游或运行时升级时壳工程基本不动(见 §三、§四)。
Q3:安装后工具栏、元件面板全是空白文字,没有图标,怎么回事?
典型原因是 QtSvg 三件套没有编译或没有放进运行时目录。缺了 libQt5Svg.so + iconengines/libqsvgicon.so + imageformats/libqsvg.so,Qt 不会报错,只会把所有 SVG 图标静默渲染成空白——这是最容易误判成"布局 bug"的坑。从 qt-for-harmonyos 5.12.12 快照自行交叉编译这三件套即可,详见 §六。
Q4:打开后工程列表是空的,"文档"点不开?
根因是 Qucs 的"工程即目录"约定:工程目录必须以 _prj 结尾,Qucs 的工程扫描器才认。首启时适配层会把 demo_amplifier.sch 播种进 demo_amplifier_prj/ 目录:
// 必须以 _prj 结尾,否则工程列表永远为空
seedProject(home + "/demo_amplifier_prj", ":/examples/demo_amplifier.sch");
同时工程内容面板的空分类(数据集、VHDL、Octave 等鸿蒙版没有的类别)已被倒序删除,避免"双击没反应"的错觉。这部分正是首审被打回的两条现象之一,详见 §七、§八、§九。
Q5:工具菜单里某些项点了报"无法启动程序"?
这是鸿蒙应用沙箱禁止 exec 外部进程的硬约束:桌面版 Qucs-S 的工具/仿真菜单原本会拉起独立子程序,在鸿蒙上既拉不起、又会触发审核的"功能异常"。我们的处理是把这些入口裁剪掉——只保留进程内实现的匹配网络综合与格式转换,仿真菜单置灰。等内核做成进程内集成后这些入口可原样恢复,详见 §八、§九。
Q6:仓库里为什么没有 Qt 运行时(156 MB 的 libqohos.so)、宿主 moc/rcc 工具、官方源码快照?
都是为了仓库能用、合规且不臃肿:预编译运行时单文件超限且可重新获取,Windows 工具链和官方快照一条命令或一条链接即可复原,没有理由让仓库背 600 多 MB 的重复数据。仓库只放源码与小型必需资源(图标、翻译、示例原理图),缺失的三块在 README §4.1 给出获取方式,CMake 三道存在性检查会在缺依赖时直接告诉你缺什么、去哪拿,详见 §十、§十一。
Q7:现在直流/交流/瞬态仿真能直接跑出曲线吗?
当前版本已能完整编辑原理图与三种仿真框(DC/AC/TR),示例工程 demo_amplifier.sch 里就摆着三张仿真指令框和增益方程。仿真内核的进程内集成(绕开被沙箱禁止的外部进程模型)是路线图上的下一项重点,落地后图纸上的仿真框即可一键出曲线,详见 §十三。
Q8:支持 x86 鸿蒙、平板或其他架构吗?
当前只构建 aarch64-linux-ohos 一种架构,对应现在的鸿蒙 PC 与平板设备。交叉编译切换架构只需改 OHOS_ARCH 与工具链,运行时和门控补丁均与架构无关,x86 适配成本可控,已列入展望,详见 §十三。
Q9:代码开源在哪?我怎么复现构建?
开源仓库在 https://atomgit.com/OpenHarmonyPCDeveloper/ohos_QucsS,含完整构建指南。复现主线四步:取回三块不入库的公共资产 → 跑 native/build_ohos.cmd 全量编译 → hvigorw assembleHap 打包 → 真机安装验收,详见 §十一。
Q10:许可证是什么?能改能商用吗?
随上游携带 GPL-2.0。你可以自由克隆、修改、再分发,但依据 GPL 传染性,你的修改版本同样需要以 GPL-2.0 开源。README 已注明上游协议与本仓库的分发关系,详见 §十。
十二、数据小结
| 维度 | 数字 |
|---|---|
| 上游源码 | Qucs-S 24.4.1,约 40 万行 C++(GPL-2.0) |
| 门控修改 | 7 处,全部 QUCS_OHOS_PORT 包裹,集中在交互层 |
| 编译规模 | 413 个翻译单元 → libqucs_shell.so 9.1 MB |
| 自交叉编译 | libQt5Svg + qsvgicon + qsvg 图标三件套(1.3 MB) |
| 中文翻译 | 2728 条(lrelease 产出 .qm 随包) |
| 开源仓库 | AtomGit 3620 文件 / 约 103 万行,0 签名材料、0 打包产物 |
| 签名 HAP | 约 476 MB(含完整 Qt for OpenHarmony 运行时) |
| 上架 | 应用市场 · 工具类 · v1.0.0 · 评分 5.0 |
十三、写在后面
回头看,这次移植真正的难度不在某一行代码,而在三个世界的对齐:Qt 的窗口与事件世界、鸿蒙的 Ability 与沙箱世界、Qucs 自己的文件与工程世界。任何两个世界之间的想当然,最后都会以"功能异常"的形式被退回来。而解决它们的方法论又出奇地一致:先承认约束是真的(沙箱禁 exec、无 HOME、无 QtSvg),再在约束之内给用户完整的产品语义,而不是给一个技术上的借口。
接下来路线图上还有三件事。第一,把仿真内核做成进程内集成——绕开被沙箱禁止的外部进程模型,让图纸上的直流/交流/瞬态仿真框真正一键出曲线,这是价值闭环的最后一块;第二,扩充随包元件库与示例工程的数量,让"出厂内容"覆盖更多教学与工程场景;第三,跟进上游版本持续升级——得益于门控纪律,这件事的成本已经被压到了最低,这是当初坚持"真实源码 + 最小修改"的最好回报。
开源仓库:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_QucsS(含完整构建指南,从获取运行时到出包一条线走通,欢迎 ISSUE 与 PR)。

更多推荐




所有评论(0)