一、写在前面

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

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

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

这篇文章记录的是 Avogadro 2 在 HarmonyOS PC / 鸿蒙真机环境中的一次适配过程。

Avogadro 2 是计算化学、分子建模、教学和材料科学领域常用的一款分子编辑器 / 可视化工具。它的核心用途是把分子结构(原子、化学键、坐标)以三维方式渲染出来,支持球棍模型、空间填充模型、线框模型等多种表示方式,并能读写 XYZ / CJSON / CML / PDB 等多种化学文件格式。

原版 Avogadro 2 是一个 C++ / Qt 桌面项目,主体由 Qt Widgets 桌面界面、Qt-free 的可移植核心库(avogadro/coreavogadro/io),以及一套基于桌面 OpenGL 4.0 的硬件加速渲染管线(avogadro/rendering)组成。它和 Electron 项目完全不一样,也不是 ArkUI 原生项目。适配鸿蒙 PC 时,关键问题不是“前端资源路径怎么加载”,而是:

  • 怎样让一个 Qt Widgets 桌面程序进入鸿蒙 Stage 模型;
  • 怎样让 native libentry.so 作为 HAP 的 native library 被 QPA 插件加载并启动;
  • 怎样让 Qt 界面绘制到鸿蒙 PC 窗口(XComponent)中;
  • 怎样把 Qt for Harmony SDK、QPA 插件和 Qt 动态库打进 HAP;
  • 桌面 OpenGL 4.0 渲染在鸿蒙上只有 OpenGL ES 3.x,没有桌面 GL,三维渲染怎么落地;
  • 怎样先做出一个可运行、可旋转、可缩放、可切换分子的 Avogadro Harmony 可视化版本。

这条路线的目标很明确:先把 Avogadro 作为一个 Qt Widgets 应用稳定跑到鸿蒙 PC 真机上,并能交互式查看分子三维结构,再逐步评估完整可移植核心(avogadro/core + avogadro/io)和 OpenGL → OpenGL ES 渲染栈的深度迁移。

在这里插入图片描述

二、项目背景:Avogadro 是 Qt/C++ 桌面应用

适配前先看项目性质。

Avogadro 2 不是 Electron 应用,也不是 WebView 应用。它的主体是 C++ / Qt 桌面程序,原始桌面版本主要面向 Windows、macOS 和 Linux。整个代码库大致分成三层:

  • 可移植核心avogadro/coreavogadro/ioavogadro/calcavogadro/quantumio。这部分是 Qt-free、GL-free 的,只依赖 Eigen 和几个 header-only 的第三方库(nlohmannpugixmlstructtoml++cppoptlib),天然适合交叉编译到 arm64-ohos。
  • 渲染层avogadro/rendering,基于桌面 OpenGL 4.0 / GLSL #version 400 / GLEW。鸿蒙上只有 OpenGL ES 3.x / EGL / Vulkan,没有桌面 GL,这一层是迁移里最硬的骨头。
  • Qt Widgets 桌面 UI:窗口、工具栏、文件对话框等。

这次适配新增的鸿蒙工程目录是:

ohos_avogadrolibs/
├── avogadro/            # 上游核心库(core / io / rendering ...)
├── thirdparty/          # header-only 第三方依赖
├── CMakeLists.txt       # 上游桌面构建(保持不动)
└── harmony_pc/          # 鸿蒙侧新增工程,桌面构建完全不受影响
    ├── AppScope/
    │   └── app.json5                  # bundleName: org.openchemistry.avogadro
    ├── build-profile.json5            # app 级签名 / product 配置
    ├── hvigorfile.ts / oh-package.json5
    ├── qtforharmony_sdk/              # 工程自带的 Qt 5.15 for HarmonyOS(arm64-v8a)
    └── entry/
        ├── build-profile.json5        # externalNativeOptions -> CMake
        ├── libs/arm64-v8a/            # 打进 HAP 的预编译 .so(QPA 插件、ssl、media)
        └── src/main/
            ├── module.json5           # deviceTypes: 2in1 / tablet
            ├── ets/                    # ArkTS 桥
            │   ├── abilitystage/MyAbilityStage.ets
            │   ├── entryability/EntryAbility.ets
            │   └── pages/Index.ets
            └── cpp/
                ├── CMakeLists.txt
                └── avogadro_ohos_shell.cpp   # 分子查看器 shell

