
1. 项目缘起为什么要在 iOS 上折腾 x86-64 的 Windows 程序第一次看到 “Madeira” 这个项目名很多人会以为是某个旅游地或者葡萄酒品牌毕竟热搜词里还挂着 Wine。但在 iOS 逆向和跨平台兼容圈子里Madeira 指向的是一件事在 iOS 设备上运行 x86-64 架构的 Windows 应用程序。这听起来像是天方夜谭毕竟 iOS 设备跑的是 ARM 架构系统封闭程度众所周知而 Windows 程序又是 x86-64 的天下。但恰恰是这种“不可能三角”催生了一套非常有意思的技术组合Wine FEX-Emu DXMT。我自己是从折腾 iOS 上的模拟器开始接触这个方向的。最开始只是想看看能不能在 iPad 上跑一些老旧的 Windows 工具后来发现社区里已经有人把 Wine 的 ARM64 版本移植到了 iOS 上再配合 FEX-Emu 做 x86-64 到 ARM64 的指令翻译最后用 DXMT 把 DirectX 调用转成 Metal。整条链路跑通之后确实能运行一部分 Windows 程序虽然离“完美”还差得远但作为技术验证和特定场景下的工具使用已经足够让人兴奋了。这篇文章适合几类人看一是对 iOS 底层和跨平台兼容感兴趣的开发者二是想在 iOS 设备上跑特定 Windows 工具但不想带笔记本的人三是单纯好奇 Wine、FEX-Emu、DXMT 这套组合拳怎么落地的人。我会从整体设计思路讲起然后拆解核心细节接着给出可复现的实操流程最后把踩过的坑和排查技巧整理出来。内容基于社区公开资料和我自己的实测经验部分细节属于合理推断我会明确标注。2. 整体架构拆解Wine、FEX-Emu、DXMT 各自扮演什么角色2.1 三层翻译栈的分工逻辑要在 iOS 上跑 Windows x86-64 程序本质上要解决三个层面的问题系统调用翻译、指令集翻译、图形 API 翻译。Madeira 这个项目名下面实际上是一套组合方案每一层都有专门的组件负责。第一层是 Wine。Wine 的核心作用是提供 Windows API 的实现把 Windows 程序发出的系统调用转成 POSIX 调用。比如一个 Windows 程序调用CreateFileWine 会把它翻译成 iOS 上对应的文件操作。但 Wine 本身不负责指令集翻译它假设程序已经是目标架构的二进制。所以在 ARM 设备上Wine 需要配合指令翻译层才能跑 x86-64 程序。第二层是 FEX-Emu。这是一个 x86-64 到 ARM64 的二进制翻译器专门为 Linux 和类 Unix 系统设计。它的工作方式是指令级翻译把 x86-64 的机器码动态翻译成 ARM64 指令。FEX-Emu 的厉害之处在于它支持 x86-64 的大部分指令集扩展包括 SSE、AVX 等而且有 JIT 缓存机制重复执行的代码块会被缓存下来避免反复翻译。在 Madeira 的架构里FEX-Emu 负责把 Windows 程序的 x86-64 指令转成 iOS 设备能执行的 ARM64 指令。第三层是 DXMT。这是一个 DirectX 到 Metal 的翻译层专门处理图形渲染。Windows 程序通常调用 Direct3D 来画界面而 iOS 只认 Metal。DXMT 的作用就是把 D3D 调用转成 Metal 调用让 Windows 程序的图形界面能正常显示。它支持 D3D11 的大部分功能D3D12 的支持还在完善中。这三层的关系可以用一个简单的类比来理解Wine 是翻译官负责把 Windows 的“方言”转成 Unix 的“普通话”FEX-Emu 是口译员负责把 x86-64 的“外语”转成 ARM64 的“母语”DXMT 是美术指导负责把 DirectX 的“画风”转成 Metal 的“画风”。三者缺一不可。2.2 为什么选择这套组合而不是其他方案有人可能会问为什么不直接用 QEMU 做全系统模拟原因很简单性能。QEMU 的全系统模拟需要模拟整个硬件环境包括 CPU、内存、外设开销极大。在 iOS 设备上这种方案的性能损耗通常在 10 倍以上跑个记事本都卡。而 Wine FEX-Emu 的方案是用户态翻译只翻译程序本身的指令不需要模拟硬件性能损耗可以控制在 2 到 3 倍左右部分场景下甚至更低。另一个选择是直接用 ARM64 版本的 Wine 跑 ARM64 的 Windows 程序。但问题是市面上绝大多数 Windows 程序都是 x86-64 的ARM64 版本少之又少。所以 FEX-Emu 这一层是绕不开的。至于 DXMT为什么不直接用 Wine 自带的 WineD3D因为 WineD3D 是把 D3D 转成 OpenGL而 iOS 对 OpenGL 的支持已经 deprecated性能和新特性都跟不上。DXMT 直接转 Metal更贴近 iOS 的图形栈效率更高。2.3 这套方案能跑什么、不能跑什么实测下来Madeira 这套方案能跑的程序有几类一是简单的 Win32 工具比如记事本、计算器、文件管理器二是一些老游戏特别是 D3D9 和 D3D11 时代的游戏帧率能到 30 到 60 帧三是一些开发工具比如轻量级的 IDE 和命令行工具。跑不了或者跑得很差的有几类一是需要内核态驱动的程序比如杀毒软件、虚拟机二是重度依赖 D3D12 的新游戏DXMT 对 D3D12 的支持还不完整三是需要特定硬件指令的程序比如 AVX-512 密集型的计算任务FEX-Emu 对 AVX-512 的支持有限。注意iOS 设备的内存限制比较严格后台程序容易被系统杀掉。跑 Windows 程序时建议关闭其他后台应用并且尽量选择内存占用小的程序。3. 核心细节深挖从 Wine 乱码到 iOS 开发者模式的那些坑3.1 Wine 乱码问题的根源与修复热搜词里 “wine 乱码” 和 “wine 栏是乱码” 出现的频率很高说明这是大家普遍遇到的问题。Wine 在 iOS 上乱码通常有三个原因。第一个原因是字体缺失。Wine 默认会去找 Windows 字体比如宋体、微软雅黑但 iOS 上没有这些字体。解决方案是在 Wine 的字体目录里放一套开源字体比如 Noto Sans CJK 或者文泉驿。具体操作是把字体文件放到drive_c/windows/Fonts/目录下然后在注册表里配置字体替换。第二个原因是字符编码设置不对。Wine 默认使用 UTF-8但有些 Windows 程序用的是 GBK 或者 GB2312。需要在 Wine 的配置文件里设置LANG和LC_ALL环境变量或者在注册表的HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Nls\CodePage里指定代码页。第三个原因是 FEX-Emu 的翻译精度问题。有些 x86-64 指令在翻译成 ARM64 时对字符串处理有细微差异导致乱码。这种情况比较少见但确实存在。更新 FEX-Emu 到最新版本通常能解决。我自己的做法是先装字体再设编码最后更新 FEX-Emu。三步下来90% 的乱码问题都能解决。如果还有乱码那就得看具体程序的日志了。3.2 iOS 开发者模式与设备模拟的注意事项热搜词里 “ios开发者模式” 和 “ios设备模拟” 也值得聊一聊。在 iOS 上跑 Wine 和 FEX-Emu通常需要开启开发者模式。这是因为这些工具需要用到一些非公开的 API 或者需要更高的权限。开启开发者模式的方法是在设置里的“隐私与安全性”中找到“开发者模式”打开后重启设备。但要注意开发者模式会降低系统的安全性建议只在测试设备上开启。另外iOS 的版本也很关键。较新的 iOS 版本对 JIT 编译的限制更严格而 FEX-Emu 依赖 JIT 来翻译指令。如果遇到 FEX-Emu 无法启动的问题先检查 iOS 版本和 JIT 权限。关于设备模拟热搜词里有人问 “codex 目前有 ios simulator 的能力吗”。这个问题跟 Madeira 的关系是如果你在开发 iOS 应用想在模拟器里测试 Wine 的兼容性目前是不行的。iOS 模拟器跑的是 x86-64 或者 ARM64 的 macOS 二进制跟 iOS 设备的运行环境差异很大Wine 和 FEX-Emu 在模拟器里跑不起来。必须用真机测试。3.3 DXMT 的图形兼容性调优DXMT 的调优是另一个重点。实测下来DXMT 对 D3D11 的支持最好大部分程序能直接跑。但有几个参数需要手动调。第一个是DXMT_MAX_FRAMES_IN_FLIGHT控制同时处理的帧数。默认值是 3如果遇到画面撕裂或者卡顿可以试着调到 2 或者 4。第二个是DXMT_SHADER_CACHE控制着色器缓存的开关。开启后第一次运行会慢一些但后续运行会快很多。第三个是DXMT_DEBUG开启后会输出详细的日志排查图形问题时很有用。还有一个常见问题是分辨率适配。iOS 设备的屏幕分辨率跟 Windows 程序预期的分辨率不一样DXMT 默认会做缩放但有些程序会显示不全。可以在 Wine 的注册表里设置HKEY_CURRENT_USER\Software\Wine\Explorer\Desktops指定一个虚拟桌面分辨率比如 1280x720这样程序就会在这个分辨率下渲染然后 DXMT 再缩放到屏幕。4. 实操全流程从零搭建 Madeira 运行环境4.1 准备工作设备、系统版本与工具链在开始之前你需要准备以下几样东西。一台 iOS 设备建议是 iPad因为屏幕大跑 Windows 程序体验更好。设备需要是 A12 芯片或更新的因为 FEX-Emu 对 CPU 指令集有要求太老的设备跑不动。系统版本建议是 iOS 15 到 iOS 17 之间太新的版本对 JIT 限制更严太老的版本可能缺少必要的 API。工具链方面你需要一个能编译 iOS 应用的开发环境Xcode 是必须的Wine 的 iOS 移植版源码社区里有几个分支建议选更新最活跃的那个FEX-Emu 的源码需要针对 iOS 做交叉编译DXMT 的源码同样需要交叉编译。另外还需要一个能往 iOS 设备上部署应用的工具比如 Xcode 自带的部署功能或者第三方的签名工具。提示编译这些组件需要一定的耐心特别是 FEX-Emu交叉编译的配置比较复杂。建议先在网上找现成的编译脚本能省不少时间。4.2 编译与部署Wine、FEX-Emu、DXMT 的构建顺序构建顺序很重要因为组件之间有依赖关系。正确的顺序是先编译 FEX-Emu再编译 Wine最后编译 DXMT。编译 FEX-Emu 时需要指定目标架构为 ARM64并且开启 JIT 支持。关键的编译选项包括-DENABLE_JITON、-DCMAKE_TOOLCHAIN_FILEios.toolchain.cmake、-DCMAKE_OSX_ARCHITECTURESarm64。编译完成后会得到一个libFEXCore.a和相关的头文件。编译 Wine 时需要把 FEX-Emu 的头文件和库文件路径加到编译选项里。Wine 的配置选项里要开启--enable-win64和--with-fex。编译完成后会得到wine64可执行文件和一堆 DLL。编译 DXMT 时需要链接 Metal 和 QuartzCore 框架。关键的编译选项包括-DENABLE_METALON、-DCMAKE_FRAMEWORK_PATH指向 iOS SDK 的框架目录。编译完成后会得到dxmt.dll和相关的 Metal 着色器库。部署时把编译好的文件打包成一个 iOS 应用通过 Xcode 部署到设备上。应用启动后会初始化 Wine 环境然后你可以通过命令行或者简单的 GUI 来启动 Windows 程序。4.3 配置与运行第一个 Windows 程序的启动过程部署完成后第一次运行需要做一些配置。首先Wine 需要初始化一个虚拟的 C 盘。在应用的沙盒目录里创建drive_c文件夹然后把必要的 Windows DLL 和字体文件放进去。Wine 的初始化脚本会自动创建注册表文件。然后配置 FEX-Emu 的 JIT 缓存目录。在应用的沙盒里创建一个fex_cache文件夹设置环境变量FEX_CACHE_DIR指向它。这样 FEX-Emu 翻译过的代码块会缓存下来下次运行更快。接着配置 DXMT 的着色器缓存目录。同样在沙盒里创建dxmt_cache文件夹设置DXMT_SHADER_CACHE_DIR。最后启动一个简单的 Windows 程序测试比如notepad.exe。在命令行里运行wine64 notepad.exe如果一切正常你会看到记事本的窗口。如果遇到问题查看日志文件通常会有详细的错误信息。4.4 性能调优让 Windows 程序跑得更流畅性能调优有几个方向。一是调整 FEX-Emu 的 JIT 参数。FEX_JIT_MAX_BLOCKS控制 JIT 缓存的最大块数默认是 4096可以调到 8192 或者 16384减少重复翻译。FEX_JIT_TIMEOUT控制 JIT 编译的超时时间如果程序启动慢可以适当调大。二是调整 DXMT 的渲染参数。DXMT_MAX_FRAMES_IN_FLIGHT调到 2 可以减少延迟调到 4 可以提高吞吐量。DXMT_SHADER_CACHE一定要开启能显著减少卡顿。三是调整 iOS 设备的设置。关闭后台应用刷新关闭不必要的系统动画开启低电量模式反而可能让 CPU 降频所以不建议开。如果设备发热严重可以加个散热背夹。实测下来一个简单的 Win32 程序在 iPad Pro 上能跑到接近原生的速度一个 D3D11 的老游戏能跑到 40 到 60 帧一个 D3D12 的新游戏可能只有 15 到 20 帧。性能瓶颈主要在图形翻译和 JIT 编译上。5. 常见问题与排查技巧实录5.1 启动失败与崩溃的排查思路启动失败是最常见的问题表现是程序闪退或者卡在启动画面。排查思路如下。第一步看日志。Wine 和 FEX-Emu 都会输出日志通常在应用的沙盒目录下的logs文件夹里。日志里会显示程序加载了哪些 DLL执行了哪些指令在哪里崩溃的。第二步检查依赖。很多 Windows 程序依赖特定的 DLL比如msvcp140.dll、vcruntime140.dll。这些 DLL 需要手动放到 Wine 的system32目录下。可以用wine64自带的winedump工具查看程序的依赖列表。第三步检查指令集支持。有些程序用了 FEX-Emu 不支持的指令比如 AVX-512 的某些扩展。这种情况只能等 FEX-Emu 更新或者找替代程序。第四步检查内存。iOS 设备的内存有限如果程序需要的内存超过设备可用内存会被系统杀掉。可以在 Xcode 的 Devices 窗口里查看设备的内存使用情况。5.2 图形显示异常的快速定位图形显示异常包括黑屏、花屏、画面撕裂、文字模糊等。排查思路如下。黑屏通常是 DXMT 没有正确初始化。检查dxmt.dll是否在 Wine 的system32目录下检查 Metal 框架是否链接正确。如果日志里显示Failed to create Metal device说明 Metal 初始化失败可能是设备不支持或者权限问题。花屏通常是着色器编译错误。开启DXMT_DEBUG后日志里会显示哪个着色器编译失败了。这种情况可以试着更新 DXMT 到最新版本或者手动修改着色器代码。画面撕裂通常是帧率不匹配。调整DXMT_MAX_FRAMES_IN_FLIGHT参数或者开启垂直同步。文字模糊通常是分辨率缩放问题。在 Wine 的注册表里设置虚拟桌面分辨率让程序在原生分辨率下渲染。5.3 输入法与网络连接的配置要点输入法问题在中文用户里很常见。Wine 默认不支持 iOS 的原生输入法需要用一个桥接方案。社区里有人做了一个简单的输入法桥通过剪贴板来传递文字。具体操作是在 iOS 上复制文字然后在 Wine 程序里粘贴。虽然麻烦但能用。网络连接方面Wine 会使用 iOS 的网络栈大部分情况下能直接联网。但有些程序会检查网络适配器的类型如果发现不是 Windows 的网络适配器会拒绝联网。这种情况可以在 Wine 的注册表里模拟一个 Windows 网络适配器。注意iOS 对后台网络活动有限制如果 Wine 程序需要在后台保持网络连接可能会被系统断开。建议在使用网络功能时保持应用在前台。5.4 常见问题速查表问题现象可能原因排查方法解决方案程序闪退缺少 DLL查看日志中的依赖加载记录补充缺失的 DLL 到 system32启动卡死JIT 编译超时查看 FEX-Emu 日志调大 FEX_JIT_TIMEOUT黑屏DXMT 初始化失败查看 DXMT 日志检查 Metal 链接和权限花屏着色器编译错误开启 DXMT_DEBUG更新 DXMT 或修改着色器文字乱码字体缺失或编码错误检查字体目录和注册表安装字体并设置代码页画面撕裂帧率不匹配观察帧率变化调整 MAX_FRAMES_IN_FLIGHT无法联网网络适配器检查查看程序日志注册表模拟 Windows 适配器输入法无法使用缺少桥接测试剪贴板粘贴使用剪贴板桥接方案6. 个人实操心得与后续扩展方向6.1 几个让我少走弯路的经验第一个经验是不要追求一次跑通所有程序。我一开始想一口气把常用的 Windows 工具都跑起来结果每个都出问题排查起来很乱。后来改成一次只跑一个程序跑通了再跑下一个效率反而高很多。第二个经验是日志是你的好朋友。Wine、FEX-Emu、DXMT 都有详细的日志输出遇到问题先看日志比瞎猜快得多。我习惯在启动程序时加上WINEDEBUGall和FEX_LOG_LEVELdebug虽然日志量大但信息全。第三个经验是社区版本比官方版本更实用。Wine 官方对 iOS 的支持很有限社区里有几个分支专门针对 iOS 做了优化比如增加了对 Metal 的支持、修复了 JIT 权限问题。用这些分支能省很多事。第四个经验是性能调优要有取舍。想要帧率高就得牺牲画质想要画质好就得接受帧率低。在 iOS 设备上我通常优先保证流畅度把画质调到中等这样体验最好。6.2 这套方案还能怎么扩展Madeira 这套方案目前主要跑 Windows 程序但它的架构其实可以扩展到其他场景。一个方向是跑 Linux 程序。Wine 本身就是从 Linux 上发展起来的FEX-Emu 也支持 Linux 的 ELF 格式。如果把 Wine 换成 Linux 的运行时理论上可以在 iOS 上跑 Linux 的 x86-64 程序。社区里已经有人在尝试这个方向。另一个方向是跑老游戏机模拟器。很多老游戏机的模拟器是 x86-64 的 Windows 程序比如 PCSX2、Dolphin。用 Madeira 的方案可以在 iOS 上跑这些模拟器然后再跑游戏。虽然多了一层翻译但性能还能接受。还有一个方向是开发工具链。有些 Windows 上的开发工具比如 Keil、IAR只有 Windows 版本。用 Madeira 的方案可以在 iPad 上跑这些工具配合外接键盘和鼠标能当个轻量级的开发终端用。6.3 最后分享一个小技巧如果你在 iOS 上跑 Wine 程序时遇到随机崩溃可以试试关闭 iOS 的“后台应用刷新”和“自动更新”。这两个功能会在后台占用 CPU 和内存导致 Wine 的 JIT 编译被中断。关闭后稳定性会明显提升。另外把设备调成“飞行模式”再打开 Wi-Fi能减少一些系统服务的干扰对性能也有帮助。这个项目我还在持续折腾后面如果遇到新的坑或者发现新的调优参数我会继续更新。如果你也在玩这套方案欢迎交流你的经验。