鸿蒙PC桌面端适配 wxWidgets 3.3.3:先把 wxBase 做稳,再用 Broadway 点亮 wxGTK 窗口
欢迎加入开源鸿蒙PC社区
欢迎加入开源鸿蒙PC社区:Harmony PC 开发者社区
欢迎在PC社区平台申请新建项目:OpenHarmony PC Developer - 开源代码托管,代码协作 - AtomGit
如有项目源码,可上传至 AtomGit 仓库,并在博文内附上仓库链接。
鸿蒙PC桌面端适配 wxWidgets 3.3.3:先把 wxBase 做稳,再用 Broadway 点亮 wxGTK 窗口
写在前面
在鸿蒙PC桌面端移植跨平台 GUI 框架,最容易出现的误区是“能编译就等于能用”。wxWidgets 的代码量很大,wxCore、窗口后端、字体、事件循环和 wxBase 并不是一个难度层级。本文记录 wxWidgets 3.3.3 在 HarmonyOS PC 上的两步适配:第一步先让 wxBase 的字符串、URI、内存缓冲区、初始化和基础工具类形成可重复的静态库消费闭环;第二步在没有 X11/Wayland 桌面服务器的鸿蒙PC上,用 GTK Broadway 路线把真实的 wxGTK 窗口跑起来,并在签名 HAP 真机上完成 Base 429、GUI 467、Drawing 1 共 897 项目标端测试。两步的证据分开归档,本文不把其中任何一步的能力挪用到另一步头上。
本文使用的上游版本是 3.3.3,源码来自 wxWidgets 官方 Release,当前配方版本为 3.3.3.2。适配配方和测试文件保存在 AtomGit 仓库 中。版本和源码摘要应以仓库中的 conandata.yml 为准;下载后先校验 SHA-256,再进入构建,避免把缓存中的旧源码误当成当前版本。
适配范围与构建选择
wxBase 部分采用 CMake、静态库、无 GUI 变体:目标是让 wxBase 的公共头文件和基础运行时先在鸿蒙PC上稳定下来。Conan 配方使用 CMakeToolchain 和 CMakeDeps 生成构建文件,编译器和 sysroot 由 HarmonyOS profile 提供,构建脚本不依赖未定义的 ${CC} 或 ${CXX} 环境变量。
GUI 部分是另一条构建线:wxBUILD_TOOLKIT=gtk3、wxUSE_GUI=ON、全静态,工具链换成 DevEco Studio 的 ohos.toolchain.cmake(OHOS_ARCH=arm64-v8a),GTK3 依赖闭包(GTK 3.24.43、Pango、Cairo、HarfBuzz、gdk-pixbuf、glib 等)由 Conan 统一供给。鸿蒙PC上没有 OpenGL 桌面栈和媒体服务,因此 wxUSE_OPENGL、wxUSE_WEBVIEW、wxUSE_MEDIACTRL 全部关闭,先验证控件、事件和绘制,不引入链接不到的平台后端。
跨平台测试中,文件系统和编码能力不能用一个平台宏粗暴替代。适配代码采用 POSIX 或运行时能力探测:根目录不可枚举、系统临时目录不可写、符号链接、Unix socket、FIFO、部分 locale 数据、特定编码和计时调度能力缺失时,测试输出带有明确的 WX_EXPECTED_SKIP 原因。能力存在时,原测试照常执行;能力缺失时记录真实跳过,而不是把跳过伪装成通过。
一个典型的鸿蒙PC边界是 /tmp。某些设备上的系统临时目录可能是只读或受限路径,直接让测试写入会得到误导性的失败。配方把临时文件切换到任务拥有的可写目录,并在测试中保留失败原因。类似地,符号链接、Unix socket 和 locale 排序不是“编译器问题”,应该用能力结果表达,而不是增加无意义的 #ifdef __OHOS__ 分支——当前补丁集里没有任何 OHOS 条件编译宏。
最小可运行消费者
下面的程序来自本版本 test_package 的同一思路,调用了 wxBase 的初始化、UTF-8 字符串、分词、URI 和内存缓冲区接口。它不创建窗口,适合先在鸿蒙PC桌面端验证包的基础 ABI 和运行时环境。
#include <wx/init.h>
#include <wx/memory.h>
#include <wx/string.h>
#include <wx/tokenzr.h>
#include <wx/uri.h>
#include <wx/version.h>
#include <iostream>
int main() {
wxInitializer initializer;
if (!initializer.IsOk()) {
std::cerr << "wx init failed\n";
return 1;
}
const wxString text = wxString::FromUTF8("OpenHarmony PC");
if (text.Upper() != "OPENHARMONY PC") return 2;
wxStringTokenizer tokens(text, " ");
if (tokens.CountTokens() != 2 ||
tokens.GetNextToken() != "OpenHarmony") return 3;
const wxURI uri("https://www.wxwidgets.org/docs/");
if (uri.GetScheme() != "https" ||
uri.GetServer() != "www.wxwidgets.org") return 4;
wxMemoryBuffer buffer;
buffer.AppendData("wx", 2);
if (buffer.GetDataLen() != 2) return 5;
if (wxMAJOR_VERSION != 3 || wxMINOR_VERSION != 3 ||
wxRELEASE_NUMBER != 3) return 6;
std::cout << "wxWidgets 3.3.3 consumer test passed\n";
return 0;
}
在实际工程中,建议让 Conan 生成 CMakeDeps 文件,然后用包导出的目标链接,而不是手写库文件路径。将上面的程序保存为 test.cpp,并配套下面的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
project(wxwidgets_consumer LANGUAGES CXX)
find_package(wxWidgets REQUIRED CONFIG)
add_executable(wxwidgets_consumer test.cpp)
target_link_libraries(wxwidgets_consumer PRIVATE wxWidgets::wxWidgets)
同一目录还需要一个最小的 consumer conanfile.py:
from conan import ConanFile
class WxWidgetsConsumer(ConanFile):
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeDeps", "CMakeToolchain", "VirtualRunEnv"
def requirements(self):
self.requires("wxwidgets/3.3.3")
再执行以下流程,具体 profile 名称按本机的鸿蒙PC工具链调整:
OHOS_PROFILE=ohos-aarch64 # replace with an existing OHOS/AArch64 profile
conan install . -pr:h="$OHOS_PROFILE" -pr:b=default --build=never --output-folder=build
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
cmake --build build --parallel 2
--build=never 是为了让未发布依赖立即暴露;只有在本地探索且明确记录边界时,才可另行使用 --build=missing。正式构建时应确认 toolchain 文件确实存在,并让消费者 ELF 在运行前完成鸿蒙PC要求的签名;交叉编译阶段只能证明目标文件生成,不能替代设备运行。
GUI 路线:为什么是 wxGTK 加 Broadway
鸿蒙PC桌面不提供 X11 或 Wayland 兼容服务器,wxWidgets 上游也没有 OpenHarmony 窗口端口,所以把 wxGTK 编出来只是第一步,还需要一个“显示服务器”。本文选择 GTK 自带的 Broadway 后端:broadwayd 在应用内监听 127.0.0.1:18085,把 GDK 的绘制指令翻译成 WebSocket 加 Canvas 指令;ArkUI 页面里放一个 Web 组件加载这个地址,真实的 wxGTK 控件就显示在鸿蒙PC应用窗口里。
要把边界说清楚:这不是 HarmonyOS 原生窗口后端。wx 窗口不进入系统窗口栈,输入经过 ArkWeb 而不是系统输入服务。但控件创建、布局、事件分发和绘制走的都是真实的 wxWidgets/GTK 代码路径,比任何截图或桩程序都更能证明 wxCore 在鸿蒙PC上的实际行为。
整个 GUI 验证装在一个签名 HAP 里,进程结构是“一个 ArkUI 前端加三个 native 子进程”:
Index.ets是 ArkUI 前端:负责启动/停止按钮、Web 显示区,以及把 NotoSansCJK 字体、glib schema 和上游测试数据从 rawfile 分发到应用私有目录;- NAPI 桥用
OH_Ability_StartNativeChildProcess依次拉起libwx_broadway.so:BroadwayMain(显示服务)和libwx_gui.so:WxMain(wxWidgets 程序),并注册子进程退出回调; - 测试模式下同一入口还能拉起
libwx_test_base.so、libwx_test_gui.so、libwx_test_drawing.so三个 Catch2 测试子进程。
GUI 子进程的核心代码如下(节选自仓库,省略了布局诊断打印)。注意 GDK_BACKEND=broadway 和 BROADWAY_DISPLAY 必须在 wxEntry 之前写进环境:
#include "child_environment.h"
#include <wx/wx.h>
class GuiApp : public wxApp {
public:
bool OnInit() override {
auto* frame = new wxFrame(nullptr, wxID_ANY,
"wxWidgets 3.3.3 - HarmonyOS Broadway",
wxDefaultPosition, wxSize(800, 520));
auto* panel = new wxPanel(frame);
auto* layout = new wxBoxSizer(wxVERTICAL);
auto* label = new wxStaticText(panel, wxID_ANY,
wxString::FromUTF8("wxWidgets GUI · HarmonyOS PC"));
auto* edit = new wxTextCtrl(panel, wxID_ANY,
wxString::FromUTF8("中文输入测试 / Hello wxWidgets"));
auto* button = new wxButton(panel, wxID_ANY,
wxString::FromUTF8("点击测试事件"));
auto* result = new wxStaticText(panel, wxID_ANY, "Clicks: 0");
auto* check = new wxCheckBox(panel, wxID_ANY,
wxString::FromUTF8("复选框状态测试"));
layout->Add(label, 0, wxALL | wxEXPAND, 16);
layout->Add(edit, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 16);
layout->Add(button, 0, wxLEFT | wxRIGHT | wxBOTTOM, 16);
layout->Add(result, 0, wxLEFT | wxRIGHT | wxBOTTOM, 16);
layout->Add(check, 0, wxLEFT | wxRIGHT | wxBOTTOM, 16);
panel->SetSizer(layout);
button->Bind(wxEVT_BUTTON, [result, count = 0](wxCommandEvent&) mutable {
result->SetLabel(wxString::Format("Clicks: %d", ++count));
});
check->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& e) {
printf("WX_CHECKBOX_EVENT checked=%d\n", e.IsChecked());
});
frame->Show();
SetTopWindow(frame);
return true;
}
};
wxIMPLEMENT_APP_NO_MAIN(GuiApp);
extern "C" __attribute__((visibility("default")))
void WxMain(NativeChildProcess_Args args) {
if (!PrepareChild(args, "wxwidgets.log")) return;
setenv("GDK_BACKEND", "broadway", 1);
setenv("BROADWAY_DISPLAY", ":tcp95", 1);
int argc = 1;
char name[] = "wxwidgets-gui";
char* argv[] = {name, nullptr};
int rc = wxEntry(argc, argv);
printf("WX_GUI_EXIT rc=%d\n", rc);
}
ArkUI 侧的显示区只有一行关键代码,Web 组件指向本机 Broadway 服务:
Web({ src: 'http://127.0.0.1:18085/', controller: this.browser })
.javaScriptAccess(true).domStorageAccess(true).width('100%').layoutWeight(1)
一个触摸偏差的教训
第一轮 GUI 验证时出现了奇怪的现象:点击复选框,增长的却是按钮计数。第一反应是 DPI 缩放导致命中区域偏移,但在 Web 组件里注入坐标诊断后,client、rect、surface 三组坐标完全一致,DPI 假设被否定。真正的根因在 Broadway 服务端:它对触摸事件序号的处理带有 Android 浏览器假设,而 ArkWeb 不是 Android Chrome,从 0 开始的触摸序号没有被正确跟踪,导致目标识别串位。修复服务端的序号处理后,同样的点击全部命中正确控件。
这个案例想说明的是:跨环境移植里“位置不对”未必是缩放问题,先采集真实坐标证据,再定位责任层,比直接加 DPI 补偿靠谱得多。
修复后的交互轮日志证明:按钮计数 1→2、复选框 true→false→true、英文文本输入 GUI_USB_r3、窗口最大化到 1603×693 后还原 800×520,最后 WX_GUI_EXIT rc=0 正常退出,两个子进程都被清理。中文标签正常显示,但中文输入法的组合输入仍未验证,这个边界不掩饰。
正式 USB W3:897 项分层结果
GUI 探索通过后,正式 W3 在 HUAWEI MateBook Pro 真机上经 USB HDC 完成:签名 HAP 通过签名校验和 profile 匹配后安装,单次启动(launch_count=1)依次执行三套上游测试。结果是 Base 429/429、GUI 467/467、Drawing 1/1,合计 897/897,三个测试子进程的退出状态都是 signal=0。
这些分母必须分层读:不能把三套分母改名拼接成“一次 897 用例的大套件”,也不能用 CTest 的 1/1 冒充任何一套。消费者 1/1 只证明 wxBase ABI;GUI 套件的通过证明的是 Broadway 路线下 wxCore 控件、事件和绘制的正确性,不等于原生窗口后端已经存在。
测试数字怎样读
wxBase 的 Catch2 清单包含 429 个精确条目,大小写折叠后有 423 个唯一名称,存在 6 组合法的大小写重复。CTest 注册项只有 1 个,不能把 CTest 的 1/1 当成 Catch2 的 429/429。归档的最终真机事务记录为 399 PASS + 30 expected SKIP = 429/429 nonfailed,CTest 为 1/1,消费者为 1/1。这 30 个跳过项都有路径和原因,表示设备能力边界,不是隐藏失败。
这组数字也说明为什么要保存有序测试清单摘要:同名 case 可能来自不同文件,单纯按显示名称去重会改变分母。复跑时应先核对清单摘要,再核对每个 WX_EXPECTED_SKIP:id;path;reason 事件是否属于冻结的允许集合。未知 case、重复事件、换行损坏或清单外映射都应该直接失败关闭。
新手适配教程:环境、过程、结论与 FAQ
环境:先明确 wxBase 与 wxCore 的边界
构建机需要 Conan 2、CMake、Ninja、Python 和 HarmonyOS SDK(GUI 线还需要 DevEco Studio 的 native 工具链与签名环境),host profile 固定为 OHOS/AArch64。先验证字符串、文件、线程和基础 ABI,再进入 GUI,可以避免把 GUI 框架、显示服务和基础库问题混在一起。运行机要准备签名工具、可写任务目录和与包版本一致的消费者环境;GUI 线还需要一台开启了 USB 调试的鸿蒙PC真机。
过程:从可重复构建到分层真机验证
- 用
--build=never预检依赖,确认 Conan 生成器和 OHOS profile 没有落回 Windows 默认架构。 - 运行 CMake 配置和构建命令,检查
build/conan_toolchain.cmake、静态库和公共头文件都来自当前包。 - 先执行安装后消费者,确认它调用 wxBase API,而不是只打印版本字符串。
- 再执行 Catch2/CTest 清单,保留每个 expected skip 的路径和原因;未知或重复事件要立即失败关闭。
- GUI 线单独构建 wxGTK 静态闭包,装进签名 HAP,先跑通 broadwayd 加 wx 子进程的窗口显示。
- 交互验证逐项记录:按钮计数、复选框状态、文本输入、窗口缩放和正常退出;坐标异常先取证再归因。
- 最后在真机执行三套套件,记录 Base 429/429、GUI 467/467、Drawing 1/1 的来源和子进程退出状态,再绑定桌面截图。
结论:复杂度在测试分母和产品边界
wxWidgets 3.3.3 的高难点不是把一个静态库编出来,而是要在大型跨平台框架中固定可交付边界:wxBase 要处理 HMDFS 临时目录、编码、线程和测试注册差异;wxCore 要在没有 X11/Wayland 的环境里找到能证明控件行为的显示路径。897/897 是三套套件的分层合计,包含明确的能力跳过项,不能简化成“全部 PASS”;Broadway 路线证明了真实 wxGTK 控件在鸿蒙PC上的行为,也不应被写成原生窗口后端。这样的分层结果比单独展示编译成功更能让读者判断适配是否可靠。
FAQ
Q:为什么不直接做原生窗口后端? A:鸿蒙PC没有 X11/Wayland 服务器,上游也没有 OHOS 端口;Broadway 路线先用真实 wxWidgets 代码路径验证 wxCore,原生后端是另一个量级的工程。Q:expected SKIP 是失败吗? A:不是,只要每项都在冻结的允许集合中并记录原因;未知 skip 不能默认为正常。Q:897/897 能拆开读吗? A:应该拆开:429 是 wxBase,467 是 GUI,1 是 Drawing,三套分母不能互相冒充。Q:中文输入能用吗? A:中文标签渲染已验证,输入法组合输入未验证;英文文本输入已通过日志证明。Q:没有 HarmonyOS 桌面截图能否发布? A:不能把本地构建或 Linux 截图写成鸿蒙PC真机证明,截图必须保留系统任务栏和桌面元素。
运行截图与复现边界
以下截图来自同一台 HUAWEI MateBook Pro(HAD-W32)的 HarmonyOS 6.1 真机图形会话。第一张是 GUI 验证轮的整屏原图(3120×2080,未裁剪未拼接):外层是 ArkUI 应用的控制栏和 Web 显示区,内部是真实的 wxGTK 窗口——中文标签、文本框、“点击测试事件”按钮和 Clicks 计数都来自 wxWidgets 控件本身,画面保留鸿蒙PC任务栏和系统托盘。