关键约束是:上游 Avogadro 源码保持不动,所有鸿蒙相关的东西都放在 harmony_pc/,所以桌面构建(cmake -S . -B build)完全不受影响。

当前适配里最核心的文件是:

harmony_pc/entry/src/main/cpp/avogadro_ohos_shell.cpp   # Qt Widgets 分子查看器
harmony_pc/entry/src/main/cpp/CMakeLists.txt            # SHELL / FULL 双模式构建
harmony_pc/entry/build-profile.json5                    # CMake 参数 / abiFilters
harmony_pc/AppScope/app.json5                           # 包名与应用信息

在这里插入图片描述

三、路线选择:借鉴 TupiTube / Krita / Natron 的 Qt for Harmony 思路

适配 Avogadro 时,参考了之前几个 Qt → HarmonyOS 工程:ok-tupitube.desk-ohosok-krita-ohosok-Natron-ohos

这里要特别区分:参考的是这些项目的 Qt for Harmony SDK、Stage 工程组织方式和 native Qt 启动管线,而不是把它们的业务代码拿来跑。最终真机上安装的包始终是 Avogadro:

bundleName: org.openchemistry.avogadro
Ability:    EntryAbility
Module:     entry

AppScope/app.json5 中应用信息配置为:

{
  "app": {
    "bundleName": "org.openchemistry.avogadro",
    "vendor": "openchemistry",
    "versionCode": 2000000,
    "versionName": "2.0.0",
    "icon": "$media:layered_image",
    "label": "$string:app_name"
  }
}

鸿蒙工程的设备类型面向 PC / 平板:

"deviceTypes": [
  "2in1",
  "tablet"
]

Qt SDK 通过 entry/build-profile.json5 的 CMake 参数传入。Avogadro 这次特意用的是工程自带的 Qt SDK(不引用其他项目的 SDK):

"externalNativeOptions": {
  "path": "./src/main/cpp/CMakeLists.txt",
  "arguments": "-DQT_PREFIX=qtforharmony_sdk -DAVOGADRO_FULL_APP=AUTO",
  "cppFlags": "",
  "abiFilters": ["arm64-v8a"]
}

这里 QT_PREFIX=qtforharmony_sdk 是相对路径,CMake 里会把它解析成 harmony_pc/qtforharmony_sdk 的绝对路径,并校验 lib/cmake/Qt5/Qt5Config.cmake 是否存在。这一点是整个适配能跑起来的基础——QT_PREFIX 必须指向 Qt for Harmony SDK,否则 CMake 找不到 Qt5Config.cmake、Qt Widgets 和 QPA 平台插件。工程内置的是 Qt 5.15.12 for HarmonyOS(arm64-v8a,OHOS SDK 18)

另一个关键参数是 AVOGADRO_FULL_APP,它决定了构建模式(这是这次适配的核心设计):

模式 取值 编译什么 状态
SHELL AUTO / OFF(默认) 只编 avogadro_ohos_shell.cpp——一个自包含的 Qt Widgets 分子查看器,只链接 Qt5 Core/Gui/Widgets/Svg/Network/OpenGL,不依赖 avogadrolibs 源码 ✅ 可运行
FULL ON 把 Avogadro 的 Qt-free 核心(avogadro/core + avogadro/io)编进 libentry.so,用真实文件解析驱动查看器。需要 -DAVOGADRO_DEPS_PREFIX(Eigen3)+ 生成头文件 🚧 进行中

本次选择的第一阶段路线是:

  1. 不重写 ArkUI 主界面;
  2. 不一次性迁完整核心库和 OpenGL 渲染栈;
  3. 先用 Harmony Stage 工程承载 Qt Widgets;
  4. 用 C++ 写一个自包含的分子查看器 SHELL(CPU 渲染,绕过 OpenGL ES 移植);
  5. 真机验证窗口、工具栏、分子三维显示、旋转、缩放、切换表示法;
  6. 再根据后续目标决定是否继续迁完整核心和 GLES 渲染。

这条路线适合快速验证 Qt 桌面工具在鸿蒙 PC 上的窗口、输入、构建、安装和运行链路。

在这里插入图片描述

四、鸿蒙工程壳:Stage Ability + XComponent + QPA → libentry.so

鸿蒙侧仍然是标准 Stage 工程。整条启动管线是这样串起来的:

