欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/

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

如有项目源码,可上传至 AtomGit 仓库,并在博文内附上仓库链接。

鸿蒙PC桌面端适配 DOSBox Staging:贯通 NativeWindow、OHAudio 与 ARM64 动态重编译

把 DOS 软件带到鸿蒙PC,需要解决两层运行环境的衔接:上层是 DOS 程序熟悉的 CPU、文件系统和音视频设备,下层是鸿蒙PC提供的 ARM64 执行环境、原生图形接口与音频服务。DOSBox Staging 正好处于这两层之间,适配工作因此覆盖了编译、依赖、文件系统语义和运行时后端等多个环节。

本文围绕上游 DOSBox Staging 0.82.2、适配包 dosbox-staging/0.82.2-r1,介绍一次已有真机日志支持的适配实践。-r1 是本次适配包标识,源码绑定官方 v0.82.2。版本背景可查阅 官方 0.82.2 发布说明。

本次成果包括 NativeWindow/OpenGLES2 帧到达验证、OHAudio 混音流回调验证,以及 ARM64 宿主上的动态核心与 DOS 客体命令执行验证。理解这三条链路,就能看出这个模拟器的适配难度具体来自哪里,也能找到新手可以逐步复现的入口。

一、环境准备:先建立可复现的依赖闭包

1. 工具与构建位置

这份配方采用 C++20、Conan 2、Meson 和 Ninja。目标是 OHOS/ARM64;执行测试和签名的阶段在具备相应工具的鸿蒙PC环境中完成。环境中需要能够识别 os=OHOS 的 Conan 配置,以及正确的编译器、sysroot 和归档工具路径。

有两个设置值得先检查。第一,Conan profile 的目标系统与架构需要匹配 OHOS/armv8。第二,Meson 使用的编译器和 llvm-ar 应绑定目标环境中的绝对路径。这样既能避免从 PATH 误取宿主工具,也便于从日志复核每一步使用的工具链。

2. 关键依赖

下面列出冻结配方中的直接依赖。它们分别承担音视频、压缩、网络和合成器等功能:

依赖版本主要用途
SDL22.32.10图形、音频与平台抽象
SDL2_net2.2.0.1SDL 网络扩展
opusfile、libogg0.12、1.3.5音频文件与容器支持
IIR1、SpeexDSP1.10.0、1.2.1滤波与音频处理
zlib、libpng1.3.2、1.6.58.2压缩与图像支持
libslirp4.9.3用户态网络组件
FluidSynth、mt32emu2.5.7、2.7.1MIDI 与音源模拟组件

这里的难点是让依赖的“版本、头文件、链接信息和运行时产物”同时一致。例如 SDL2_net 原配方引用较早的 SDL2,而本次原生后端验证使用 SDL2 2.32.10,配方通过明确的版本覆盖统一这一依赖。音视频链路最终依赖实际解析出的包,因此复现时应保留完整 Conan 依赖图和包身份。

源码与配方入口:AtomGit 适配仓库,对应 适配合并请求 #6478。本文复现代码针对上述适配包及其能力探针。

二、构建过程:处理发行包、pkg-config 与安装后的 ELF

1. 给官方源码归档补上确定的版本身份

官方源码压缩包适合做可校验输入,但其中没有 .git 目录。上游版本脚本依赖 Git 元信息,直接搬到归档构建中便会遇到版本生成问题。

本次做法是由配方传入绑定上游标签的 DOSBOX_SOURCE_GIT_HASH,让版本脚本可以处理正式源码归档,同时通过 POSIX shell 调用脚本。源码包的 SHA-256 固定为:

387c97b373c3164ab5abbbc2b210bf94b5567057abe44ee1e8b4d4e725bd422c

GoogleTest 1.14.0 与对应 WrapDB 补丁也分别绑定摘要,构建设置 wrap_mode=nodownload。提前准备完整输入后,Meson 配置阶段便能稳定使用指定的测试框架。

2. 从真实头文件布局修正 pkg-config 接线

一个典型问题是 opusfile.h 实际位于 include/opus,而生成的 pkg-config 信息只暴露 include。此外,公共头文件还需要 Ogg 的依赖边。若只处理当前编译报错,下一个消费者仍可能因传递依赖缺失而失败。

