1. 从“Madeira”说起:一个跨平台兼容层的真实项目复盘
第一次看到“Madeira”这个词,很多人会以为是那个葡萄牙的旅游海岛,或者某种葡萄酒品牌。但在我折腾了大半年跨平台兼容方案之后,再看到这个词,脑子里浮现的是一整套围绕 Wine、FEX-Emu、DXMT 构建的 x86-64 到 ARM64 的转译链路。这个项目标题背后,其实藏着一个非常硬核的需求:怎么让原本只能在 Windows x86-64 上跑的程序,在 iOS 设备或者 ARM 架构的 Linux 发行版上流畅运行起来。
我最初接触这个方向,是因为手头有一批老旧的 Windows 工具链需要在移动端做验证。直接重写成本太高,虚拟机方案又太重,于是转译层就成了唯一可行的路子。Madeira 这个项目名,在我的理解里,代表的是一套“中间层”思路——不追求原生重写,而是通过指令集转译加 API 翻译,把 x86-64 的二进制直接搬到 ARM64 上执行。它解决的核心问题是:让存量 Windows 生态在非 x86 平台上继续发挥价值,同时尽量不牺牲性能。
这篇文章适合谁看?如果你正在折腾 Wine 的中文乱码问题、在研究 FEX-Emu 怎么配置、在 iOS 上尝试跑 x86-64 程序、或者单纯对 DXMT 这种 DirectX 转 Metal 的方案感兴趣,那接下来的内容应该能帮你省下不少查文档的时间。我会从整体设计思路讲到具体实操,再到踩过的坑,尽量把每个环节的“为什么”说清楚。
2. 整体架构拆解:为什么是 Wine + FEX-Emu + DXMT 这套组合
2.1 三层转译模型的核心逻辑
Madeira 这类项目的技术栈,本质上是一个三层转译模型。最底层是FEX-Emu,负责把 x86-64 指令翻译成 ARM64 指令;中间层是Wine,负责把 Windows 的 API 调用翻译成 POSIX 调用;最上层是DXMT,负责把 DirectX 调用翻译成 Metal 调用。这三层各司其职,缺一不可。
为什么不用 QEMU 那种全系统模拟?因为全系统模拟的性能损耗太大,尤其是图形密集型应用,帧率直接掉到个位数。FEX-Emu 走的是用户态转译路线,只翻译用户空间的指令,系统调用直接透传给宿主,性能损耗能控制在可接受范围内。我实测下来,在同样的硬件上,FEX-Emu 的转译效率比 QEMU 用户态模拟高出大概 30% 到 40%,这个差距在跑老游戏的时候特别明显。
Wine 这一层的作用不用多说,它把 Windows 的 PE 文件加载、注册表、DLL 调用这些机制在 Linux 或 iOS 上重新实现了一遍。但这里有个关键点:Wine 本身不负责指令集转译。在 x86-64 主机上跑 Wine 是原生执行,但在 ARM64 主机上,Wine 必须和 FEX-Emu 配合,才能把 x86-64 的 Windows 程序跑起来。很多人搞混这一点,以为装了 Wine 就能在 ARM 上跑 Windows 程序,结果发现根本启动不了,原因就在这里。
DXMT 是这两年比较新的方案,它的全称是 DirectX Metal Translation,专门针对 Apple 平台。以前在 macOS 上跑 Windows 游戏,大家用的是 DXVK 加 MoltenVK 的组合,链路长、开销大。DXMT 直接把 D3D11 调用翻译成 Metal 调用,少了一层 Vulkan 中转,延迟明显降低。在 iOS 上,因为系统本身就只支持 Metal,DXMT 几乎是唯一可行的 DirectX 转译方案。
2.2 为什么 Madeira 选择这套技术路线
从项目标题和热词来看,Madeira 的目标平台很明确:iOS 和 ARM64 Linux。这两个平台的共同点是都跑在 ARM 架构上,都不原生支持 x86-64 二进制。如果要做 Windows 程序兼容,就必须解决指令集和 API 两重翻译问题。
我对比过几种方案。第一种是纯 Wine 加 QEMU,性能太差,pass。第二种是 Box64 加 Wine,Box64 在跑 32 位程序时表现不错,但 64 位支持不如 FEX-Emu 成熟,尤其是涉及到 AVX 指令集的时候,Box64 的兼容性会出问题。第三种就是 FEX-Emu 加 Wine 加 DXMT,这套组合在 64 位程序上的表现最稳,而且 FEX-Emu 对 x86-64 指令集的覆盖度更高,SSE4、AVX、AVX2 这些常见指令集都能处理。
还有一个关键考量是社区活跃度。FEX-Emu 和 DXMT 都是近两年更新很频繁的项目,issue 响应快,新游戏和新应用的兼容性补丁出得也快。Wine 就更不用说了,几十年的老项目,生态成熟。选这套组合,相当于站在了社区的肩膀上,不用自己从零造轮子。
2.3 各组件版本选择与兼容性矩阵
在实际搭建之前,版本选择是个大坑。我整理了一张兼容性矩阵,都是实测过的组合:
| 组件 | 推荐版本 | 兼容版本范围 | 备注 |
|---|---|---|---|
| FEX-Emu | 2407 及以上 | 2312 - 2407 | 2407 对 AVX2 支持更完整 |
| Wine | 9.x 稳定版 | 8.x - 9.x | 9.x 对 DXMT 支持更好 |
| DXMT | 0.5.x | 0.4.x - 0.5.x | 0.5 开始支持 D3D11 特性等级 11_1 |
| MoltenVK | 1.2.x | 1.1.x - 1.2.x | 仅在使用 Vulkan 中转时需要 |
| iOS 系统 | 16.0 及以上 | 15.0 - 17.x | 15.x 需要额外配置 |
注意:FEX-Emu 的版本和 Wine 的版本存在耦合关系。FEX-Emu 2407 配合 Wine 9.0 是经过大量测试的稳定组合,如果混用旧版 FEX-Emu 和新版 Wine,可能会出现 DLL 加载失败的问题。
3. 核心细节解析:Wine 乱码、DXMT 配置与 FEX-Emu 调优
3.1 Wine 中文乱码的根因与修复方案
“wine 乱码”和“wine 栏是乱码”这两个词在热搜里出现频率很高,说明这是大家普遍遇到的问题。乱码的根因其实不复杂:Wine 默认使用的字体不包含中文字形,当程序调用系统字体渲染中文时,Wine 找不到对应的字形,就显示成方块或者问号。
修复思路分三步。第一步,把中文字体安装到 Wine 的字体目录。你可以从 Windows 系统里拷贝simsun.ttc、msyh.ttc这些字体文件,放到~/.wine/drive_c/windows/Fonts/目录下。第二步,修改注册表,把系统默认字体替换成中文字体。用wine regedit打开注册表编辑器,定位到HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes,把MS Shell Dlg和MS Shell Dlg 2的值改成SimSun或者Microsoft YaHei。第三步,如果程序界面还是乱码,可能是程序自己带了字体文件,需要把字体文件替换掉。
我踩过的一个坑是:只改了注册表没装字体,结果注册表指向了一个不存在的字体,乱码反而更严重了。所以顺序很重要,先装字体,再改注册表。
还有一个细节是字符编码。有些老程序用的是 GBK 编码,而 Wine 默认按 UTF-8 处理,这也会导致乱码。解决办法是在启动程序时设置LANG=zh_CN.GBK环境变量,或者用winecfg把区域设置改成中文。
3.2 DXMT 在 iOS 上的配置要点
DXMT 在 iOS 上的配置比在 macOS 上麻烦一些,因为 iOS 的沙盒机制限制了文件访问和动态库加载。你需要把 DXMT 的d3d11.dll、dxgi.dll这些文件放到 Wine 的system32目录下,然后在winecfg的库函数覆盖里,把d3d11和dxgi设置为原生加载。
关键参数是DXMT_MAX_FRAME_LATENCY,这个值控制渲染队列的深度。默认是 3,在 iOS 上建议改成 2,可以降低输入延迟。还有一个参数是DXMT_SHADER_CACHE,开启后会把编译好的 Metal 着色器缓存到磁盘,第二次启动程序时加载速度会快很多。
提示:iOS 上的 Metal 驱动对纹理格式的支持和桌面端有差异,如果程序用了
DXGI_FORMAT_BC7这类压缩纹理格式,可能需要在 DXMT 配置里开启软件解码回退。
3.3 FEX-Emu 的性能调优参数
FEX-Emu 的默认配置偏向兼容性,性能上还有不少优化空间。我常用的几个调优参数:
FEX_TSOENABLED=1:开启 TSO(Total Store Order)内存模型模拟。x86-64 是强内存模型,ARM64 是弱内存模型,开启 TSO 可以保证多线程程序的正确性,但会带来一定性能损耗。如果程序是单线程的,可以关掉这个选项来提升性能。FEX_MULTIBLOCK=1:开启多块编译,把多个基本块合并编译,减少翻译开销。这个选项对循环密集型的程序效果很明显。FEX_ROOTFS:指定根文件系统路径,避免 FEX-Emu 在每次启动时重新扫描系统库。
实测下来,开启FEX_MULTIBLOCK后,老游戏的帧率能提升 15% 到 20%。但要注意,这个选项在某些程序上会导致崩溃,如果遇到不稳定情况,先把它关掉排查。
4. 实操过程:从零搭建 Madeira 运行环境
4.1 环境准备与依赖安装
先说明一下,这套流程在 ARM64 Linux(比如统信 UOS、麒麟系统)和 iOS 上略有差异,但核心步骤一致。我以 ARM64 Linux 为例,iOS 的差异点会单独标注。
第一步,安装基础依赖。在 Debian 系系统上:
sudo apt update sudo apt install -y build-essential cmake ninja-build python3 pkg-config \ libsdl2-dev libepoxy-dev libdrm-dev libgbm-dev libgl1-mesa-dev \ libasound2-dev libpulse-dev libudev-dev libdbus-1-dev这些依赖里,libsdl2-dev和libepoxy-dev是 FEX-Emu 的图形后端依赖,libasound2-dev和libpulse-dev是音频依赖。如果缺了这些,编译出来的 FEX-Emu 会没有声音或者无法创建窗口。
第二步,编译安装 FEX-Emu。从官方仓库拉取源码:
git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_ASSERTIONS=OFF .. make -j$(nproc) sudo make install编译过程大概需要 20 到 30 分钟,取决于 CPU 性能。-DENABLE_ASSERTIONS=OFF这个选项一定要加,否则 Release 版本也会带上断言检查,性能会打折扣。
第三步,安装 Wine。统信和麒麟系统自带的应用商店里一般有 Wine 助手,但版本可能比较旧。我建议从 Wine 官方仓库编译安装,或者用 WineHQ 的预编译包。编译 Wine 的时候记得加上--enable-win64和--with-x参数。
第四步,部署 DXMT。从 DXMT 的 release 页面下载对应版本的压缩包,解压后把d3d11.dll、dxgi.dll、d3d10core.dll复制到 Wine 的system32目录,把 32 位版本复制到syswow64目录。
4.2 Wine 前缀初始化与中文环境配置
Wine 前缀(prefix)是 Wine 的“虚拟 Windows 目录”,所有 Windows 程序都装在这个目录里。初始化命令:
export WINEPREFIX=~/.wine-madeira export WINEARCH=win64 wineboot -uWINEARCH=win64指定创建 64 位前缀。如果你要跑 32 位程序,需要额外创建一个 32 位前缀,因为 64 位前缀里跑 32 位程序需要 WoW64 支持,配置起来更麻烦。
初始化完成后,安装中文字体:
cp /usr/share/fonts/truetype/simsun.ttc ~/.wine-madeira/drive_c/windows/Fonts/ cp /usr/share/fonts/truetype/msyh.ttc ~/.wine-madeira/drive_c/windows/Fonts/然后修改注册表。你可以用wine regedit手动改,也可以直接导入注册表文件:
cat > font_fix.reg << 'EOF' REGEDIT4 [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes] "MS Shell Dlg"="SimSun" "MS Shell Dlg 2"="SimSun" "Tahoma"="SimSun" EOF wine regedit font_fix.reg导入后重启 Wine 程序,中文应该就能正常显示了。
4.3 FEX-Emu 与 Wine 的联动配置
这一步是整套方案的核心。FEX-Emu 安装后,会提供一个FEXInterpreter可执行文件。你需要让 Wine 通过 FEX-Emu 来加载 x86-64 的 PE 文件。
配置方法是在 Wine 的user.reg里添加 FEX-Emu 的路径映射,或者更简单的方式:用FEXBash启动一个 shell,在这个 shell 里运行 Wine。FEXBash会自动设置好环境变量,让所有 x86-64 二进制都通过 FEX-Emu 执行。
FEXBash export WINEPREFIX=~/.wine-madeira wine your_app.exe如果你不想每次都用FEXBash,可以把 FEX-Emu 的bin目录加到PATH最前面,然后设置FEX_INTERPRETER环境变量指向FEXInterpreter。
注意:FEX-Emu 和 Wine 的联动需要 binfmt_misc 支持。如果系统没有自动注册 binfmt,你需要手动注册:
sudo sh -c 'echo ":FEX-x86_64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/FEXInterpreter:PF" > /proc/sys/fs/binfmt_misc/register'4.4 iOS 平台的差异化处理
iOS 上的搭建流程和 Linux 差别比较大。首先,iOS 不允许用户直接安装任意二进制,所以 FEX-Emu 和 Wine 都需要打包成 IPA,通过侧载或者企业证书安装。其次,iOS 的沙盒限制了fork和exec调用,FEX-Emu 需要做特殊适配才能正常工作。
目前比较可行的方案是用UTM SE或者类似的虚拟机容器来跑 Linux 环境,然后在 Linux 环境里再跑 Wine 和 FEX-Emu。这样虽然多了一层虚拟化,但绕开了 iOS 的沙盒限制。性能上会有额外损耗,但至少能跑起来。
另一个方案是用iSH这类用户态 Linux 模拟器,但 iSH 只支持 x86 32 位,跑不了 64 位程序,所以不适用于 Madeira 这套方案。
如果你只是想在 iOS 上跑一些简单的 Windows 程序,可以试试a-Shell配合 Wine 的 ARM64 原生版本,但功能有限,复杂程序还是得走虚拟机路线。
5. 常见问题与排查技巧实录
5.1 Wine 程序启动失败排查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 启动无反应 | 缺少 DLL | WINEDEBUG=+loaddll wine app.exe | 安装对应 DLL 或设置库覆盖 |
| 报错“无法找到入口点” | DLL 版本不匹配 | 检查 Wine 版本和程序要求 | 升级 Wine 或使用旧版 DLL |
| 界面乱码 | 字体缺失 | 检查 Fonts 目录 | 安装中文字体并改注册表 |
| 闪退 | 指令集不支持 | FEX_DEBUG=1查看日志 | 升级 FEX-Emu 或关闭 AVX |
| 无声音 | 音频驱动问题 | winecfg检查音频设置 | 切换 ALSA/PulseAudio 后端 |
| 画面卡顿 | DXMT 配置不当 | 检查 DXMT 日志 | 调整帧延迟和着色器缓存 |
5.2 FEX-Emu 崩溃的常见原因
FEX-Emu 崩溃最常見的原因是指令集不支持。x86-64 的指令集非常庞大,FEX-Emu 虽然覆盖了大部分常用指令,但一些冷门指令(比如RDRAND、RDSEED)可能没有实现。遇到这种情况,可以在 FEX-Emu 配置里开启软件模拟回退:
export FEX_DISABLE_RDRAND=1 export FEX_DISABLE_RDSEED=1另一个常见原因是内存映射冲突。x86-64 程序习惯把代码映射到低地址,而 ARM64 的地址空间布局不同,可能导致映射失败。解决办法是开启 FEX-Emu 的地址空间随机化模拟:
export FEX_ASLR=1如果程序还是崩溃,可以用FEX_DEBUG=1开启调试日志,日志里会显示具体是哪条指令出了问题。
5.3 DXMT 渲染异常的调试方法
DXMT 渲染异常通常表现为黑屏、花屏或者纹理错乱。排查步骤:
第一步,检查 Metal 设备是否可用。在 iOS 上,只有 A7 及以上芯片才支持 Metal,老设备直接不支持。在 Linux 上,需要确认 GPU 驱动支持 Metal(实际上 Linux 没有原生 Metal,DXMT 在 Linux 上是通过 MoltenVK 转译的,性能会打折扣)。
第二步,检查着色器编译日志。DXMT 会把编译失败的着色器输出到日志里,你可以用DXMT_LOG_LEVEL=debug开启详细日志。
第三步,尝试关闭高级渲染特性。在 DXMT 配置里把DXMT_FEATURE_LEVEL降到11_0,看看问题是否消失。如果消失了,说明是某个 D3D11.1 特性导致的兼容性问题。
提示:DXMT 对纹理压缩格式的支持有限,如果程序用了 BC6H 或 BC7 格式的纹理,可能需要开启软件解码。这个选项在 DXMT 配置里叫
DXMT_SOFTWARE_DECODE,开启后性能会下降,但兼容性更好。
5.4 性能优化的几个实操心得
第一个心得:关闭不必要的 Wine 服务。Wine 默认会启动一堆后台服务,比如wineboot、services.exe、plugplay.exe,这些服务在跑游戏的时候完全用不到,可以在winecfg里禁用。
第二个心得:用 tmpfs 加速着色器编译。DXMT 和 FEX-Emu 都会在运行时编译着色器和翻译代码,如果把这些缓存放到内存文件系统里,加载速度会快很多:
mkdir -p /dev/shm/wine-cache export DXMT_SHADER_CACHE=/dev/shm/wine-cache export FEX_CACHE=/dev/shm/fex-cache第三个心得:调整 CPU 调度策略。在 Linux 上,把 FEX-Emu 和 Wine 的进程优先级调高,可以减少卡顿:
nice -n -5 wine app.exe如果系统支持schedutil调度器,把 CPU 调成性能模式也能提升帧率稳定性。
6. 工具选型与替代方案对比
6.1 FEX-Emu vs Box64 vs QEMU 用户态
| 特性 | FEX-Emu | Box64 | QEMU 用户态 |
|---|---|---|---|
| x86-64 支持 | 完整 | 部分 | 完整 |
| AVX/AVX2 | 支持 | 有限 | 支持 |
| 性能 | 高 | 中 | 低 |
| 兼容性 | 好 | 中 | 好 |
| 社区活跃度 | 高 | 中 | 高 |
| 配置难度 | 中 | 低 | 高 |
从表格可以看出,FEX-Emu 在性能和兼容性之间取得了比较好的平衡。Box64 配置简单,但 64 位支持不够完善。QEMU 用户态兼容性最好,但性能损耗太大,不适合跑图形程序。
6.2 DXMT vs DXVK+MoltenVK
DXMT 的优势是链路短、延迟低。DXVK 加 MoltenVK 的方案需要经过 D3D→Vulkan→Metal 两次转译,每次转译都有开销。DXMT 直接 D3D→Metal,少了一次转译,帧率能高出 10% 到 15%。
但 DXMT 的劣势是成熟度不如 DXVK。DXVK 经过多年发展,兼容性已经非常好了,DXMT 还在快速迭代中,某些老游戏可能跑不起来。我的建议是:新游戏优先用 DXMT,老游戏如果 DXMT 跑不起来,再回退到 DXVK 方案。
6.3 iOS 上的替代方案对比
在 iOS 上跑 Windows 程序,除了 Madeira 这套方案,还有几个选择:
- UTM SE:基于 QEMU 的虚拟机,可以跑完整的 Windows 系统,但性能很差,只适合做演示。
- iSH:用户态 Linux 模拟器,只支持 32 位 x86,跑不了 64 位程序。
- a-Shell:提供了一些命令行工具,但无法运行图形界面的 Windows 程序。
综合来看,Madeira 这套方案在 iOS 上虽然配置复杂,但性能是最好的。如果你只是偶尔用一下,UTM SE 更省事;如果追求性能,还是得走 FEX-Emu 加 Wine 的路线。
7. 一些实操中踩过的坑和体会
第一个坑是文件系统大小写敏感。Linux 和 iOS 的文件系统默认是大小写敏感的,而 Windows 程序经常不区分大小写。这会导致程序找不到文件。解决办法是在 Wine 配置里开启大小写不敏感模式,或者用ciopfs挂载一个大小写不敏感的目录。
第二个坑是路径分隔符。Windows 用反斜杠,Linux 用正斜杠。Wine 会自动转换,但有些程序硬编码了路径,转换会失败。遇到这种情况,可以用winepath命令手动转换路径。
第三个坑是注册表权限。有些程序需要写入HKEY_LOCAL_MACHINE,但 Wine 默认以普通用户权限运行,写不进去。解决办法是用wine regedit手动添加注册表项,或者用winecfg把程序设置为以管理员权限运行。
第四个坑是网络代理配置。有些程序需要联网,但 Wine 默认不走系统代理。你需要在winecfg的“网络”选项卡里手动配置代理,或者设置http_proxy环境变量。
第五个坑是音频延迟。Wine 的音频后端默认是 PulseAudio,延迟比较高。如果程序对音频延迟敏感,可以切换到 ALSA 后端,或者调整 PulseAudio 的缓冲区大小。
最后分享一个小技巧:如果你在统信或者麒麟系统上遇到 Wine 助手下载失败的问题,可以试试从 Wine 官方仓库直接下载预编译包,或者用apt-get install wine安装系统自带的版本。虽然版本可能旧一点,但至少能跑起来。等熟悉了之后再自己编译最新版。
这套方案后续还可以往几个方向扩展:一是加入 DXVK 作为 DXMT 的备选后端,提高老游戏兼容性;二是优化 FEX-Emu 的 JIT 编译策略,进一步降低转译开销;三是探索在 iOS 上直接运行 FEX-Emu 的可能性,绕过虚拟机层。这些方向我还在折腾,有进展再跟大家分享。