HarmonyOS 启动 org.openchemistry.avogadro(EntryAbility)
  -> EntryAbility.onWindowStageCreate() 加载 pages/Index
  -> Index.ets 构建一个 XComponent(libraryname: plugins_platforms_qopenharmony)
  -> EntryAbility 调用 qpa.startQtApplication(this)
  -> QPA 插件在 XComponent 上创建 EGL/GLES 表面,并 dlopen() libentry.so
  -> 解析 libentry.so 导出的 extern "C" qtmain
  -> qtmain -> main() -> QApplication -> Avogadro 主窗口渲染进这个表面

Index.ets 里的核心就是一个 XComponent,它的 libraryname 指向 QPA 平台插件:

XComponent({
  id: this.windowId,
  type: XComponentType.NODE,
  libraryname: 'plugins_platforms_qopenharmony'
})
  .width('100%')
  .height('100%');

MyAbilityStage.ets 在工程启动时把 QPA 插件挂上去,并通过 onAcceptWant 返回主窗口实例 key:

onCreate(): void {
  qpa.attachAbilityStage(this);
}

onAcceptWant(want: Want): string {
  // 首次启动返回 avogadro_main_window
  ...
}

EntryAbility.ets 在窗口 stage 创建后,真正拉起 Qt 应用:

import qpa from 'libplugins_platforms_qopenharmony.so';

async onWindowStageCreate(windowStage: Window.WindowStage) {
  ...
  await this.launchParamSet();
  this.startQtApplication();
}

private startQtApplication() {
  qpa.startQtApplication(this);   // QPA 插件随后 dlopen libentry.so 并调用 qtmain
}

native 侧的 CMakeLists.txt 把入口编译成 entry(即 libentry.so)。SHELL 模式下只编一个源文件:

add_library(entry SHARED
    "${CMAKE_CURRENT_SOURCE_DIR}/avogadro_ohos_shell.cpp")

target_compile_features(entry PRIVATE cxx_std_17)
target_compile_definitions(entry PRIVATE
    APP_NAME="Avogadro"
    OPENHARMONY
    NAPI_DISABLE_CPP_EXCEPTIONS)
set_target_properties(entry PROPERTIES BUILD_RPATH "$ORIGIN")

target_link_libraries(entry PRIVATE
    Qt5::Core
    Qt5::Gui
    Qt5::Widgets
    Qt5::Svg
    Qt5::Network
    Qt5::OpenGL)

这里有一个踩过的坑写进了注释:链接 Qt5::Gui 会让 hvigor 的 collectAllLibs 把 Qt 的图片格式插件(qgif/qico/qjpeg/qsvg)一起打包,而 qsvg 插件又依赖 libQt5Svg.so,所以必须同时链接 Qt5::Svg,否则 QPA 插件加载 libentry.so 时会失败,真机上表现为白窗口。另外 QPA 平台插件本身是被 ArkTS 加载的,不需要在这里链接。

C++ 入口通过导出的 qtmain 被 QPA 插件调用:

int main(int argc, char** argv)
{
  QApplication app(argc, argv);
  app.setApplicationName(QStringLiteral("Avogadro"));
  app.setStyle(QStyleFactory::create(QStringLiteral("Fusion")));
  app.setStyleSheet(QLatin1String(kStyleSheet));   // Avogadro 风格深色主题
  auto* w = new MainWindow();
  w->show();
  return app.exec();
}

#if defined(Q_OS_OPENHARMONY)
// Qt OpenHarmony 平台插件 dlopen() libentry.so 后调用 "qtmain"
extern "C" int qtmain(int argc, char* argv[])
{
  return main(argc, argv);
}
#endif

五、SHELL 分子查看器:先做可运行的 MVP

完整核心库 + OpenGL 渲染栈的迁移不是第一阶段目标。为了先把真机可用链路跑通,这次实现了一个自包含的 Qt Widgets 分子查看器 avogadro_ohos_shell.cpp

它的主窗口 MainWindow 是一个标准 QMainWindow:顶部一条工具栏,中间一块自绘的分子画布 MoleculeView(继承 QWidget),底部一个状态栏。

当前 SHELL 查看器的能力包括:

  • 内置 5 种分子:水 (H₂O)甲烷 (CH₄)氨 (NH₃)乙醇 (C₂H₆O)苯 (C₆H₆)
  • 三种表示法:球棍(Ball and stick)空间填充(Space filling)线框(Wireframe)
  • CPK / Jmol 元素配色、按共价半径定球大小、基于距离的化学键自动识别(bond perception);
  • 拖拽旋转滚轮缩放Reset view 复位视角;
  • 画家算法的深度排序 + 雾化(远处变暗),让三维层次更自然;
  • 打开 .xyz 文件QFileDialog,真机上后续可接 OHOS 文件选择器);
  • 状态栏显示分子式 + 原子数 + 键数