配方先检查真实头文件和库文件,再修正任务内生成的 opusfile.pc,补齐正确的 include 目录、-lopusfile 和 Requires: ogg opus。SDL2 的 include/SDL2 布局也按同样思路核对。修复集中在消费者的生成信息中,保留依赖包本身的身份。

这一经验很适合新手:遇到“已经安装依赖,编译器仍找不到头文件”时,先检查生成的 .pc 文件、公开头文件位置和传递依赖,再决定修复哪一层。

3. 对安装完成的二进制签名

HarmonyOS PC 的 ELF 执行还涉及签名。Meson 安装阶段可能修改 ELF 元数据,因此配方在安装后对 package/bin/dosbox 的最终字节重新签名,检查签名节数量并保留执行权限。

这一步使构建目录中的程序、安装目录中的程序和真正接受消费者测试的程序形成清晰的对应关系。排查执行失败时,应同时核对文件权限、动态依赖以及最终安装字节的签名。

三、原生图形:从 SDL 呈现走到帧到达回调

本次图形验证采用 SDL nativewindow 视频驱动和 opengles2 渲染器。配方中的桌面 OpenGL 选项与 SDL 内部的 GLES2 渲染路径分别配置,运行记录明确给出实际后端名称。

能力探针在同一进程建立 NativeImage 消费 surface,取得真实 surface ID,注册帧到达监听,再将窗口参数交给 SDL NativeWindow 后端。DOSBox 以 output=texture 执行工作负载,通过呈现调用和帧回调共同确认图形数据已经走通。

真机消费者日志中的结果为:

DOSBOX_CONSUMER_VIDEO|nativewindow|opengles2|frame_available=1|PASS

其中 frame_available=1 是关键证据:它把验证推进到了原生 surface 的帧交付。测试还保留了无效宽度等参数的拒绝检查,确保错误输入能够在初始化阶段得到明确处理。

对于准备接入桌面应用的开发者,这条链路的价值在于:SDL 调用、原生 surface 和帧生命周期已经有了可观察的连接点,后续可以沿着同样的接口定位窗口初始化与呈现问题。

四、原生音频:观察 DOSBox 混音流进入 OHAudio

DOSBox 的音频路径经过混音、缓冲、格式处理和 SDL 回调,最后交给 OHAudio。验证时需要沿着这条生产数据流观察结果。

本次探针通过两个显式开关启用测试音调:DOSBOX_CAPABILITY_PROBE=1 和 DOSBOX_CAPABILITY_AUDIO_TONE=1。音调在 DOSBox 主混音器输出边界加入,然后经过正常队列和回调路径。回调只读取收到的采样,检查有限且非零的数据,并记录实际驱动及字节数。

真机结果为:

DOSBOX_CONSUMER_AUDIO|ohaudio|mixed_nonzero=1|PASS

这证明本次受控工作负载的混音数据已经到达 OHAudio 回调路径。复现时,音频模式会给工作负载保留约五秒运行窗口,让初始化、混音和回调有时间发生;这比启动后立即退出更容易得到稳定且有解释力的结果。

五、ARM64 动态重编译:把代码缓存与客体执行连起来

DOSBox 在 ARM64 宿主上运行 DOS 客体时,动态核心需要管理生成代码、代码缓存和跳转执行。这里既涉及不同指令集之间的转换,也涉及宿主对可写、可执行内存的约束。

适配补丁针对代码缓存的权限路径、动态核心返回值和执行阶段增加了处理与诊断。验证检查动态核心入口及生成代码执行后的阶段标记,并传入 DOS 客体命令作为工作负载;普通核心还提供一组对照。日志中的命令参数回显与客体实际打印结果需要分别判读,不能仅凭参数里出现哨兵字符串就判定客体输出正确。

本次记录中的动态验证使用专用能力探针:在该模式下采用 RWX 代码缓存,并包含异常返回的诊断处理;默认运行路径保留 W^X 权限切换。下面的结果与这一明确的测试模式绑定:

