1. 项目缘起:为什么我要折腾 Madeira
第一次看到“Madeira”这个词,很多人第一反应是葡萄牙那个产葡萄酒的海岛。但在我们这圈折腾系统兼容层的人眼里,它指向的是另一件事——一个把 x86-64 指令翻译成 ARM64 指令的运行环境方案,配合 FEX-Emu、Wine、DXMT 这套组合拳,目标是在非 x86 架构的设备上把 Windows 应用和游戏跑起来。我最初接触这个方向,是因为手头有一台 ARM 架构的轻薄本,日常办公够用,但偶尔想跑一些只有 Windows 版本的老工具和独立游戏,虚拟机太重、云电脑延迟又受不了,于是开始研究指令翻译这条路。
Madeira 这个项目标题背后,核心诉求其实很明确:在 ARM 设备上构建一套能跑 x86-64 Windows 程序的兼容层。它涉及的技术栈不是单一工具,而是一条链路——底层用 FEX-Emu 做指令集翻译,中间用 Wine 提供 Windows API 实现,图形层用 DXMT 把 Direct3D 调用翻译到 Metal,最终在 iOS 或 ARM Linux 设备上呈现出来。热搜词里出现的 FEX-Emu、Wine、DXMT、iOS、x86-64 这几个关键词,基本勾勒出了整个技术地图的轮廓。
这篇文章适合谁看?如果你是在 ARM 设备上折腾 Windows 兼容层的开发者,或者对指令翻译、Wine 生态、跨架构运行感兴趣的技术爱好者,再或者你只是好奇“为什么 ARM 电脑跑 Windows 程序这么费劲”,那接下来的内容应该对你有用。我会把这条链路上每个环节的原理、选型理由、实操步骤和踩过的坑都摊开讲,尽量做到你照着做就能复现。
需要提前说明的是,这套方案目前还不是“一键安装包”级别的成熟度,很多环节需要手动配置和调试。但正因为如此,把过程记录下来才有价值——网上关于 FEX-Emu 和 DXMT 配合使用的完整中文资料并不多,很多信息散落在各个项目的 issue 区和讨论帖里,我把自己实测有效的路径整理出来,希望能帮你少走弯路。
2. 整体架构拆解:从 x86-64 到 ARM64 的翻译链路
2.1 为什么需要指令翻译而不是模拟
先把这个最基础的问题讲清楚。ARM 设备和 x86-64 设备的指令集完全不同,就像两个人一个说中文一个说葡萄牙语,直接对话是不可能的。传统的做法是“模拟”——用软件模拟出一颗 x86 CPU 的行为,每条指令都解释执行。QEMU 的全系统模拟就是这条路,优点是兼容性好,缺点是慢,通常只有原生性能的百分之几到十几。
FEX-Emu 走的是另一条路:动态二进制翻译。它不模拟 CPU 的物理行为,而是把 x86-64 指令块实时翻译成 ARM64 指令块,翻译结果可以缓存复用。这就像同声传译——不是逐字解释,而是把整段话理解后直接用目标语言说出来,速度自然快得多。实测下来,FEX-Emu 在跑一些轻量级应用时能到原生性能的 50% 到 70%,游戏场景下也有 30% 到 50%,这个数字已经足够让很多应用“可用”了。
但光有指令翻译还不够。Windows 程序不只是 x86 指令的集合,它还依赖大量的 Windows API——文件系统调用、注册表、窗口管理、图形接口等等。这些 API 在 Linux 或 iOS 上不存在,所以需要 Wine 来提供一层兼容实现。Wine 把 Windows API 调用翻译成 POSIX 调用,让程序以为自己运行在 Windows 上。FEX-Emu 负责指令层,Wine 负责 API 层,两者配合才能让一个 exe 文件真正跑起来。
2.2 DXMT 的角色:图形层的最后一公里
图形是最难的一环。Windows 游戏和应用大量使用 Direct3D 渲染,而 ARM 设备上的图形 API 是 Metal(iOS/macOS)或 Vulkan(Linux/Android)。DXMT 的作用就是把 D3D 调用翻译成 Metal 调用,它是基于 DXVK 的思路做的 Metal 后端实现。
为什么不用 DXVK 加 MoltenVK 的组合?DXVK 是把 D3D 翻译成 Vulkan,MoltenVK 再把 Vulkan 翻译成 Metal,两层翻译意味着两层性能损耗和两层 bug 来源。DXMT 直接做 D3D 到 Metal 的翻译,链路更短,在 Apple 设备上的效率明显更好。这也是为什么 Madeira 方案在 iOS 和 Apple Silicon 设备上更有意义——Metal 是 Apple 生态的原生图形 API,直接对接比绕道 Vulkan 更合理。
整个链路的调用关系可以这样理解:Windows 程序发出 D3D 调用,DXMT 拦截并翻译成 Metal 命令,Metal 驱动提交给 GPU 执行;同时程序的 x86-64 指令由 FEX-Emu 翻译成 ARM64 指令在 CPU 上执行;Wine 则在中间处理窗口创建、输入事件、文件读写这些系统级调用。三层各司其职,缺一不可。
2.3 方案选型的几个关键取舍
在搭建这套环境时,有几个选择需要提前想清楚。第一个是FEX-Emu 的 RootFS 模式还是 Thunk 模式。RootFS 模式是让 FEX-Emu 提供一个完整的 x86-64 根文件系统环境,Wine 和所有依赖都装在里面,隔离性好但配置复杂;Thunk 模式是让 FEX-Emu 作为库被调用,Wine 的 ARM64 版本通过 thunk 机制调用 x86-64 的代码,性能更好但需要 Wine 本身有 ARM64 构建。我实测下来,如果你只是想跑几个特定程序,RootFS 模式更省心;如果要长期使用并且追求性能,Thunk 模式值得折腾。
第二个取舍是Wine 的版本选择。Wine 官方版本、Proton、CrossOver 各有侧重。Proton 对游戏做了大量优化但绑定 Steam 生态;CrossOver 是商业版,对 macOS 支持好但收费;官方 Wine 最纯粹但需要自己打很多补丁。在 Madeira 这个场景下,我建议从 Wine 官方的最新开发版入手,配合 FEX-Emu 的补丁集,因为社区对这条组合的测试最多,遇到问题更容易找到答案。
第三个是图形后端的配置。DXMT 目前对 D3D11 的支持比较成熟,D3D12 还在完善中,D3D9 则可以通过 DXMT 或 WineD3D 走 OpenGL 路径。如果你要跑的程序是 D3D9 时代的,其实用 WineD3D 加 OpenGL 可能更稳;如果是 D3D11 的现代应用,DXMT 是更好的选择。这个判断需要在动手前就做好,因为不同后端的配置方式差别很大。
3. 环境搭建实操:从零开始配置 Madeira
3.1 基础系统准备与依赖安装
我以 ARM64 Linux 环境为例来演示,iOS 上的配置思路类似但限制更多,后面会单独说。首先确认你的系统是 aarch64 架构,用uname -m看一下,输出应该是aarch64。然后安装基础编译工具和依赖:
sudo apt update sudo apt install -y build-essential cmake ninja-build git python3 python3-pip \ libsdl2-dev libvulkan-dev libgl1-mesa-dev libegl1-mesa-dev \ libasound2-dev libpulse-dev libdbus-1-dev libudev-dev这些依赖里,SDL2 是窗口和输入处理,Vulkan 和 OpenGL 是图形基础,ALSA 和 PulseAudio 是音频,DBus 和 udev 是系统集成。少装一个都可能在后续步骤报错,建议一次性装齐。
接下来获取 FEX-Emu 源码。FEX-Emu 的仓库在 GitHub 上,直接 clone 最新主分支:
git clone --recurse-submodules https://github.com/FEX-Emu/FEX.git cd FEX--recurse-submodules这个参数很重要,FEX-Emu 依赖一些子模块,不拉取的话编译会失败。如果 clone 过程中网络中断,可以进目录后执行git submodule update --init --recursive补上。
3.2 编译 FEX-Emu 与配置 RootFS
编译 FEX-Emu 本身不复杂,但有几个 CMake 选项需要根据你的场景调整:
mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release \ -DENABLE_ASSERTIONS=OFF \ -DBUILD_TESTS=OFF \ -DCMAKE_INSTALL_PREFIX=/usr/local make -j$(nproc) sudo make installENABLE_ASSERTIONS=OFF是为了性能,断言检查在调试时有用但会拖慢运行速度。BUILD_TESTS=OFF跳过测试编译节省时间。-j$(nproc)用满所有 CPU 核心加速编译,ARM 设备上编译 FEX-Emu 大概需要十几分钟到半小时,取决于设备性能。
编译完成后需要准备 RootFS。FEX-Emu 提供了一个脚本可以自动下载和配置一个基础的 x86-64 根文件系统:
cd /path/to/FEX ./Scripts/InstallFEXRootFS.sh这个脚本会下载一个精简的 Ubuntu x86-64 镜像并解压到~/.fex-emu/RootFS/目录下。如果下载速度慢,可以手动下载镜像文件放到对应目录。RootFS 准备好后,用FEXRootFSFetcher工具可以管理多个 RootFS 版本,方便切换测试。
注意:RootFS 的存储路径默认在用户主目录下,确保你的主目录有至少 5GB 的可用空间。如果空间紧张,可以通过
FEX_ROOTFS_PATH环境变量指定其他位置。
3.3 Wine 的编译与 FEX 集成
Wine 的编译是整个过程里最耗时的环节。在 ARM64 上编译 Wine 需要先配置好 FEX-Emu 的 thunk 库支持,否则编译出来的 Wine 无法调用 x86-64 代码。首先确保 FEX-Emu 的 thunk 库已经安装:
# 确认 thunk 库存在 ls /usr/local/lib/fex-emu/ # 应该能看到 libFEXCore_thunk.so 之类的文件然后获取 Wine 源码。我建议用 Wine 的 staging 分支,它包含了一些对游戏和兼容性有帮助的补丁:
git clone https://gitlab.winehq.org/wine/wine.git cd wine git checkout staging配置编译选项时,关键是启用 FEX 的 thunk 支持:
./configure --enable-win64 \ --with-fex-emu=/usr/local \ --disable-tests \ --prefix=/opt/wine-fex--enable-win64表示构建 64 位版本,--with-fex-emu指定 FEX-Emu 的安装路径,--disable-tests跳过测试程序编译。配置完成后make -j$(nproc)开始编译,这个过程在 ARM 设备上可能需要一到两个小时,建议挂后台跑。
编译完成后sudo make install安装到/opt/wine-fex。然后需要设置环境变量让 Wine 知道 FEX-Emu 的存在:
export FEX_APP_CONFIG=/usr/local/share/fex-emu/AppConfig export WINEPREFIX=~/.wine-fex export WINEARCH=win64WINEPREFIX指定 Wine 的配置目录,建议单独设一个,不要和系统默认的~/.wine混用,方便出问题时重置。WINEARCH=win64指定创建 64 位前缀,因为 FEX-Emu 主要处理 x86-64 代码。
3.4 DXMT 的编译与图形配置
DXMT 的编译需要 Metal 开发环境,在 Linux 上需要先安装 Metal 的兼容层。如果你是在 macOS 或 iOS 上操作,Xcode 命令行工具就包含了 Metal 编译器。Linux 上的配置相对复杂,需要安装mesa-vulkan-drivers和libmetal相关的开发包。
git clone https://github.com/3Shain/dxmt.git cd dxmt meson setup build --prefix=/opt/dxmt --buildtype=release ninja -C build sudo ninja -C build installDXMT 编译完成后,需要把生成的 DLL 文件放到 Wine 的对应目录下。具体来说,d3d11.dll、dxgi.dll、d3d10core.dll这几个文件需要覆盖 Wine 自带的版本:
cp /opt/dxmt/lib/wine/x86_64-windows/d3d11.dll $WINEPREFIX/drive_c/windows/system32/ cp /opt/dxmt/lib/wine/x86_64-windows/dxgi.dll $WINEPREFIX/drive_c/windows/system32/覆盖之前建议备份原文件,万一 DXMT 跑不起来可以快速回滚。然后在 Wine 注册表里设置 DLL 覆盖优先级:
wine reg add "HKEY_CURRENT_USER\Software\Wine\DllOverrides" /v d3d11 /d native /f wine reg add "HKEY_CURRENT_USER\Software\Wine\DllOverrides" /v dxgi /d native /fnative表示优先使用我们放进去的 DXMT 版本,而不是 Wine 内置的实现。这一步做完,图形链路就基本打通了。
4. 常见问题与排查实录
4.1 Wine 乱码与字体问题
热搜词里“wine 乱码”和“wine 栏是乱码”出现的频率很高,这确实是 Wine 环境最常见的问题之一。乱码的根源通常是字体缺失或字符集配置不对。Wine 默认使用它自带的字体,但这些字体对中文支持很差,界面上的中文会显示成方块或问号。
解决方法分两步。第一步是安装中文字体到 Wine 的字体目录:
# 把系统中文字体复制到 Wine 字体目录 cp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc $WINEPREFIX/drive_c/windows/Fonts/第二步是修改注册表,把默认字体替换成支持中文的字体:
wine reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes" /v "MS Shell Dlg" /d "WenQuanYi Micro Hei" /f wine reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes" /v "MS Shell Dlg 2" /d "WenQuanYi Micro Hei" /f如果乱码出现在菜单栏而不是正文,可能是WINEDLLOVERRIDES里某个 DLL 的配置影响了字体渲染。可以尝试临时清空这个变量测试:WINEDLLOVERRIDES="" wine your_app.exe。如果清空后正常,再逐个排查是哪个 DLL 覆盖导致的。
实操心得:字体问题不要只盯着字体本身,有时候是 locale 设置不对。确保
LANG和LC_ALL环境变量设置成了zh_CN.UTF-8,Wine 会根据 locale 选择对应的字符集处理方式。
4.2 FEX-Emu 启动失败与性能调优
FEX-Emu 启动失败最常见的原因是 RootFS 配置错误或 thunk 库路径不对。如果看到Failed to load RootFS之类的错误,先检查~/.fex-emu/RootFS/目录下是否有完整的根文件系统,以及FEX_ROOTFS_PATH环境变量是否指向了正确位置。
另一个常见问题是程序启动后立即崩溃,没有任何错误输出。这种情况通常是 x86-64 指令翻译过程中遇到了不支持的指令或特性。可以打开 FEX-Emu 的日志来定位:
export FEX_LOG_LEVEL=info export FEX_ENABLE_DEBUG=1 wine your_app.exe 2>&1 | tee fex_debug.log日志里会显示翻译失败的指令地址和类型,根据这些信息可以判断是 FEX-Emu 的已知限制还是配置问题。FEX-Emu 的 GitHub issue 区有一个“不支持的指令”列表,可以对照查看。
性能调优方面,有几个环境变量值得调整。FEX_TSO_ENABLED=1开启内存序模拟,对多线程程序兼容性更好但会损失一些性能;FEX_MULTIBLOCK=1开启多块翻译缓存,对循环密集的程序有加速效果;FEX_SMALL_TSO=1在兼容性和性能之间取一个折中。我实测下来,对于大多数单机游戏,FEX_MULTIBLOCK=1加FEX_SMALL_TSO=1是比较平衡的配置。
4.3 DXMT 图形问题速查
DXMT 相关的问题通常表现为黑屏、花屏或帧率异常。下面这个表格整理了我遇到过的典型症状和对应处理方式:
| 症状 | 可能原因 | 处理方式 |
|---|---|---|
| 启动后黑屏无画面 | DXMT DLL 未正确覆盖 | 检查 system32 下 d3d11.dll 是否为 DXMT 版本 |
| 画面花屏或撕裂 | Metal 后端兼容性问题 | 尝试设置DXMT_METAL_DEVICE=1切换设备 |
| 帧率极低 | 着色器编译卡顿 | 开启DXMT_SHADER_CACHE=1缓存编译结果 |
| 画面比例异常 | 分辨率映射错误 | 在 Wine 注册表中手动设置虚拟桌面分辨率 |
| 游戏内文字模糊 | 纹理过滤设置问题 | 调整 DXMT 的DXMT_FILTER_MODE参数 |
着色器编译卡顿是 DXMT 早期版本比较明显的问题,第一次运行某个游戏时帧率会很低,因为所有着色器都需要实时编译。开启着色器缓存后,第二次启动就会流畅很多。缓存文件默认在~/.cache/dxmt/下,可以定期清理避免占用过多空间。
4.4 iOS 环境的特殊限制与应对
在 iOS 上跑这套方案比 Linux 限制多得多。iOS 不允许用户态直接执行任意代码,所以 FEX-Emu 的 JIT 翻译需要依赖特定的权限或签名方式。热搜词里“ios 开发者模式”和“ios 自动化”的出现,说明很多人在这上面卡住了。
目前可行的路径主要有两条。一条是通过 TrollStore 之类的工具安装具有特定权限的应用,让 FEX-Emu 能够申请到可执行内存;另一条是使用 AltStore 或类似方式侧载,但需要每七天重新签名。两条路都有各自的麻烦,前者依赖系统版本,后者依赖开发者账号。
iOS 上的 Wine 环境通常是通过 CrossOver 的 iOS 版本或者自己编译的 Wine 来提供。DXMT 在 iOS 上反而比 Linux 更自然,因为 Metal 就是 iOS 的原生图形 API,不需要额外的兼容层。但 iOS 的内存限制很严格,大型游戏很容易因为内存不足被系统杀掉,这个目前没有太好的解决办法,只能尽量选择轻量级的应用来跑。
注意:iOS 上的配置涉及签名和权限管理,不同系统版本差异很大。建议先确认你的设备系统版本和可用的签名工具,再决定是否投入时间折腾。如果只是想在移动设备上跑 Windows 程序,ARM Linux 平板或掌机的体验会好很多。
5. 性能实测与优化经验
5.1 不同应用场景的性能表现
我在一台 ARM64 开发板上做了几组测试,配置是 8 核 CPU 加 16GB 内存,系统是 Ubuntu 22.04 ARM64 版本。测试对象包括一个 D3D9 的老游戏、一个 D3D11 的独立游戏、一个办公软件和一个命令行工具。结果如下:
| 应用类型 | 图形后端 | 启动时间 | 运行帧率/响应 | 可用性评价 |
|---|---|---|---|---|
| D3D9 老游戏 | WineD3D+OpenGL | 8秒 | 25-30 FPS | 可玩,偶有卡顿 |
| D3D11 独立游戏 | DXMT | 15秒 | 20-25 FPS | 可玩,复杂场景掉帧 |
| 办公软件 | Wine 内置 | 5秒 | 响应流畅 | 完全可用 |
| 命令行工具 | 无图形 | 2秒 | 接近原生 | 完全可用 |
从数据可以看出,图形越复杂、API 越新,性能损耗越大。D3D9 通过 OpenGL 路径反而比 D3D11 通过 DXMT 更流畅,这是因为 OpenGL 在 ARM Linux 上的驱动成熟度更高。如果你的目标应用是 D3D9 时代的,不妨先试试 WineD3D 路径,可能比 DXMT 更省心。
5.2 编译优化与运行参数调校
FEX-Emu 和 Wine 的编译选项对最终性能影响很大。除了前面提到的 Release 构建和关闭断言,还有几个选项值得关注。FEX-Emu 的-DENABLE_LTO=ON开启链接时优化,能提升 5% 到 10% 的性能,但编译时间会增加不少。Wine 的--enable-optimize会启用编译器优化,默认是开启的,但如果你之前手动关过,记得打开。
运行时的参数调校同样重要。FEX-Emu 的FEX_TSO_ENABLED和FEX_MULTIBLOCK前面说过了,这里补充一个FEX_ROOTFS_OVERLAY参数,可以指定一个可写的覆盖层,让 RootFS 保持只读的同时允许程序写入临时文件。这个对需要写配置文件的程序很有用:
export FEX_ROOTFS_OVERLAY=~/.fex-emu/Overlay mkdir -p $FEX_ROOTFS_OVERLAYWine 这边,WINEDEBUG环境变量可以控制日志输出级别。调试时用WINEDEBUG=+d3d11,+dxgi查看图形相关日志,正常使用时设为WINEDEBUG=-all关闭所有日志以提升性能。这个差别在低性能设备上很明显,我实测关闭日志后帧率能提升 3 到 5 帧。
5.3 内存与存储的优化技巧
ARM 设备的内存通常比 x86 设备紧张,而 FEX-Emu 的翻译缓存和 Wine 的前缀文件都会占用不少空间。几个优化方向:一是限制 FEX-Emu 的翻译缓存大小,通过FEX_MAX_CACHE_SIZE环境变量设置上限,避免缓存无限增长;二是定期清理 Wine 前缀里的临时文件,$WINEPREFIX/drive_c/users/下的 Temp 目录经常积累大量垃圾;三是把 RootFS 和 Wine 前缀放在 SSD 上,机械存储会明显拖慢启动速度。
存储方面,FEX-Emu 的 RootFS 和 Wine 前缀加起来大概 3 到 5GB,加上 DXMT 的着色器缓存,总共需要预留 10GB 左右的空间。如果设备存储紧张,可以考虑把不常用的 RootFS 版本删掉,只保留当前使用的版本。
6. 后续扩展与个人体会
这套方案目前还在快速迭代中,FEX-Emu 和 DXMT 都在持续更新,每隔几个月就会有明显的改进。我个人的建议是保持关注上游仓库的 release 页面,但不要盲目追新——新版本可能引入新的 bug,稳定可用的版本比最新版本更重要。我通常会在一个新版本发布后等一两周,看看 issue 区有没有严重的回归问题,再决定是否升级。
另外值得一提的是,这套技术栈的思路其实可以迁移到其他场景。比如在 ARM 服务器上跑 x86 的遗留服务,或者在没有 x86 硬件的环境下做兼容性测试,FEX-Emu 加 Wine 的组合都能派上用场。DXMT 的 Metal 后端思路也可以借鉴到其他图形翻译项目里,理解它的架构设计对做类似工具很有帮助。
最后分享一个我在调试过程中总结的小技巧:遇到问题时,先把链路拆开单独测试。先确认 FEX-Emu 能跑一个简单的 x86-64 命令行程序,再确认 Wine 能启动记事本,最后才测试图形程序。每一步都确认无误后再往下走,比一上来就跑大型游戏然后面对一堆报错要高效得多。这个“分层验证”的习惯帮我省了很多时间,希望你也能用上。