画布的交互逻辑很直接——滚轮缩放、左键拖拽改 yaw/pitch,重绘时用 QMatrix4x4 做旋转再投影到屏幕:

void wheelEvent(QWheelEvent* e) override {
  const double steps = e->angleDelta().y() / 120.0;
  m_zoom *= std::pow(1.15, steps);
  m_zoom = qBound(0.2, m_zoom, 8.0);
  update();
}

void mouseMoveEvent(QMouseEvent* e) override {
  if (!m_dragging) return;
  const QPoint d = e->pos() - m_last;
  m_last = e->pos();
  m_yaw   += d.x() * 0.01;
  m_pitch += d.y() * 0.01;
  m_pitch  = qBound(-M_PI / 2 + 0.01, m_pitch, M_PI / 2 - 0.01);
  update();
}

工具栏里两个下拉框分别切换“当前分子”和“表示法”:

m_repCombo->addItems({QStringLiteral("Ball and stick"),
                      QStringLiteral("Space filling"),
                      QStringLiteral("Wireframe")});

六、为什么先用 QPainter 做 CPU 渲染,而不是 OpenGL

这是 Avogadro 适配里最需要解释的一个决策。

原版 Avogadro 的三维渲染(avogadro/rendering)是桌面 OpenGL 4.0:GLSL #version 400、用 GLEW 加载函数、球用 impostor、gl_FragData / glBindFragDataLocation 等等。而 HarmonyOS 上只有 OpenGL ES 3.x / EGL / Vulkan,没有桌面 GL。直接把渲染层搬过来是跑不起来的,需要一次完整的 GL 4.0 → GLES 3.0 移植,已核实的“必改清单”包括:

  • sized internal formattexture2d.cpp / solidpipeline.cpp 用了无尺寸的 GL_DEPTH_COMPONENT / GL_RGB,GLES3 要求带尺寸的格式并匹配类型;
  • GLSL ES#version 400#version 300 es,补 precision 限定符,把 gl_FragData / glBindFragDataLocation 换成 layout(location=) 输出;
  • 球 impostor 用了 gl_FragDepth(GLES3 合法,但会关掉 early-Z,需在目标 GPU 上实测);圆柱是真三角网格,可原样移植;
  • glLineWidth > 1 在 GLES3 core 不支持,线框几何要么丢掉,要么用四边形画;
  • 丢掉 GLEW(glewInit),改用 OHOS NDK 的 EGL/GLES 头;
  • EGL context 是线程亲和的,所有 GL 调用要留在 XComponent 表面线程上。

为了先让应用跑起来、可演示,SHELL 直接绕开了这一整套:三维场景用 QPainter 在 CPU 上做深度排序光栅化(画家算法),所以这个 shell 不需要任何 OpenGL ES shader 移植就能在真机上交互运行。这是“先能跑、再优化”的典型取舍——OpenGL → GLES 的移植留给后续 Phase 3。

七、构建、安装和真机运行

本次工程路径:

/XM/ohos_avogadrolibs/harmony_pc

构建时需要指定 DevEco SDK 环境变量,否则可能遇到 hvigor / SDK 版本不匹配问题,例如:

(0 , hvigor_1.WithParamReplacement) is not a function

最终使用的构建命令是:

cd /XM/ohos_avogadrolibs/harmony_pc

env DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
OHOS_BASE_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony \
/Applications/DevEco-Studio.app/Contents/tools/node/bin/node \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js \
--mode module -p module=entry -p product=default assembleHap \
--analyze=normal --parallel --incremental --no-daemon --stacktrace

native 部分的 CMake 构建由 entry/build-profile.json5 → externalNativeOptions 驱动,会把 -DQT_PREFIX=qtforharmony_sdk -DAVOGADRO_FULL_APP=AUTO 传给 CMakeLists.txt,只编 arm64-v8a。

签名后的 HAP 路径:

harmony_pc/entry/build/default/outputs/default/entry-default-signed.hap

安装到真机:

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

启动应用:

/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa start \
-b org.openchemistry.avogadro \
-a EntryAbility

强停应用:

/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa force-stop org.openchemistry.avogadro