DOSBOX_CONSUMER_DYNREC|entry=1|guest_execution=1|PASS

这样的记录把“进入了哪个核心”和“生成代码调用是否返回”变成了可观察事件。这也是模拟器适配相较于普通工具编译更复杂的地方:它需要把内存权限、宿主指令执行和客体程序行为放在同一条验证链上。

六、新手可复现的音频检查脚本

以下脚本提取自本次冻结消费者中的 OHAudio 检查。运行前需要准备同一适配包的已签名 dosbox、完整运行时依赖,并在实际鸿蒙PC环境执行。第一个参数传入可执行文件路径;Conan VirtualRunEnv 或等价运行环境应提前激活。

将代码保存为 check-dosbox-audio.sh:

#!/bin/sh
set -eu

dosbox_bin=${1:?usage: sh check-dosbox-audio.sh /path/to/dosbox}
test -x "$dosbox_bin" || {
    printf '%s\n' 'DOSBox executable is missing or not executable' >&2
    exit 1
}

if ! output=$(
    DOSBOX_CAPABILITY_PROBE=1 \
    DOSBOX_CAPABILITY_AUDIO_TONE=1 \
    DOSBOX_CAPABILITY_AUDIO_ONLY=1 \
    SDL_VIDEODRIVER=dummy \
    SDL_AUDIODRIVER=ohaudio \
    "$dosbox_bin" -c "echo DOSBOX_OHAUDIO_WORKLOAD" \
        -c "choice /T:y,5 /N" --exit 2>&1
); then
    printf '%s\n' "$output" >&2
    exit 1
fi

printf '%s\n' "$output"
case "$output" in
    *"DOSBOX_CAPABILITY_AUDIO_ONLY|video=disabled|audio=enabled"*) ;;
    *) printf '%s\n' 'Missing audio-only initialization marker' >&2; exit 1 ;;
esac
case "$output" in
    *"DOSBOX_CAPABILITY_AUDIO_CALLBACK|driver=ohaudio|bytes="*"|nonzero=1"*) ;;
    *) printf '%s\n' 'Missing OHAudio mixed-stream marker' >&2; exit 1 ;;
esac

printf '%s\n' 'DOSBOX_AUDIO_CHECK_PASS'

调用方式:

sh check-dosbox-audio.sh /path/to/signed/package/bin/dosbox

这里的 /path/to/signed/package/bin/dosbox 需要替换为实际安装路径。脚本使用 POSIX sh 语法,检查初始化与非零混音标记;音频单项测试明确使用 AUDIO_ONLY 模式。完整复现应使用对应适配修订中的 test_package/test.sh,继续覆盖图形、动态核心和负例检查。

七、测试结论:原清单与原生能力分别展示

本文采用的真机归档为 r178,运行日期为 2026-09-14。该次运行的包内 DOSBox 二进制 SHA-256 为:

0270effeadcf6c4e3469e7b5b01fd4a5e03fd96f06ed773b997a6c1927de8e30
验证项目实际结果读法
上游 Meson 原清单24 项全部执行:23 OK、1 EXPECTEDFAIL严格 PASS 比例为 23/24,即 95.83%
原生消费者版本、图形、音频、动态核心四类检查通过归档终态为 4/4 PASS
鸿蒙专属验证1/1 PASS复用同次运行的安装包能力流证据

唯一的 EXPECTEDFAIL 是 gtest example,属于上游指定的预期失败示例;原日志记录其退出状态为 1。其余 23 项为 OK,实际失败、跳过和超时均为 0。归档汇总曾按上游框架的预期终态写成 24/24 PASS;本文按严格 PASS 口径保留 23/24,并完整披露原始状态。

消费者的四类检查共享一个安装包测试流程,鸿蒙专属验证也引用这条能力流。因此表中各行分别说明验证层次,计数保持各自含义。

八、鸿蒙PC真机完整桌面截图

下面四张图于 2026-10-20 在 HUAWEI MateBook Pro(HAD-W32)的可见 HiShell 会话中采集,原始尺寸均为 3120×2080,保留完整桌面、程序输出和系统任务栏。设备报告的产品软件版本为 HAD-W24 6.1.0.117(SP68C00E100R13P3)。此次文章演示使用与前述归档相同 SHA-256 的 DOSBox 二进制,演示日志与历史 r178 测试记录分开保存。

