1. 从“Madeira”这个名字说起:一个跨平台兼容层的野心
第一次看到“Madeira”这个项目名,很多人会以为是某个旅游项目或者葡萄酒相关的工具。但结合关键词里的 FEX-Emu、Wine、DXMT、iOS、x86-64 这几个词,方向就很清楚了——这是一个围绕跨架构指令翻译与 Windows 应用兼容运行的技术项目。Madeira 是葡萄牙的一座岛屿,而 FEX-Emu 的命名传统本身就带点地理色彩(FEX 取自 FEX-Emu 团队),所以 Madeira 大概率是这条技术路线上的一个新组件或新分支。
先把这几个关键词的关系理清楚,不然后面全是糊涂账:
- FEX-Emu:一个用户态的 x86-64 指令翻译层,核心作用是在 ARM64 设备上运行 x86-64 的 Linux 程序。它工作在用户空间,不需要内核补丁,靠动态二进制翻译(Dynamic Binary Translation)把 x86-64 指令实时翻译成 ARM64 指令。
- Wine:Windows 应用在类 Unix 系统上的兼容层,负责把 Windows API 调用翻译成 POSIX 调用。它不模拟硬件,只翻译 API,所以效率比完整虚拟机高得多。
- DXMT:DirectX 到 Metal 的翻译层,专门解决 Windows 游戏/图形程序在 Apple 平台上的图形 API 映射问题。D3D 调用被翻译成 Metal 调用,绕开了 OpenGL 这条老路。
- iOS / x86-64:这两个词放在一起,指向的是在 ARM64 的 iOS 设备上运行 x86-64 代码这个场景。
把这四者串起来,Madeira 的定位就浮出水面了:它很可能是一个把 FEX-Emu、Wine、DXMT 这三层技术栈整合起来,目标是在 ARM64 平台(尤其是 Apple 生态)上运行 x86-64 Windows 应用的集成方案。这不是简单的“装个 Wine 就完事”,而是三层翻译叠加:x86-64 到 ARM64 的指令翻译、Windows API 到 POSIX 的调用翻译、DirectX 到 Metal 的图形翻译。
为什么这件事值得单独做一个项目?因为单独用 Wine 在 ARM64 上跑 x86-64 Windows 程序是跑不起来的——Wine 本身不负责指令集翻译,它假设你的 CPU 能直接执行目标程序的指令。所以在 ARM64 上,必须有一个指令翻译层垫在 Wine 下面。FEX-Emu 就是干这个的。而图形部分,Wine 自带的 WineD3D 走的是 OpenGL,在 Apple 平台上 OpenGL 已经被弃用,性能和兼容性都不理想,所以需要 DXMT 把 D3D 直接翻译到 Metal。
这三层叠起来,才是 Madeira 这类项目真正要解决的问题。下面我按实际搭建和调试的顺序,把这套东西拆开讲。
2. 三层翻译栈的协作逻辑:为什么不能只装一个 Wine
2.1 指令翻译层为什么必须存在
很多人第一次接触这个领域时的直觉是:“Wine 不是能跑 Windows 程序吗?那我在 ARM 机器上装个 Wine 不就行了?”这个直觉错在了一个根本点上:Wine 是 API 翻译层,不是指令翻译层。
打个比方。假设你是一个只懂中文的人,要读一本法文书。Wine 相当于一个翻译,把法文(Windows API)翻译成中文(POSIX API)。但如果这本书是用一种你完全不认识的字母系统(x86-64 指令)印刷的,那翻译再厉害也没用,因为你连字母都认不出来。FEX-Emu 就是那个教你认字母系统的人,它把 x86-64 指令逐条翻译成 ARM64 指令,让 CPU 能真正执行。
FEX-Emu 的工作方式是动态二进制翻译。程序运行时,FEX 把 x86-64 的代码块翻译成 ARM64 代码块,翻译结果会被缓存起来,下次执行到同一块代码时直接复用。这个缓存机制是性能的关键——第一次执行慢,后续执行快。它和 QEMU 的用户态模式(qemu-user)思路类似,但 FEX 针对游戏和交互式应用做了更多优化,比如对 x86 特有的标志位处理、对 SSE/AVX 指令的映射等。
实测下来,FEX-Emu 在 ARM64 上跑 x86-64 程序的性能损耗大概在 30% 到 60% 之间,具体取决于程序的指令特征。计算密集型的程序损耗大,I/O 密集型的损耗小。这个数字意味着:轻量级应用基本可用,重度游戏会比较吃力,但配合 DXMT 的图形加速,部分游戏还是能跑到可玩帧率。
2.2 Wine 在 ARM64 上的特殊配置
Wine 在 ARM64 上跑 x86-64 Windows 程序,需要用到Wine 的 WoW64 模式。传统 WoW64 是 Windows 上 32 位程序跑在 64 位系统上的机制,Wine 借用了这个概念,实现了“新 WoW64”——让 32 位 Windows 程序在纯 64 位 Wine 上运行,不需要 32 位宿主库。
在 Madeira 这类方案里,Wine 的配置有几个关键点:
- Wine 版本选择:必须用支持新 WoW64 的版本,通常是 Wine 8.0 以上。老版本在 ARM64 上跑 x86-64 程序会有各种奇怪的问题。
- Wineprefix 架构:创建 prefix 时要明确指定
WINEARCH=win64,因为我们要跑的是 x86-64 程序。如果 prefix 被创建成 win32,后续装 x86-64 程序会直接报错。 - DLL 覆盖:DXMT 需要覆盖 Wine 自带的 d3d11.dll、d3d10core.dll、dxgi.dll 等图形相关 DLL。这些覆盖必须在 prefix 创建后、装程序前完成,否则程序可能已经加载了旧的 DLL。
这里有个容易踩的坑:Wine 的 DLL 覆盖有两种方式,一种是winecfg里图形界面设置,一种是直接改注册表。图形界面设置在批量部署时很麻烦,直接改注册表更可靠。注册表路径是HKEY_CURRENT_USER\Software\Wine\DllOverrides,把 d3d11、dxgi 等键值设成native就行。
2.3 DXMT 的图形翻译路径
DXMT 的核心工作是把 Direct3D 11/12 的调用翻译成 Metal 调用。为什么不用 WineD3D 走 OpenGL?因为 Apple 从 macOS 10.14 开始就弃用了 OpenGL,驱动停留在 OpenGL 4.1,很多 D3D11 的特性无法映射。而 Metal 是 Apple 的原生图形 API,驱动更新及时,特性支持完整。
DXMT 的翻译粒度是命令级的。D3D 的 DrawCall、资源创建、着色器编译等操作,都会被翻译成对应的 Metal 操作。着色器部分,DXMT 会把 HLSL 编译成 Metal Shading Language(MSL),这个编译过程在程序首次运行时完成,结果会被缓存。所以第一次跑某个游戏时着色器编译会卡顿,第二次就流畅了。
在 Madeira 的架构里,DXMT 是作为 Wine 的一个“图形后端”存在的。Wine 加载 d3d11.dll 时,实际加载的是 DXMT 提供的实现,而不是 Wine 自带的 WineD3D。这个替换过程对上层程序是透明的,程序以为自己还在调 D3D,实际上调用已经被 DXMT 接管并翻译成 Metal 了。
三层栈的调用链是这样的:
Windows 程序 (x86-64 指令) ↓ FEX-Emu 翻译指令 ARM64 指令执行 ↓ Wine 翻译 API POSIX 调用 ↓ DXMT 翻译图形 Metal 调用 ↓ GPU 执行每一层都有性能开销,但每一层都是必要的。少了 FEX,指令跑不起来;少了 Wine,API 对不上;少了 DXMT,图形渲染走 OpenGL 老路,性能和兼容性都差。
3. 在 Apple 生态上落地 Madeira 的实际步骤
3.1 环境准备与依赖安装
在 Apple 平台上搭建这套环境,第一步是确认系统版本和硬件。Apple Silicon(M 系列芯片)是 ARM64 架构,这是 FEX-Emu 能工作的前提。Intel Mac 不需要 FEX,因为本身就是 x86-64,但 Intel Mac 上 DXMT 的 Metal 支持取决于 GPU 型号,老款 Intel 核显的 Metal 特性支持不完整,可能跑不起来。
依赖安装的顺序很重要,顺序错了会出现各种链接错误:
- 安装 Homebrew:这是 macOS 上包管理的基础,后面大部分依赖都通过它装。
- 安装 FEX-Emu:FEX 在 Homebrew 上有 formula,直接
brew install fex-emu即可。装完后要确认FEXBash或FEXInterpreter能正常运行。 - 安装 Wine:建议用
brew install --cask wine-stable,或者用 WineHQ 的官方构建。注意要选支持 ARM64 的版本。 - 编译或安装 DXMT:DXMT 目前没有现成的 Homebrew formula,需要从源码编译。编译需要 Xcode Command Line Tools 和 Metal 工具链。
这里有个实测经验:FEX-Emu 和 Wine 的安装顺序会影响动态库的查找路径。如果先装 Wine 再装 FEX,Wine 可能链接到系统自带的翻译层而不是 FEX。正确的做法是先装 FEX,确认 FEX 的库路径在PATH和DYLD_LIBRARY_PATH里,再装 Wine。
3.2 Wineprefix 的创建与 DXMT 注入
创建 Wineprefix 的命令行操作:
export WINEPREFIX=~/madeira-prefix export WINEARCH=win64 wineboot --init这三行做完,~/madeira-prefix目录下会生成一个完整的 Windows 目录结构,包括drive_c、注册表文件等。接下来是 DXMT 的注入:
# 把 DXMT 的 DLL 复制到 prefix 的 system32 目录 cp dxmt/d3d11.dll $WINEPREFIX/drive_c/windows/system32/ cp dxmt/dxgi.dll $WINEPREFIX/drive_c/windows/system32/ cp dxmt/d3d10core.dll $WINEPREFIX/drive_c/windows/system32/ # 设置 DLL 覆盖 wine reg add "HKEY_CURRENT_USER\Software\Wine\DllOverrides" /v d3d11 /t REG_SZ /d native /f wine reg add "HKEY_CURRENT_USER\Software\Wine\DllOverrides" /v dxgi /t REG_SZ /d native /f wine reg add "HKEY_CURRENT_USER\Software\Wine\DllOverrides" /v d3d10core /t REG_SZ /d native /f注意:DLL 覆盖设置成
native后,Wine 会优先加载 prefix 里的 DLL,而不是自带的。如果 DXMT 的 DLL 有问题,程序会直接崩溃而不是回退到 WineD3D。调试时可以先设成builtin确认 Wine 本身能跑,再切回native测 DXMT。
3.3 运行第一个 x86-64 Windows 程序
找一个简单的 x86-64 Windows 程序做测试,比如 7-Zip 的命令行版本或者一个小的 D3D 测试程序。运行命令:
FEXBash -c "WINEPREFIX=~/madeira-prefix wine /path/to/program.exe"FEXBash是 FEX-Emu 提供的 shell 包装,它会把 shell 里启动的所有 x86-64 程序都通过 FEX 翻译执行。如果不套 FEXBash,直接wine program.exe,Wine 会尝试用 ARM64 的方式加载 x86-64 的 exe,结果就是“无法执行二进制文件”的错误。
第一次运行会明显慢,因为 FEX 在翻译指令并缓存。第二次运行同一程序会快很多。如果程序有图形界面,DXMT 会在首次渲染时编译着色器,这也会造成卡顿。这些都是正常现象,不是配置错误。
4. 调试与排错:那些文档里不会写的坑
4.1 Wine 乱码问题的根因与修复
“wine 乱码”是搜索热词里出现频率很高的问题,在 Madeira 这类方案里同样会遇到。乱码的根因通常有三个:
字体缺失。Wine 默认不带中文字体,程序调用中文字体时找不到,就会显示成方块或乱码。解决办法是把系统中文字体链接到 Wine 的字体目录:
ln -s /System/Library/Fonts/PingFang.ttc $WINEPREFIX/drive_c/windows/Fonts/或者从 Linux 系统拷贝文泉驿、Noto Sans CJK 等字体到drive_c/windows/Fonts/。
编码设置错误。Wine 的 locale 设置和程序期望的编码不一致时,非 ASCII 字符会乱码。在winecfg里把 locale 设成zh_CN.UTF-8,或者在启动时加LANG=zh_CN.UTF-8环境变量。
注册表字体替换没配。Wine 有一套字体替换机制,在注册表HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes里配置。把MS Shell Dlg和MS Shell Dlg 2映射到实际存在的中文字体,能解决大部分界面乱码。
实测下来,字体问题解决了,90% 的乱码就消失了。剩下 10% 是程序自己硬编码了字体名,Wine 找不到对应字体,这种只能靠装更多字体或者用winetricks装字体包来解决。
4.2 FEX-Emu 的性能调优参数
FEX-Emu 有几个环境变量能显著影响性能,这些在官方文档里藏得比较深:
| 环境变量 | 作用 | 推荐值 |
|---|---|---|
FEX_TSOENABLED | 控制 x86 内存序模拟 | 1(默认,兼容性好) |
FEX_VECTORTSOENABLED | 向量指令的内存序模拟 | 1 |
FEX_MEMCPY_SET | 内存拷贝优化策略 | 根据 CPU 调 |
FEX_ROOTFS | 指定根文件系统路径 | 按需设置 |
FEX_CACHE_SIZE | 翻译缓存大小 | 512M 起步 |
FEX_TSOENABLED这个参数值得单独说。x86 的内存序模型是 TSO(Total Store Order),ARM 是弱内存序。FEX 默认会插入内存屏障来模拟 TSO,这保证了多线程程序的正确性,但有性能开销。如果跑的是单线程程序,或者程序本身不依赖严格内存序,可以关掉这个选项换性能。但关掉之后有些程序会随机崩溃,所以生产环境建议保持开启。
翻译缓存大小也很关键。FEX 把翻译后的 ARM64 代码缓存在内存里,缓存越大,能缓存的代码块越多,重复翻译越少。但缓存太大会挤占程序本身的内存。512M 到 1G 是比较平衡的范围,具体看设备内存大小。
4.3 DXMT 着色器编译卡顿的缓解
DXMT 首次运行游戏时的着色器编译卡顿,是体验上最大的痛点。缓解办法有几个:
预编译着色器缓存。如果游戏支持,可以在别的机器上跑一遍生成着色器缓存,然后把缓存文件拷过来。DXMT 的缓存文件通常在~/Library/Caches/DXMT/或者 prefix 的drive_c/users/xxx/Temp/下。
异步着色器编译。DXMT 较新版本支持异步编译,着色器在后台线程编译,主线程继续渲染。这会导致部分物体暂时不显示,但不会卡顿。开启方式是在 DXMT 配置里设async_shader_compilation = true。
降低画质设置。着色器数量和画质设置直接相关。把阴影、反射、后处理等吃着色器的选项调低,能显著减少首次编译的着色器数量。
4.4 程序崩溃时的排查链路
程序崩溃时,不要急着重装,按这个链路排查:
- 看 Wine 的调试输出。启动时加
WINEDEBUG=+all,Wine 会打印所有 API 调用。输出量很大,但崩溃前的最后几行通常能指出问题。 - 确认 FEX 是否正常翻译。加
FEX_LOG_LEVEL=info,看 FEX 有没有报翻译失败。如果某个指令 FEX 不认识,会在这里报出来。 - 检查 DXMT 的日志。DXMT 会在
~/Library/Logs/DXMT/下写日志,图形相关的崩溃通常在这里有线索。 - 回退 DLL 覆盖。把 d3d11、dxgi 的覆盖从
native改回builtin,如果程序能跑(虽然图形可能不对),说明问题在 DXMT;如果还是崩溃,问题在 Wine 或 FEX。 - 换一个简单程序测试。用一个已知能跑的程序(比如 notepad)测试,确认基础环境没问题,再测目标程序。
这个链路的核心思路是逐层剥离:先确认 FEX 没问题,再确认 Wine 没问题,最后确认 DXMT 没问题。不要一上来就怀疑最上层,底层的问题会伪装成上层的问题。
5. 这套方案能跑什么、不能跑什么
5.1 实际可用的程序类型
根据实测和社区反馈,Madeira 这类三层翻译方案能跑的程序大致分几类:
办公类:老版本的 Office(2010 到 2016)、Notepad++、7-Zip、WinRAR 等。这类程序对图形要求低,主要吃 CPU 和 I/O,FEX 的翻译开销可以接受。
轻量游戏:2D 游戏、老款 3D 游戏(DirectX 9 时代)、独立游戏。DXMT 对 D3D11 的支持比较完整,D3D9 通过 D3D11 的兼容层也能跑。但 D3D12 的支持还在完善中,新游戏跑起来问题较多。
开发工具:一些只有 Windows 版本的 IDE、串口调试工具、嵌入式开发工具。这类工具通常对性能不敏感,能跑起来就行。
不能跑的:带内核态驱动的程序(反作弊系统、虚拟化软件)、依赖特定硬件指令的程序(AVX-512 密集计算)、对时序要求极高的程序(音频实时处理)。
5.2 性能预期管理
不要期望这套方案能跑到原生性能。三层翻译叠加,性能损耗是客观存在的。合理的预期是:
- 办公程序:流畅度约为原生的 60% 到 80%
- 轻量游戏:帧率约为原生的 40% 到 60%
- 重度游戏:帧率约为原生的 20% 到 40%,且可能有兼容性问题
这个预期不是劝退,而是让你在调试时有个参照。如果一个程序跑起来只有原生 10% 的性能,那说明配置有问题,不是方案本身的上限。
5.3 和虚拟机的对比
有人会问:为什么不直接用虚拟机跑 Windows?虚拟机(如 Parallels、UTM)在 ARM64 上跑 Windows ARM 版,性能比三层翻译好,兼容性也好。但虚拟机的代价是:
- 资源占用大:虚拟机要分配固定内存和磁盘,三层翻译方案是进程级的,按需占用。
- 启动慢:虚拟机启动要几十秒,三层翻译方案启动一个程序只要几秒。
- 集成度低:虚拟机里的程序很难和宿主系统的文件系统、剪贴板深度集成,三层翻译方案天然共享文件系统。
所以两者的适用场景不同:需要跑一个完整的 Windows 环境,用虚拟机;只需要跑某几个 Windows 程序,用三层翻译方案更轻量。
6. 从 Madeira 看跨平台兼容的技术走向
Madeira 这类项目的价值,不只是“让某个 Windows 程序在 ARM 上跑起来”,而是验证了一条技术路径:用多层用户态翻译替代硬件虚拟化。这条路走通了,意味着跨平台兼容不再依赖 CPU 的虚拟化扩展,而是靠软件层的翻译和映射。
FEX-Emu 的指令翻译、Wine 的 API 翻译、DXMT 的图形翻译,三层各司其职,层与层之间的接口是标准化的(ELF 加载、POSIX 调用、Metal API)。这种分层设计的好处是每层可以独立演进:FEX 可以优化翻译算法,Wine 可以补更多 API,DXMT 可以支持更多 D3D 特性,互不影响。
实际搭建过程中,我最大的体会是日志和分层排查比什么都重要。三层栈叠在一起,出问题时表象可能一样(程序崩溃),但根因可能在任意一层。养成看日志、逐层剥离的习惯,能省下大量瞎试的时间。另外,不要追求一次配好所有东西,先用最简单的程序把三层栈跑通,再逐步加复杂度,这样每一步都有明确的验证点。
这套方案目前还在快速迭代中,FEX 的翻译效率、DXMT 的 D3D12 支持、Wine 的新 WoW64 稳定性,都在持续改进。如果你手头有 ARM64 设备,又恰好有几个非跑不可的 Windows 程序,值得花时间搭一套试试。踩坑是必然的,但踩完之后对系统底层的理解会上一个台阶。