A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】32 / 56
■ 应用:轻绘图(from SciDAVis)(AirPlot)
■ 定位:科学绘图与数据分析(SciDAVis)
■ 上游:SciDAVis | 路线:Qt5 5.15+QPA | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 SciDAVis
SciDAVis 是一款开源的科学数据分析与可视化程序,围绕"表格—绘图—拟合"组织工作流:数据以表格与矩阵录入,经 2D/3D 绘图呈现,再用 muParser 表达式与 GSL 数值库做曲线拟合。它采用 Qt Widgets 与 MDI 多文档界面,与 Origin 面向的实验数据处理场景高度重合,是高校与实验室里常见的轻量替代品。
鸿蒙侧工程名为 AirPlot,应用中文名"轻图示",包名 com.airplot.app,版本 1.0.0,deviceTypes 覆盖 phone、tablet 与 2in1,module.json5 中显式关闭 compressNativeLibs。移植路线为 Qt 5.15.16 加自研 ohos QPA——版本由上游 find_package(Qt5 ...) 决定,架构蓝本取自 AirDraw(Krita 的 Qt5 QPA 初代)与 AirKam(digiKam 的 Qt5 双层架构定型作)。PORTING.md 记载的首日实机验证在 MateBook Pro 2in1 与 TXZ-W20 平板上完成,后续各轮主测设备为 MatePad 11.5。
对整个移植系列而言,这个工程的样本价值在于:上游是十几万行的经典 Qt Widgets 桌面程序,没有 QML、没有现代跨平台抽象,MDI 子窗、模态对话框、QFileDialog 静态调用全部是桌面默认语义——把它们搬进无窗口管理器的单表面环境,是比"渲染出画面"深一层的问题。
▍二、先划清边界:不把编译通过当成适配完成
上游 SciDAVis 的每个交互细节都假设自己运行在有 WM 的桌面上:主窗是 ApplicationWindow,MDI 子窗可以拖拽浮动的假设、导入导出弹 QFileDialog、启动弹 QSplashScreen 与设置向导、联网检查更新。这些语义若不逐层替换,编出来的库只会得到一个能画 cannot-represent 内容的空壳。
工程在 PORTING.md 的补丁清单与风险登记册里把边界写得很清楚,当前产物的每一层对应关系如下。
层次 原项目实现(桌面 SciDAVis) 鸿蒙侧当前方案
应用入口 scidavis/src/main.cpp + QSplashScreen airplot_ohos_entry.cpp 承担入口,splash 跳过
窗口承载 X11 窗口 + WM 管理 MDI 框架窗 XComponent(TEXTURE) + ohos QPA 单表面
界面形态 MDI 浮动子窗 + Qt 原生菜单栏 QMdiArea::TabbedView 标签页 + ArkTS 菜单栏
文件对话框 QFileDialog 静态调用(导入/导出全链) -include shim 头重定向到系统 Picker 桥
数值脚本 muParser + 可选 Python 脚本 muParser 保留,Python 以 SCRIPTING_PYTHON=OFF 裁剪
3D 绘图 桌面 OpenGL(qwtplot3d,glBegin/GLU) gl4es 翻译层(Maleoon 兼容补丁版,实测项)
联网能力 启动时检查更新与下载链接 SEARCH_FOR_UPDATES=OFF、DOWNLOAD_LINKS=OFF 裁剪 Qt5Network
汉化 随包 scidavis_zh-cn.ts(364KB) 宿主 lrelease 出 .qm,rawfile 部署,2521 条装载
需要强调的是最后两行代表的取舍方式:能关掉的外部依赖直接从 CMake 层关掉,让 find_package 不再执行;不能关掉的桌面 GL 依赖用翻译层兜住。边界清晰之后,"能跑"与"不能用功能冒充已移植"才有共同的判据。
▍三、鸿蒙版的整体架构
工程是 AirKam 双层架构的又一个落地实例:ArkTS 壳管 Ability、资源部署与菜单栏,NAPI 薄壳 libplotbridge.so 直收 XComponent 输入回调,libscidavis_core.so 在 JS 主线程完成 dlopen 与 QApplication 构造,渲染与事件泵驻留在独立线程。
text
EntryAbility
├── rawfile→沙箱部署(stamp 幂等)/ 隐藏系统标题栏 / 沉浸式
└── Index.ets:XComponent(TEXTURE) + splash 遮罩 + ArkTS 菜单栏
└── NAPI:libplotbridge.so(薄壳,只链系统库;触摸/鼠标/键盘回调直收)
▼ initPlot(JS 主线程)按拓扑序 dlopen 全部依赖(RTLD_NOW|RTLD_GLOBAL)
└── libscidavis_core.so(scidavis_patches/airplot_ohos_entry.cpp)
├── QApplication(JS 主线程创建)+ ApplicationWindow(Qt MDI 主窗)
├── RenderThread:Plot_Step 泵(8ms 保底节拍 + 条件变量唤醒)
│ sendWindowSystemEvents + processEvents(16ms)
├── 指针/键盘注入:QWindowSystemInterface(原生 px,Qt 内 ÷dpr 一次)
├── 菜单桥:QMenuBar→JSON 导出 + QAction::trigger 回注
└── libscidavis(143 cpp/7.3 万行)+ Qwt5 + qwtplot3d + muParser + GSL
Qt 插件静态注册(libqohos/libqsvg/libqsvgicon 从 bundle libs dlopen 后注册)
主要目录如下:
text
AirPlot/
├── AppScope/app.json5 # 包名 com.airplot.app、应用名"轻绘图"
├── scidavis-master/ # 上游源码树(zip 展开 + 三子模块代理补齐)
├── native/
│ ├── build_thirdparty.sh # muParser/Qwt5-qt5/qwtplot3d/minigzip 交叉编译
│ ├── build_scidavis_ohos.sh # libscidavis + 壳入口 → libscidavis_core.so
│ ├── scidavis_patches/ # airplot_ohos_entry.cpp、文件对话框桥
│ ├── scidavis_build/ 与 install/ # 构建树与产物前缀
│ └── fix_needed.py # NEEDED 规整
├── ai_log/ # 每轮会话实录(yyyy-MM-dd 摘要-vN.md)
├── qa/ # 验收记录
└── entry/src/main/
├── ets/pages/Index.ets # 壳层主页与菜单栏
├── ets/utils/FileManager.ets # 沙箱文件中转
└── cpp/
├── napi_init.cpp # NAPI 桥:事件入队、pick 回调、IME 桥
├── qt_platform/ohos/ # 自研 ohos QPA(复用 AirDraw 蓝本重建)
├── ohos_deps/ 与 qt5_source/ # Qt5.15.16 运行时与源码 include 树(软链复用)
└── deploy_deps.sh # Qt5/GSL/gl4es 部署到 entry/libs
▍四、核心适配过程
### 1. 双泵架构与线程模型
QApplication 在 JS 主线程创建,而事件派发由两条泵分担:RenderThread 的 Plot_Step 以 8ms 保底节拍(条件变量可即时唤醒)执行 sendWindowSystemEvents 与 16ms 的 processEvents;指针、滚轮事件经 NAPI 无锁环入队后在泵线程统一注入,杜绝 ArkTS 主线程直调 Qt 的线程违例。输入坐标全程使用原生物理像素,Qt 内一次性除以 dpr。菜单桥把 QMenuBar 树导出为 JSON、动作经 QAction::trigger 回注,构成"ArkTS 菜单栏 + Qt 执行"的闭环。
### 2. 依赖矩阵:复用为主、新编为辅
Qt 5.15.16 运行时七模块(Core/Gui/Widgets/Svg/PrintSupport/Xml/OpenGL)与 QPA 蓝本直接复用 AirDraw 的产物与脚本;GSL 2.7 复用 AirEDA 的 GTK 栈;gl4es 的 libGL/libGLU 复用 AirCad 的 Maleoon 兼容补丁版。本项目新编的只有四个小件:muParser 2.3.4、Qwt5-qt5 5.2.3、qwtplot3d 与上游随包的 minigzip。liborigin 导入以 ORIGIN_IMPORT=OFF 跳过。这条"粮仓分工"让工程的重心可以集中在 scidavis 本体上。
### 3. 上游补丁五件套
native/scidavis_patches/ 与树内 __OHOS__ 守卫承载全部修改:P1 让上游 main.cpp 退出编译、入口交给 airplot_ohos_entry.cpp;P2 在三层 CMake 注入 GSL/muParser/Qwt 路径并加 musl 兼容 flags(-Wno-register、-D_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR);P3 用编译期 -include shim 头把导入/导出全链的 QFileDialog 静态调用重定向到系统 Picker 桥(airplot_filedialog_bridge.cpp);P4 关闭联网检查;P5 把 TS_PATH/DOC_PATH/PLUGIN_PATH 指向沙箱。运行期路径还处理了一个隐蔽点:fitPlugins 的 QLibrary 扫描在沙箱下拒载 .so,插件须平铺进 bundle libs 并以绝对路径访问。
### 4. 汉化与系统键盘桥
官方 scidavis_zh-cn.ts 经宿主 lrelease 生成 .qm 随 rawfile 部署,2521 条翻译装载后菜单树全中文。语言键名处理过一个小坑:qm 文件名是 zh-cn,而 Qt 默认取 QLocale 短名 zh,构造前需写死 settings 的 /General/Language=zh-cn。键盘桥是手册 §7.6.2 的裁剪版:点表格单元格经 tap 判定(按压原点与释放位移小于 20 物理px)后自动开启编辑器并弹出系统键盘,隐形宿主 TextArea 的 onChange 差分回注为 commit/退格/回车三种动作,提交目标解析到 QLineEdit/QTextEdit 直投 QInputMethodEvent;键盘避让设为 KeyboardAvoidMode.OFFSET 整页上移,保持画布尺寸恒定。
### 5. MDI 的触屏化改造
浮窗式 MDI 在触屏上问题集中:拖拽偏移、QWidgetAnimator 悬空崩溃、小标题栏难以命中。工程改为上游原生支持的 QMdiArea::TabbedView 标签页模式,并连锁修正了三处:Qt5 非虚的 showNormal 用名字隐藏拦截收口全部调用方,初始化与激活路径统一改 showMaximized 并延时 200ms 经宿主泵执行;TabbedView 下 addSubWindow 不再发 subWindowActivated,菜单填充在 initTable 内补调 customMenu;JSON 导出器改用 findChildren<QMenuBar*> 取动作最多者自愈,可见性过滤随接管语义移除。改造后建表即填充、拖拽压力扫描无崩溃。
▍五、适配中遇到的几个难点
### 难点一:宿主线程消亡引发的悬空字体引擎
点击列属性说明区必崩,反汇编定位在 QFontEngineFT::recalcAdvances,face 为 NULL——字体引擎是线程本地数据,而 QtHostThread 在 Plot_Init 返回后随 napi detached lambda 消亡。三重后果同时出现:QWidget 归属死线程导致选中零重绘、启动期创建的字体引擎 face 悬空导致冷整形段错误、全部 QueuedConnection 投递落空。修复是让宿主线程驻留为事件泵(4ms 循环,与 RenderThread 泵互斥),把死队列救活。这轮还沉淀了判定方法:在另一台仍跑旧构建的设备上同点位复现,即可洗清新改动的嫌疑。
### 难点二:双泵架构的锁纪律与模态饿死
导入卡死被系统以 THREAD_BLOCK_6S 杀进程,freeze 栈显示 MMITask 卡在 Plot_TriggerAction 等锁、RenderThread 卡在 QMessageBox::exec——模态对话框终身持有泵线程的锁,另一条泵的输入通道随之饿死。修复分三层:菜单动作转投 RenderThread 异步执行、查表改有界锁并规定"任何线程不得在 ArkTS 主线程等 g_qtWorkMutex"、宿主线程同挂 8ms 输入泵并在模态期间先 drain 指针队列再冲 WSI。经验证模态可触摸关闭,导入不再冻结。
### 难点三:菜单导出器的 id 盗用与 TabbedView 信号缺失
"点新建表格却激活了别的窗口"这类怪象的根因在菜单导出器:局部 int seq=1 每次重建都重置,动态窗口菜单 clear 后新动作按重置序列盗走旧 id。id 序列改为全局单调原子量后,连建表格 2/3/4 全部正确。换窗后菜单失效则是另一层:ArkTS 只比对项数,ForEach key 不含 id 导致节点不重建,修正为 json 原文比对加代数掺入 key。TabbedView 改造后又暴露 subWindowActivated 不发、menuBar() 在最大化接管后返回空栏等上游语义分叉,最终以"取动作最多的菜单栏 + 幂等补调 customMenu"双管齐下。
### 难点四:IME 链路的注册时序与判型
键盘桥首测无响应的根因是注册时序:QOhos_SetImeNotifyHandler 必须在 Qt 插件静态注册(libqohos 由其 dlopen)之后调用,之前 dlsym 必落空。打字即崩则是 NAPI 参数缓冲区按 argc=2 声明却读第 3 参,越界 napi_value 进入 IsStringWithoutSwitchState 校验即段错误,修正为 args[4] + 逐参 napi_typeof 验型。tap 判定与编辑器门(焦点为行编辑/文本域才放行 show)解决了拖拽框选误弹键盘,QApplication::widgetAt 返回最深子控件的问题以父链一级 qobject_cast<QAbstractItemView*> 兜住。
### 难点五:moc 缓存吞掉修复的"假成功"
一轮修改后"修复部分生效、问题复现不稳定",最终发现 MyWidget.h 的改动因 moc 生成物缓存从未重新生成而从未真正上机——多轮"构建成功"链接的是新旧混杂代码。强制重编后还撞出上游头文件在 protected slots: 段放成员变量的历史违规。工程由此强化构建纪律:原生构建必须检查真实退出码(>/dev/null 2>&1; echo ok 式假成功被明令禁止)、错误 grep 用 -i(moc 输出 "Error:" 为大写)、部署验证以新行为签名为准。
▍六、编译、安装与启动
构建入口集中在 native/,顺序为:build_thirdparty.sh 交叉编译 muParser/Qwt5-qt5/qwtplot3d/minigzip 到 native/install/;qt_platform/ohos/build_libqohos.sh 独立重建 QPA;build_scidavis_ohos.sh 编出 libscidavis_core.so;deploy_deps.sh 把 Qt5 运行时、GSL、gl4es 部署到 entry/libs/arm64-v8a/;fix_needed.py 做 NEEDED 规整。打包遵循五件套:strip:false、compressNativeLibs:false、NEEDED 去版本号、entry/libs 模块级通道、拓扑序 dlopen 清单。HAP 构建走标准流程:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
产物为 entry/build/default/outputs/default/entry-default-signed.hap,安装启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airplot.app
PORTING.md 记载的首日与后续各轮验证可作为参照:HAP 启动后 17 库拓扑 dlopen、EGL 首帧上屏,ApplicationWindow 约 350ms 完成构造;八个顶级中文菜单上屏,官方 zh-cn.qm 2521 条装载;表格单元格点击选中重绘、系统保存/打开选择器弹出、多实例与 TabbedView 均在 MatePad 11.5 上闭环。
▍七、当前功能边界
当前版本已覆盖的能力:
- 可安装 HAP、双层架构、QPA 单表面渲染、EGL 首帧与 splash 遮罩衔接;
- ApplicationWindow 主窗与 TabbedView 标签页 MDI(新建表格/矩阵、多实例、激活切换);
- 触摸选中与重绘、tap 弹键盘、IME 差分提交、键盘避让 OFFSET;
- 八个顶级中文菜单、菜单树 JSON 导出与 QAction 回注、勾选与级联;
- 系统文件选择器桥(打开/保存 .sciprj、导入图像同链路);
- 官方 2521 条简中翻译、沉浸式布局与安全区避让;
- muParser 表达式与 GSL 依赖就位,2D 绘图主链(Qwt raster)不受 3D 风险影响。
尚未覆盖或待验证的能力同样有据可查:拟合(muParser/GSL fitPlugins 扫描)与 3D 图(gl4es 翻译层)在里程碑里列为 M4 实测项,尚未完成;系统选择器的完整保存落盘与取消交互留人工补验;BACK 键收键盘、键盘避让下的焦点漂移治理、编辑格被遮挡时的滚动治理列在第五轮遗留清单;S1~S14 强制标准的逐项对照与上游 master 完整功能遍历未开始;列属性面板的历史崩溃点列为持续观察项。因此当前版本更准确的定位是"SciDAVis 鸿蒙 PC 可交互基础版"——表格、绘图与文档管理的骨架已经在真机上跑通,科学计算主链的实测验收是下一阶段的主线。
▍八、总结
SciDAVis 的移植把 Qt Widgets 时代桌面程序的两个硬骨头摆上了台面:无窗口管理器环境下的 MDI 与模态语义,以及双泵线程模型下的生命周期纪律。工程的答卷是把边界前置——入口、对话框、脚本、联网逐层替换或裁剪,再以五轮真机迭代把线程、菜单、输入、键盘这些"最后一公里"逐个收口。
从遗留清单看,后续重心很集中:完成拟合与 3D 绘图的实测验收,补齐键盘遮挡滚动与焦点稳定,然后按 S1~S14 标准清单做全面对照。每轮 ai_log/ 的方法学沉淀(坐标定式、freeze 证据链、moc 缓存纪律)已经成为这个系列工程可以复用的资产,这或许比单个功能的进度更有长期价值。
■ 应用:轻绘图(from SciDAVis)(AirPlot)
■ 定位:科学绘图与数据分析(SciDAVis)
■ 上游:SciDAVis | 路线:Qt5 5.15+QPA | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 SciDAVis
SciDAVis 是一款开源的科学数据分析与可视化程序,围绕"表格—绘图—拟合"组织工作流:数据以表格与矩阵录入,经 2D/3D 绘图呈现,再用 muParser 表达式与 GSL 数值库做曲线拟合。它采用 Qt Widgets 与 MDI 多文档界面,与 Origin 面向的实验数据处理场景高度重合,是高校与实验室里常见的轻量替代品。
鸿蒙侧工程名为 AirPlot,应用中文名"轻图示",包名 com.airplot.app,版本 1.0.0,deviceTypes 覆盖 phone、tablet 与 2in1,module.json5 中显式关闭 compressNativeLibs。移植路线为 Qt 5.15.16 加自研 ohos QPA——版本由上游 find_package(Qt5 ...) 决定,架构蓝本取自 AirDraw(Krita 的 Qt5 QPA 初代)与 AirKam(digiKam 的 Qt5 双层架构定型作)。PORTING.md 记载的首日实机验证在 MateBook Pro 2in1 与 TXZ-W20 平板上完成,后续各轮主测设备为 MatePad 11.5。
对整个移植系列而言,这个工程的样本价值在于:上游是十几万行的经典 Qt Widgets 桌面程序,没有 QML、没有现代跨平台抽象,MDI 子窗、模态对话框、QFileDialog 静态调用全部是桌面默认语义——把它们搬进无窗口管理器的单表面环境,是比"渲染出画面"深一层的问题。
▍二、先划清边界:不把编译通过当成适配完成
上游 SciDAVis 的每个交互细节都假设自己运行在有 WM 的桌面上:主窗是 ApplicationWindow,MDI 子窗可以拖拽浮动的假设、导入导出弹 QFileDialog、启动弹 QSplashScreen 与设置向导、联网检查更新。这些语义若不逐层替换,编出来的库只会得到一个能画 cannot-represent 内容的空壳。
工程在 PORTING.md 的补丁清单与风险登记册里把边界写得很清楚,当前产物的每一层对应关系如下。
层次 原项目实现(桌面 SciDAVis) 鸿蒙侧当前方案
应用入口 scidavis/src/main.cpp + QSplashScreen airplot_ohos_entry.cpp 承担入口,splash 跳过
窗口承载 X11 窗口 + WM 管理 MDI 框架窗 XComponent(TEXTURE) + ohos QPA 单表面
界面形态 MDI 浮动子窗 + Qt 原生菜单栏 QMdiArea::TabbedView 标签页 + ArkTS 菜单栏
文件对话框 QFileDialog 静态调用(导入/导出全链) -include shim 头重定向到系统 Picker 桥
数值脚本 muParser + 可选 Python 脚本 muParser 保留,Python 以 SCRIPTING_PYTHON=OFF 裁剪
3D 绘图 桌面 OpenGL(qwtplot3d,glBegin/GLU) gl4es 翻译层(Maleoon 兼容补丁版,实测项)
联网能力 启动时检查更新与下载链接 SEARCH_FOR_UPDATES=OFF、DOWNLOAD_LINKS=OFF 裁剪 Qt5Network
汉化 随包 scidavis_zh-cn.ts(364KB) 宿主 lrelease 出 .qm,rawfile 部署,2521 条装载
需要强调的是最后两行代表的取舍方式:能关掉的外部依赖直接从 CMake 层关掉,让 find_package 不再执行;不能关掉的桌面 GL 依赖用翻译层兜住。边界清晰之后,"能跑"与"不能用功能冒充已移植"才有共同的判据。
▍三、鸿蒙版的整体架构
工程是 AirKam 双层架构的又一个落地实例:ArkTS 壳管 Ability、资源部署与菜单栏,NAPI 薄壳 libplotbridge.so 直收 XComponent 输入回调,libscidavis_core.so 在 JS 主线程完成 dlopen 与 QApplication 构造,渲染与事件泵驻留在独立线程。
text
EntryAbility
├── rawfile→沙箱部署(stamp 幂等)/ 隐藏系统标题栏 / 沉浸式
└── Index.ets:XComponent(TEXTURE) + splash 遮罩 + ArkTS 菜单栏
└── NAPI:libplotbridge.so(薄壳,只链系统库;触摸/鼠标/键盘回调直收)
▼ initPlot(JS 主线程)按拓扑序 dlopen 全部依赖(RTLD_NOW|RTLD_GLOBAL)
└── libscidavis_core.so(scidavis_patches/airplot_ohos_entry.cpp)
├── QApplication(JS 主线程创建)+ ApplicationWindow(Qt MDI 主窗)
├── RenderThread:Plot_Step 泵(8ms 保底节拍 + 条件变量唤醒)
│ sendWindowSystemEvents + processEvents(16ms)
├── 指针/键盘注入:QWindowSystemInterface(原生 px,Qt 内 ÷dpr 一次)
├── 菜单桥:QMenuBar→JSON 导出 + QAction::trigger 回注
└── libscidavis(143 cpp/7.3 万行)+ Qwt5 + qwtplot3d + muParser + GSL
Qt 插件静态注册(libqohos/libqsvg/libqsvgicon 从 bundle libs dlopen 后注册)
主要目录如下:
text
AirPlot/
├── AppScope/app.json5 # 包名 com.airplot.app、应用名"轻绘图"
├── scidavis-master/ # 上游源码树(zip 展开 + 三子模块代理补齐)
├── native/
│ ├── build_thirdparty.sh # muParser/Qwt5-qt5/qwtplot3d/minigzip 交叉编译
│ ├── build_scidavis_ohos.sh # libscidavis + 壳入口 → libscidavis_core.so
│ ├── scidavis_patches/ # airplot_ohos_entry.cpp、文件对话框桥
│ ├── scidavis_build/ 与 install/ # 构建树与产物前缀
│ └── fix_needed.py # NEEDED 规整
├── ai_log/ # 每轮会话实录(yyyy-MM-dd 摘要-vN.md)
├── qa/ # 验收记录
└── entry/src/main/
├── ets/pages/Index.ets # 壳层主页与菜单栏
├── ets/utils/FileManager.ets # 沙箱文件中转
└── cpp/
├── napi_init.cpp # NAPI 桥:事件入队、pick 回调、IME 桥
├── qt_platform/ohos/ # 自研 ohos QPA(复用 AirDraw 蓝本重建)
├── ohos_deps/ 与 qt5_source/ # Qt5.15.16 运行时与源码 include 树(软链复用)
└── deploy_deps.sh # Qt5/GSL/gl4es 部署到 entry/libs
▍四、核心适配过程
### 1. 双泵架构与线程模型
QApplication 在 JS 主线程创建,而事件派发由两条泵分担:RenderThread 的 Plot_Step 以 8ms 保底节拍(条件变量可即时唤醒)执行 sendWindowSystemEvents 与 16ms 的 processEvents;指针、滚轮事件经 NAPI 无锁环入队后在泵线程统一注入,杜绝 ArkTS 主线程直调 Qt 的线程违例。输入坐标全程使用原生物理像素,Qt 内一次性除以 dpr。菜单桥把 QMenuBar 树导出为 JSON、动作经 QAction::trigger 回注,构成"ArkTS 菜单栏 + Qt 执行"的闭环。
### 2. 依赖矩阵:复用为主、新编为辅
Qt 5.15.16 运行时七模块(Core/Gui/Widgets/Svg/PrintSupport/Xml/OpenGL)与 QPA 蓝本直接复用 AirDraw 的产物与脚本;GSL 2.7 复用 AirEDA 的 GTK 栈;gl4es 的 libGL/libGLU 复用 AirCad 的 Maleoon 兼容补丁版。本项目新编的只有四个小件:muParser 2.3.4、Qwt5-qt5 5.2.3、qwtplot3d 与上游随包的 minigzip。liborigin 导入以 ORIGIN_IMPORT=OFF 跳过。这条"粮仓分工"让工程的重心可以集中在 scidavis 本体上。
### 3. 上游补丁五件套
native/scidavis_patches/ 与树内 __OHOS__ 守卫承载全部修改:P1 让上游 main.cpp 退出编译、入口交给 airplot_ohos_entry.cpp;P2 在三层 CMake 注入 GSL/muParser/Qwt 路径并加 musl 兼容 flags(-Wno-register、-D_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR);P3 用编译期 -include shim 头把导入/导出全链的 QFileDialog 静态调用重定向到系统 Picker 桥(airplot_filedialog_bridge.cpp);P4 关闭联网检查;P5 把 TS_PATH/DOC_PATH/PLUGIN_PATH 指向沙箱。运行期路径还处理了一个隐蔽点:fitPlugins 的 QLibrary 扫描在沙箱下拒载 .so,插件须平铺进 bundle libs 并以绝对路径访问。
### 4. 汉化与系统键盘桥
官方 scidavis_zh-cn.ts 经宿主 lrelease 生成 .qm 随 rawfile 部署,2521 条翻译装载后菜单树全中文。语言键名处理过一个小坑:qm 文件名是 zh-cn,而 Qt 默认取 QLocale 短名 zh,构造前需写死 settings 的 /General/Language=zh-cn。键盘桥是手册 §7.6.2 的裁剪版:点表格单元格经 tap 判定(按压原点与释放位移小于 20 物理px)后自动开启编辑器并弹出系统键盘,隐形宿主 TextArea 的 onChange 差分回注为 commit/退格/回车三种动作,提交目标解析到 QLineEdit/QTextEdit 直投 QInputMethodEvent;键盘避让设为 KeyboardAvoidMode.OFFSET 整页上移,保持画布尺寸恒定。
### 5. MDI 的触屏化改造
浮窗式 MDI 在触屏上问题集中:拖拽偏移、QWidgetAnimator 悬空崩溃、小标题栏难以命中。工程改为上游原生支持的 QMdiArea::TabbedView 标签页模式,并连锁修正了三处:Qt5 非虚的 showNormal 用名字隐藏拦截收口全部调用方,初始化与激活路径统一改 showMaximized 并延时 200ms 经宿主泵执行;TabbedView 下 addSubWindow 不再发 subWindowActivated,菜单填充在 initTable 内补调 customMenu;JSON 导出器改用 findChildren<QMenuBar*> 取动作最多者自愈,可见性过滤随接管语义移除。改造后建表即填充、拖拽压力扫描无崩溃。
▍五、适配中遇到的几个难点
### 难点一:宿主线程消亡引发的悬空字体引擎
点击列属性说明区必崩,反汇编定位在 QFontEngineFT::recalcAdvances,face 为 NULL——字体引擎是线程本地数据,而 QtHostThread 在 Plot_Init 返回后随 napi detached lambda 消亡。三重后果同时出现:QWidget 归属死线程导致选中零重绘、启动期创建的字体引擎 face 悬空导致冷整形段错误、全部 QueuedConnection 投递落空。修复是让宿主线程驻留为事件泵(4ms 循环,与 RenderThread 泵互斥),把死队列救活。这轮还沉淀了判定方法:在另一台仍跑旧构建的设备上同点位复现,即可洗清新改动的嫌疑。
### 难点二:双泵架构的锁纪律与模态饿死
导入卡死被系统以 THREAD_BLOCK_6S 杀进程,freeze 栈显示 MMITask 卡在 Plot_TriggerAction 等锁、RenderThread 卡在 QMessageBox::exec——模态对话框终身持有泵线程的锁,另一条泵的输入通道随之饿死。修复分三层:菜单动作转投 RenderThread 异步执行、查表改有界锁并规定"任何线程不得在 ArkTS 主线程等 g_qtWorkMutex"、宿主线程同挂 8ms 输入泵并在模态期间先 drain 指针队列再冲 WSI。经验证模态可触摸关闭,导入不再冻结。
### 难点三:菜单导出器的 id 盗用与 TabbedView 信号缺失
"点新建表格却激活了别的窗口"这类怪象的根因在菜单导出器:局部 int seq=1 每次重建都重置,动态窗口菜单 clear 后新动作按重置序列盗走旧 id。id 序列改为全局单调原子量后,连建表格 2/3/4 全部正确。换窗后菜单失效则是另一层:ArkTS 只比对项数,ForEach key 不含 id 导致节点不重建,修正为 json 原文比对加代数掺入 key。TabbedView 改造后又暴露 subWindowActivated 不发、menuBar() 在最大化接管后返回空栏等上游语义分叉,最终以"取动作最多的菜单栏 + 幂等补调 customMenu"双管齐下。
### 难点四:IME 链路的注册时序与判型
键盘桥首测无响应的根因是注册时序:QOhos_SetImeNotifyHandler 必须在 Qt 插件静态注册(libqohos 由其 dlopen)之后调用,之前 dlsym 必落空。打字即崩则是 NAPI 参数缓冲区按 argc=2 声明却读第 3 参,越界 napi_value 进入 IsStringWithoutSwitchState 校验即段错误,修正为 args[4] + 逐参 napi_typeof 验型。tap 判定与编辑器门(焦点为行编辑/文本域才放行 show)解决了拖拽框选误弹键盘,QApplication::widgetAt 返回最深子控件的问题以父链一级 qobject_cast<QAbstractItemView*> 兜住。
### 难点五:moc 缓存吞掉修复的"假成功"
一轮修改后"修复部分生效、问题复现不稳定",最终发现 MyWidget.h 的改动因 moc 生成物缓存从未重新生成而从未真正上机——多轮"构建成功"链接的是新旧混杂代码。强制重编后还撞出上游头文件在 protected slots: 段放成员变量的历史违规。工程由此强化构建纪律:原生构建必须检查真实退出码(>/dev/null 2>&1; echo ok 式假成功被明令禁止)、错误 grep 用 -i(moc 输出 "Error:" 为大写)、部署验证以新行为签名为准。
▍六、编译、安装与启动
构建入口集中在 native/,顺序为:build_thirdparty.sh 交叉编译 muParser/Qwt5-qt5/qwtplot3d/minigzip 到 native/install/;qt_platform/ohos/build_libqohos.sh 独立重建 QPA;build_scidavis_ohos.sh 编出 libscidavis_core.so;deploy_deps.sh 把 Qt5 运行时、GSL、gl4es 部署到 entry/libs/arm64-v8a/;fix_needed.py 做 NEEDED 规整。打包遵循五件套:strip:false、compressNativeLibs:false、NEEDED 去版本号、entry/libs 模块级通道、拓扑序 dlopen 清单。HAP 构建走标准流程:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
产物为 entry/build/default/outputs/default/entry-default-signed.hap,安装启动:
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airplot.app
PORTING.md 记载的首日与后续各轮验证可作为参照:HAP 启动后 17 库拓扑 dlopen、EGL 首帧上屏,ApplicationWindow 约 350ms 完成构造;八个顶级中文菜单上屏,官方 zh-cn.qm 2521 条装载;表格单元格点击选中重绘、系统保存/打开选择器弹出、多实例与 TabbedView 均在 MatePad 11.5 上闭环。
▍七、当前功能边界
当前版本已覆盖的能力:
- 可安装 HAP、双层架构、QPA 单表面渲染、EGL 首帧与 splash 遮罩衔接;
- ApplicationWindow 主窗与 TabbedView 标签页 MDI(新建表格/矩阵、多实例、激活切换);
- 触摸选中与重绘、tap 弹键盘、IME 差分提交、键盘避让 OFFSET;
- 八个顶级中文菜单、菜单树 JSON 导出与 QAction 回注、勾选与级联;
- 系统文件选择器桥(打开/保存 .sciprj、导入图像同链路);
- 官方 2521 条简中翻译、沉浸式布局与安全区避让;
- muParser 表达式与 GSL 依赖就位,2D 绘图主链(Qwt raster)不受 3D 风险影响。
尚未覆盖或待验证的能力同样有据可查:拟合(muParser/GSL fitPlugins 扫描)与 3D 图(gl4es 翻译层)在里程碑里列为 M4 实测项,尚未完成;系统选择器的完整保存落盘与取消交互留人工补验;BACK 键收键盘、键盘避让下的焦点漂移治理、编辑格被遮挡时的滚动治理列在第五轮遗留清单;S1~S14 强制标准的逐项对照与上游 master 完整功能遍历未开始;列属性面板的历史崩溃点列为持续观察项。因此当前版本更准确的定位是"SciDAVis 鸿蒙 PC 可交互基础版"——表格、绘图与文档管理的骨架已经在真机上跑通,科学计算主链的实测验收是下一阶段的主线。
▍八、总结
SciDAVis 的移植把 Qt Widgets 时代桌面程序的两个硬骨头摆上了台面:无窗口管理器环境下的 MDI 与模态语义,以及双泵线程模型下的生命周期纪律。工程的答卷是把边界前置——入口、对话框、脚本、联网逐层替换或裁剪,再以五轮真机迭代把线程、菜单、输入、键盘这些"最后一公里"逐个收口。
从遗留清单看,后续重心很集中:完成拟合与 3D 绘图的实测验收,补齐键盘遮挡滚动与焦点稳定,然后按 S1~S14 标准清单做全面对照。每轮 ai_log/ 的方法学沉淀(坐标定式、freeze 证据链、moc 缓存纪律)已经成为这个系列工程可以复用的资产,这或许比单个功能的进度更有长期价值。
楼主
8 天前