在浏览器环境中直接运行经典 Windows EXE 文件,是 WebAssembly (WASM) 时代的一项创新实践。这种方法无需完整操作系统镜像,仅通过 x86 指令仿真器结合精简的 Win32 API 兼容层,即可让遗留应用如 Solitaire 或 SkiFree 在现代浏览器中复活。RetroTick 项目便是典型代表,用户只需拖拽 EXE 文件,即可即时执行。这种 “轻量用户态仿真” 避免了全系统模拟的复杂性,特别适合游戏和工具类单体应用。核心观点在于:syscall shims(系统调用垫片)是兼容层的灵魂,它将 EXE 的原生 API 调用桥接到浏览器原语,实现无缝交互。
技术原理源于 x86 仿真与 PE(Portable Executable)加载机制。浏览器下载 EXE 二进制后,WASM 模块解析其 DOS 存根、PE 头、节表和导入表。将代码节、数据节映射到 WASM 的线性内存(Linear Memory),模拟虚拟地址空间,包括栈初始化和入口点设置。随后,x86 仿真器进入 fetch-decode-execute 循环:从仿真内存读取指令,解码操作码与操作数,更新寄存器、标志位和内存。不同于 QEMU 的动态 JIT,此类项目多采用简单解释器,确保跨平台兼容,但牺牲部分速度。为提升性能,可预解码基本块或引入 WASM-native 加速。
关键在于 Win32 API 的 “钩子” 机制(Hooking)。传统 Windows 通过 IAT(Import Address Table)加载 DLL 函数地址;仿真器在解析导入表时,将这些地址替换为 “哨兵地址”(sentinel addresses),这些地址指向仿真器的陷阱处理器。当 EXE 执行 call [api@IAT] 时,CPU 跳转至哨兵,触发宿主回调。垫片函数从仿真栈 / 寄存器读取参数(如指针、句柄),解码后转发至 JavaScript 层。例如,kernel32.dll 的 GetTickCount 返回浏览器 performance.now ();user32.dll 的 MessageBox 渲染到虚拟 DOM 或 alert。gdi32.dll 的绘图调用映射到 Canvas 2DContext,实现位图 / 文本渲染;DirectDraw 通过 COM 接口桥接到 WebGL,提供硬件加速。
证据可见类似 retrowin32 项目:“emulator 通过 sentinel addresses hook API calls,并将参数 marshal 到宿主实现。” 此设计回显 WASM 的 import/export 模型:仿真器导出 step/run 接口,导入 host_write、host_draw 等。浏览器宿主使用 IndexedDB 模拟文件系统,requestAnimationFrame 处理定时器,Pointer Events 捕获输入。RetroTick 演示了此架构:拖拽后,Calculator 或 Minesweeper 无缝运行,证明了对 Win95 时代简单 GUI 应用的兼容。
工程落地需关注参数调优与清单。首先,WASM 内存配置:初始 64MB,最大 1GB(浏览器上限~4GB),通过 Memory.grow 动态扩展。避免 OOM 时 fallback 到分块加载。其次,shim 覆盖优先级清单:
| API 模块 | 关键函数 | 浏览器映射 | 参数建议 |
|---|---|---|---|
| kernel32 | GetTickCount, WriteFile | performance.now(), console.log/IndexedDB | 精度 1ms,缓冲 4KB |
| user32 | CreateWindow, MessageBox | Canvas/DOM overlay | 窗口大小限 1024x768,模态优先 alert |
| gdi32 | BitBlt, TextOut | CanvasRenderingContext2D | 抗锯齿 off,批处理渲染 FPS>30 |
| advapi32 | RegOpenKey | in-memory JSON store | 仅 RO,键路径深度 < 5 |
兼容清单聚焦单线程 32bit EXE:<10MB 大小、无多进程、无 DirectX 9+。测试集:FreeCell、Clock、QBasic。回滚策略:若 API 未实现,注入 shim stub 返回 ERROR_CALL_NOT_IMPLEMENTED,并日志缺失调用。
监控要点包括:1)仿真 CPU 循环 / 秒(目标 >1M cycles/s);2)JS 堆使用(<500MB);3)渲染 FPS(>60);4)内存泄漏检测(每 10s 检查)。使用 PerformanceObserver 追踪 long tasks,Web Vitals 优化。风险控制:API 缺失导致崩溃概率 20%,通过动态加载 shim JS 模块缓解;性能瓶颈在复杂循环,引入 Worker offload x86 loop。
实际部署参数示例:WASM 编译 flag -O3 --import-memory;JS 桥接使用 SharedArrayBuffer(需 COOP/COEP headers)。超时阈值:加载 5s,执行 30s 无响应则 pause。扩展性:支持 DLL 注入,通过额外 PE 加载器。
此技术不止 RetroTick,Internet Archive 的 Emularity 亦用 WASM 提升仿真速度。未来,随着 WASM GC 和 threads,复杂多线程 app 如 Super PI 将更易支持。
资料来源:
(正文字数:1028)