1. 从“Madeira”说起:一个跨平台兼容项目的整体设计思路
“Madeira”这个名字乍一看像是个地名,但在跨平台兼容圈子里,它代表的是一个把 x86-64 应用搬到 ARM 设备上运行的项目方向。结合热搜词里的 FEX-Emu、Wine、DXMT、iOS 这些关键词,可以很清楚地看出这个项目的核心定位:在 ARM 架构的设备上,通过指令翻译层加 Windows 兼容层,让原本为 x86-64 Windows 编译的程序跑起来。这套思路并不是凭空冒出来的,它是近几年 ARM 设备性能暴涨、而 x86 生态软件存量又极其庞大的矛盾催生出来的产物。
我最早接触这类方案是从 FEX-Emu 开始的。FEX-Emu 做的是 x86-64 到 ARM64 的指令级翻译,它把 x86 的机器码动态翻译成 ARM64 能执行的指令。而 Wine 负责的是 Windows API 的兼容层,让 Windows 程序以为自己还在 Windows 上。DXMT 则是把 Direct3D 调用翻译成 Metal,这样在 Apple 设备上也能跑 Windows 游戏。三者叠在一起,就构成了一个完整的“x86-64 Windows 程序在 ARM 设备上运行”的链路。
为什么是这三个组合而不是别的?这里面的选型逻辑值得说清楚。传统的做法是用 QEMU 做全系统模拟,但 QEMU 的性能损耗太大,跑个轻量应用还行,跑游戏基本没法看。FEX-Emu 走的是用户态翻译路线,只翻译用户空间的指令,系统调用直接透传给宿主,性能损耗小得多。Wine 不用虚拟机,直接做 API 转换,省掉了整个 Windows 内核的开销。DXMT 针对图形做了专门优化,比通用的 DXVK 在 Apple 平台上更贴合 Metal 的特性。这套组合的本质是每一层只做自己最擅长的事,把损耗分摊到最小。
这个项目适合谁来参考?如果你是在 ARM 笔记本、Apple Silicon 设备或者国产 ARM 平台上折腾 Windows 软件的人,这套方案值得研究。如果你只是想在普通 x86 电脑上跑 Windows 程序,那直接用 Wine 就够了,不需要 FEX-Emu 这一层。另外,做 iOS 开发的人可能会对其中涉及的一些概念有共鸣,比如开发者模式、证书配置、WebView 行为这些,虽然和 Madeira 不是一回事,但底层思路有相通之处。
2. 核心组件拆解:FEX-Emu、Wine、DXMT 各自在干什么
2.1 FEX-Emu:x86-64 到 ARM64 的指令翻译引擎
FEX-Emu 的核心工作是把 x86-64 的指令流翻译成 ARM64 的指令流。它采用的是 JIT(即时编译)方式,程序运行时才做翻译,翻译结果会缓存起来,下次遇到同样的代码块就直接用缓存。这样做的好处是启动快、内存占用可控,缺点是第一次执行某段代码时会有翻译开销。
具体来说,FEX-Emu 维护了一个 x86-64 的寄存器状态映射到 ARM64 寄存器的表。x86-64 有 16 个通用寄存器,ARM64 有 31 个,映射起来有富余。但 x86-64 的指令语义和 ARM64 差别很大,比如 x86 的 flag 寄存器在 ARM64 上没有直接对应,FEX-Emu 需要用额外的指令来模拟 flag 的计算。这部分是性能损耗的主要来源之一。
我在实际使用中发现,FEX-Emu 对 SSE 指令集的支持比较完整,但 AVX 的支持要看版本。如果你的目标程序大量使用 AVX2 或 AVX-512,需要确认 FEX-Emu 的版本是否支持。另外,FEX-Emu 有一个配置文件可以调整翻译缓存的大小,默认值偏保守,如果你内存充足,可以适当调大,减少重复翻译的开销。
注意:FEX-Emu 的 rootfs 需要和宿主系统的库版本匹配,如果宿主是较新的发行版,而 rootfs 是旧的,可能会出现库符号找不到的问题。建议 rootfs 和宿主用同一发行版的不同版本,减少兼容性问题。
2.2 Wine:Windows API 的兼容层
Wine 做的事情是把 Windows 的 API 调用翻译成 POSIX 调用。比如 Windows 的CreateFile会被翻译成 Linux 的open,Windows 的ReadFile会被翻译成read。Wine 不模拟 Windows 内核,它只是提供了一套 DLL 来替代 Windows 的系统 DLL,这些 DLL 内部调用宿主系统的接口。
Wine 的架构分为几个层次:最上面是 Windows 程序,中间是 Wine 提供的 DLL(如 kernel32.dll、user32.dll、gdi32.dll),下面是 Wine 的 ntdll.dll,它负责和宿主内核交互。Wine 还有一个 wineserver 进程,负责管理窗口、进程间通信、注册表等全局状态。
热搜词里出现了“wine 乱码”和“wine 栏是乱码”,这是很典型的问题。Wine 的乱码通常来自两个方面:一是字体缺失,Wine 默认的字体可能不包含中文字形;二是 locale 设置不对,Wine 需要正确的 locale 才能正确渲染非 ASCII 字符。解决方法是安装中文字体到 Wine 的字体目录,并在 Wine 配置里设置正确的 locale。
2.3 DXMT:Direct3D 到 Metal 的翻译层
DXMT 是专门为 Apple 平台设计的,它把 Direct3D 11 和部分 Direct3D 12 的调用翻译成 Metal 调用。为什么不用 DXVK?DXVK 是把 D3D 翻译成 Vulkan,然后在 Apple 平台上再通过 MoltenVK 把 Vulkan 翻译成 Metal。多了一层翻译,性能损耗更大。DXMT 直接做 D3D 到 Metal 的翻译,少了一层,效率更高。
DXMT 目前对 D3D11 的支持比较成熟,D3D12 的支持还在完善中。如果你要跑的游戏是 D3D11 的,DXMT 基本能用;如果是 D3D12 的,可能需要等更新或者用其他方案。DXMT 的配置需要在 Wine 的注册表里设置一些键值,比如指定用 DXMT 而不是 WineD3D,以及设置 Metal 的一些参数。
3. 实操过程:从零搭建一套可用的运行环境
3.1 环境准备与依赖安装
先确认你的设备是 ARM64 架构的。在终端里执行uname -m,如果输出aarch64或arm64,说明架构对了。然后确认系统版本,FEX-Emu 对内核版本有要求,建议内核 5.10 以上。
安装依赖的时候,需要装编译工具链、CMake、Ninja、Python 等。以 Ubuntu 为例:
sudo apt update sudo apt install -y build-essential cmake ninja-build python3 python3-pip git然后克隆 FEX-Emu 的仓库:
git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive编译的时候要注意,FEX-Emu 需要指定安装路径,建议装到/usr/local下:
mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release .. make -j$(nproc) sudo make install编译过程大概需要十几分钟到半小时,取决于设备性能。编译完成后,需要配置 rootfs。rootfs 是一个包含 x86-64 库和 Wine 的目录,FEX-Emu 会在里面执行 x86-64 程序。
3.2 Wine 与 DXMT 的配置
Wine 的安装有两种方式:一种是用系统包管理器装,另一种是编译安装。系统包管理器装的版本可能比较旧,建议编译安装或者用 Wine 官方提供的仓库。
编译 Wine 的时候,需要先装依赖:
sudo apt install -y libgnutls28-dev libkrb5-dev flex bison然后配置和编译:
./configure --enable-win64 --prefix=/usr/local make -j$(nproc) sudo make installDXMT 的安装需要先下载预编译的包,然后把 DLL 放到 Wine 的对应目录里。DXMT 提供了d3d11.dll、dxgi.dll、d3d10core.dll等文件,需要覆盖 Wine 自带的版本。覆盖之前建议备份原文件,方便出问题时回滚。
配置 Wine 的时候,用winecfg打开配置界面,在“库”标签页里把d3d11、dxgi设置为“原装”(即使用 DXMT 的版本)。然后在注册表里设置 DXMT 的一些参数,比如HKCU\Software\Wine\DXMT下的MetalDevice指定用哪个 GPU。
3.3 运行第一个 x86-64 Windows 程序
找一个简单的 Windows 程序做测试,比如 Notepad++ 的安装包。把安装包放到 rootfs 的某个目录里,然后用 FEX-Emu 执行:
FEXBash -c "wine /path/to/installer.exe"如果一切正常,安装界面会弹出来。安装完成后,可以在 rootfs 的 Wine 目录里找到安装的程序,用同样的方式启动。
我第一次跑的时候遇到了字体乱码的问题,菜单栏全是方块。后来发现是 rootfs 里没有中文字体。解决方法是从宿主系统拷贝字体到 rootfs 的/usr/share/wine/fonts/目录,然后在winecfg里把默认字体设置为中文字体。
提示:如果程序启动时报错说找不到某个 DLL,可以用
WINEDEBUG=+loaddll来查看 Wine 加载 DLL 的详细过程,定位是哪个 DLL 缺失。
4. 常见问题与排查技巧实录
4.1 Wine 乱码问题的系统化排查
Wine 乱码是最常见的问题,表现有多种:菜单栏乱码、对话框乱码、输入框乱码。排查的时候按以下顺序来:
第一,检查 locale。在终端执行locale,确认LANG和LC_ALL设置正确。如果宿主是中文环境,Wine 通常能继承,但有时候需要显式设置:
export LANG=zh_CN.UTF-8 export LC_ALL=zh_CN.UTF-8第二,检查字体。Wine 需要字体文件才能渲染文字。把宿主的中文字体拷贝到 Wine 的字体目录:
cp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc ~/.wine/drive_c/windows/Fonts/然后在winecfg的“显示”标签页里,把默认字体设置为“WenQuanYi Micro Hei”或类似的中文字体。
第三,检查注册表中的字体替换设置。Wine 有一个字体替换表,在HKCU\Software\Wine\Fonts\Replacements下。如果某个字体被替换成了不包含中文的字体,就会乱码。可以把MS Shell Dlg和MS Shell Dlg 2替换成中文字体。
4.2 FEX-Emu 性能调优与兼容性处理
FEX-Emu 的性能调优主要从几个方面入手。第一是调整翻译缓存大小,在~/.fex-emu/Config.json里把TSOEnabled设为false(如果程序不需要严格的内存序),把HalfBarrierTSOEnabled设为true,可以减少内存屏障指令的开销。第二是开启多线程翻译,在配置里设置Multiblock为true,让 FEX-Emu 一次翻译多个基本块,减少翻译次数。
兼容性方面,有些程序会检测 CPU 特性,如果发现不是 Intel 或 AMD 的 CPU 就拒绝运行。这时候可以在 FEX-Emu 的配置里设置CPUID相关的选项,伪装成特定的 CPU 型号。另外,有些程序依赖特定的 Windows 版本,可以在winecfg里设置 Windows 版本为 Windows 10 或 Windows 11。
4.3 图形相关的典型故障处理
DXMT 的图形问题通常表现为黑屏、花屏、闪退。排查的时候先确认 DXMT 的 DLL 是否正确加载,可以用WINEDEBUG=+dxmt查看日志。如果日志里显示 Metal 设备创建失败,可能是 GPU 不支持某些特性,或者 Metal 版本太低。
另一个常见问题是分辨率不对。DXMT 默认会使用窗口的分辨率,但有些游戏会强制设置全屏分辨率。可以在winecfg的“显示”标签页里勾选“模拟虚拟桌面”,然后设置一个合适的分辨率。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 启动即闪退 | DLL 缺失或版本不匹配 | WINEDEBUG=+loaddll | 补齐缺失的 DLL,或回滚到兼容版本 |
| 菜单乱码 | 字体缺失或 locale 错误 | 检查locale和字体目录 | 安装中文字体,设置正确 locale |
| 黑屏无画面 | DXMT 未生效或 Metal 设备创建失败 | WINEDEBUG=+dxmt | 确认 DXMT DLL 已覆盖,检查 GPU 支持 |
| 性能极低 | 翻译缓存太小或 TSO 开销大 | 查看 FEX-Emu 配置 | 调大缓存,关闭不必要的 TSO |
| 程序检测 CPU 失败 | CPUID 伪装未设置 | 查看程序日志 | 在 FEX-Emu 配置里设置 CPUID |
5. 跨平台兼容方案的延伸思考与个人经验
这套方案的价值不仅在于跑 Windows 程序,它其实打开了一个思路:在 ARM 设备上,通过分层翻译来复用 x86 生态的存量软件。这个思路可以延伸到很多场景。比如在国产 ARM 平台上,很多行业软件只有 x86 版本,用这套方案可以不用等软件厂商适配就能先用起来。再比如在 Apple Silicon 上,有些老游戏只有 Windows 版,用这套方案可以不用装虚拟机就能玩。
我在实际使用中最大的体会是,兼容层的稳定性比性能更重要。一开始我追求高帧率,把 FEX-Emu 的 TSO 关了,结果有些程序跑着跑着就崩溃。后来把 TSO 打开,帧率降了一点,但稳定性好了很多。所以调优的时候要循序渐进,先保证能跑,再追求跑得快。
另一个体会是,日志是最好的朋友。Wine 和 FEX-Emu 都提供了详细的日志选项,遇到问题不要瞎猜,打开日志看输出,大部分问题都能定位到具体的 DLL 或配置项。我习惯在调试的时候把WINEDEBUG设成+all,虽然日志量大,但信息全,翻一翻总能找到线索。
最后分享一个小技巧:如果你要跑的程序比较多,可以给每个程序单独建一个 Wine prefix,而不是全放在默认的~/.wine里。这样每个程序的配置互不干扰,出问题了也容易回滚。建 prefix 的命令是WINEPREFIX=/path/to/prefix winecfg,第一次执行会初始化一个新的 prefix。
这个方向后续还可以扩展的地方很多,比如把 FEX-Emu 和容器技术结合,做成一个可移植的运行环境;或者把 DXMT 的配置做成预设,针对不同游戏自动切换。这些都需要在实际使用中慢慢摸索,但底层的思路是一样的:分层翻译,各司其职,用最小的损耗换取最大的兼容性。