第二张是修复触摸序号后的交互验证轮:按钮计数到 2、复选框处于勾选态、文本框内容为 GUI_USB_r3,状态栏显示 wxWidgets 子进程 PID。这一轮同时证明了窗口最大化和还原,日志以 WX_GUI_EXIT rc=0 结束。

第三张是 wxBase 消费者在终端中的运行结果,wxWidgets 3.3.3 consumer test passed 与鸿蒙PC桌面同屏:

截图旁的信息:运行日期 2026-09-25 至 2026-09-26,配方版本 3.3.3.2,设备为 USB HDC 连接的真机;正式 W3 的 897/897 分层结果以归档的 result.xml 为准,截图证明的是 GUI 路线和交互行为,不是把 Broadway 写成原生后端。
小结
wxWidgets 3.3.3 的鸿蒙PC桌面端适配重点不在于堆叠平台宏,而在于缩小并固定产品边界、使用能力探测表达真实环境、保存完整测试清单,并让消费者实际调用被测包。wxBase 先把 ABI、临时目录、编码和测试分母做实;GUI 再用 wxGTK 加 Broadway 把真实控件、事件和绘制带上真机,最终以三套套件 897/897 的分层结果收口。后续如果要做原生窗口后端或验证输入法组合输入,这条基线就是可比较的起点。所有源码和配方请通过 AtomGit 项目公开获取,发布前再根据当前主线和设备镜像复核版本、截图与测试结果。
更多推荐

所有评论(0)