查看安装信息(确认包名 / ABI / SDK 版本):

/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell bm dump -n org.openchemistry.avogadro

关键输出应包含:

bundleName: org.openchemistry.avogadro
versionName: 2.0.0
cpuAbi:     arm64-v8a

在这里插入图片描述

八、真机验证流程

最终真机验证按下面这条路径走:

安装 HAP
-> 启动 Avogadro(aa start)
-> 默认加载乙醇(Ethanol)球棍模型
-> 用 "Molecule:" 下拉框切换到苯(Benzene)等其他分子
-> 用 "View:" 下拉框切换 Ball and stick / Space filling / Wireframe
-> 鼠标拖拽旋转、滚轮缩放
-> 观察底部状态栏的分子式 + 原子数 + 键数
-> Reset view 复位

切换表示法和切换分子是验证“工具栏交互 + 重绘”是否正常的最直接方式;旋转和缩放则验证鼠标 / 滚轮事件是否正确传到了 Qt 窗口。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

九、当前版本边界和后续方向

这次适配完成的是 Avogadro 在鸿蒙 PC 真机上的第一阶段可运行版本(SHELL)。它已经解决了几个关键问题:

  • HarmonyOS Stage 工程接入;
  • 工程自带 Qt for Harmony SDK(Qt 5.15.12 / arm64-v8a)接入;
  • ArkTS → XComponent → QPA → libentry.soqtmain 的完整启动管线;
  • Qt Widgets native 应用启动与窗口显示;
  • 链接 Qt5::Svg 解决 qsvg 插件依赖导致的白窗口问题;
  • Avogadro 包名和应用信息替换;
  • HAP 构建、签名、安装;
  • 真机三维分子显示、拖拽旋转、滚轮缩放、表示法切换、分子切换;
  • 用 CPU QPainter 渲染绕开 OpenGL ES 移植,先把应用跑起来。

后续方向(roadmap):

  • Phase 2 — 接入可移植核心(-DAVOGADRO_FULL_APP=ON:把 avogadro/core + avogadro/io 编进 libentry.so,用 FileFormatManager 加载真实 XYZ / CJSON / CML / PDB 文件替换内置分子。前提是为 arm64-ohos 提供 Eigen3-DAVOGADRO_DEPS_PREFIX),并产出源码 include 的生成头文件*export.havogadro/core/version.h、元素 / 空间群数据表)。
  • Phase 3 — 渲染移植(OpenGL 4.0 → OpenGL ES 3.0):按第六节的“必改清单”把 avogadro/rendering 移到 GLES3 / GLSL-ES,换用 EGL/GLES,丢掉 GLEW。
  • Phase 4 — 原生外壳(可选):长期可考虑彻底脱 Qt,把移植后的 GLES 渲染器直接挂进 ArkUI 的 XComponent(NAPI),用 ArkTS 工具栏 + DocumentViewPicker 文件选择器,做出更原生的鸿蒙体验。

十、总结

这次 Avogadro 适配不是一次简单的套壳,也不是 Electron 项目的资源搬运。它更接近一次 Qt 桌面工具在鸿蒙 PC 上的 native 运行验证,而且因为 Avogadro 自带一套桌面 OpenGL 渲染栈,它比一般 Qt Widgets 工具更能体现“鸿蒙没有桌面 GL”这个现实约束下的工程取舍。

整个过程可以概括为:

识别原项目技术栈(Qt Widgets + 可移植核心 + 桌面 OpenGL)
-> 新增 harmony_pc Stage 工程,上游源码保持不动
-> CMake 接入工程自带 Qt for Harmony SDK(Core/Gui/Widgets/Svg/Network/OpenGL)
-> 设计 SHELL / FULL 双模式,先编可运行的 SHELL
-> 用 QPainter CPU 渲染绕开 OpenGL ES 移植
-> 替换包名和应用信息
-> 构建签名 HAP
-> 安装到鸿蒙 PC 真机验证三维分子显示与交互

最终结果是,一个以 org.openchemistry.avogadro 为包名的 Avogadro Harmony PC 应用,可以在真机上加载内置分子、以球棍 / 空间填充 / 线框三种方式三维显示,并支持拖拽旋转和滚轮缩放。虽然它还不是完整上游 Avogadro(核心库与 OpenGL 渲染栈仍在迁移中),但已经具备了第一阶段可运行、可演示、可继续扩展的基础。

Logo

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

更多推荐