1. 版本与二进制身份

截图显示 HarmonyOS/aarch64 运行环境、DOSBox Staging 0.82.2、实际二进制摘要和版本命令退出码 0。先确认“运行的是哪个文件”,后续后端结果才有可追溯的对象。

鸿蒙PC完整桌面:DOSBox版本、ARM64环境及二进制摘要

2. OHAudio 混音回调

本次演示记录了 48 kHz 立体声、960 帧缓冲及 7680 字节回调,nonzero=1 表示回调检测到非零混音数据,演示退出码为 0。这里验证的是受控测试音调经过实际混音及音频回调的数据路径。

鸿蒙PC完整桌面:OHAudio回调、非零采样及退出码0

3. 动态核心执行阶段

图中可见 core=dynrec|execution=entered,以及 create-block-after、run-code-after 等阶段标记,演示退出码为 0。客体 echo 命令作为工作负载传入;截图中的哨兵字符串来自参数回显,不单独用它证明客体打印断言。

鸿蒙PC完整桌面:dynrec核心入口及生成代码执行阶段

4. NativeWindow 首帧与缓冲区等待诊断

图中实际观察到 driver=nativewindow|renderer=opengles2|frame_available=1,随后演示在 25 秒限时到达时返回 124;栈回溯停留在 eglSwapBuffers、NativeWindowRequestBuffer 一带。这张图证明了首帧到达,也保留了后续缓冲区等待现象,整段演示不记作正常退出通过。与历史 r178 的消费者终态分开呈现,便于读者判断两个运行记录各自说明的事实。

鸿蒙PC完整桌面:NativeWindow首帧到达及超时124诊断

这条图形探针使用 NativeImage 消费 surface,截图展示的是其在真实鸿蒙PC桌面终端中的运行证据。接入可交互桌面窗口时,开发者还需要管理消费者取帧、缓冲区归还和窗口生命周期;本次等待栈正好给出了继续定位呈现链路的入口。

九、FAQ

为什么适配 DOSBox 还需要专门处理 SDL2?

SDL2 连接模拟器和宿主系统。NativeWindow 的窗口参数、EGL/GLES2 渲染以及 OHAudio 的流生命周期,都会直接影响 DOSBox 的图形和音频行为。先统一 SDL2 版本和后端接口,能够让上层模拟器沿着确定的路径运行。

NativeWindow 参数应该从哪里取得?

本文验证路径由同一进程创建 NativeImage 消费 surface,读取其真实 ID,并建立帧到达监听。完整消费者会清除外部 surface 参数,再建立这组资源。复现时应沿用对应修订中的初始化流程。

为什么包安装后还要重新签名?

安装过程可能改动 ELF 字节。对最终安装文件签名,可以使被执行的文件与签名输入精确一致;同时需要检查执行权限和动态库依赖。

为什么动态核心检查还要有普通核心对照?

普通核心可以执行同样的客体命令,同时帮助检查动态核心标记是否只在相应路径产生。两组结果配合代码执行后的阶段标记,让判断更加具体。

新手应该按什么顺序复现?

先核对源码和依赖版本,再构建并运行完整原测试清单,随后检查最终安装包的签名与版本,最后执行图形、OHAudio 和动态核心消费者。把工作目录、实际命令、返回码和日志保存在一起,后续就能按失败位置快速定位。

结语

DOSBox Staging 的鸿蒙PC适配,把依赖元数据、原生图形、音频混音和跨指令集执行连接成了一套可以逐层观察的流程。最有价值的成果是每条核心链路都有对应输入、实际后端和可复核输出:图形看帧到达,音频看混音流,动态核心看执行阶段与客体行为。

对开源鸿蒙PC生态而言,这类适配也沉淀了可复用的方法:正确处理 pkg-config 的公共依赖,针对最终 ELF 完成签名,并在真实生产调用边界设计消费者验证。后续接入其他多媒体软件和模拟器时,这些方法都能直接帮助缩小问题范围。

参考资料

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