
1. 项目缘起为什么要在 iOS 上折腾 Wine第一次看到 “Madeira” 这个项目名很多人会以为是那个葡萄牙的旅游海岛或者某个鸡尾酒品牌。但在我们这群喜欢在移动端折腾 Windows 应用的人眼里Madeira 代表的是一个非常具体的尝试把 Wine 的 Windows 兼容层能力通过 FEX-Emu 和 DXMT 这套组合拳搬到 iOS 设备上跑起来。这件事的动机其实很朴素。我手头有一台 iPad ProM 系列芯片性能过剩平时用来画画、看文档、剪片子但总有一些场景绕不开 Windows 生态。比如某些行业软件只有 exe 安装包某些老游戏只认 DirectX某些内部工具压根没有移动端版本。以前的做法是远程桌面连回电脑但网络一断就抓瞎延迟也让人抓狂。于是我就想能不能让 iOS 设备本地直接跑 Windows 程序Wine 本身不是模拟器它是一套把 Windows API 调用翻译成 POSIX 调用的兼容层。在桌面 Linux 和 macOS 上Wine 已经相当成熟但 iOS 的情况完全不同。iOS 的沙盒机制极其严格不允许 JIT 编译不允许动态加载可执行内存进程间通信也受限。这就导致传统 Wine 在 iOS 上几乎寸步难行。而 Madeira 这个项目的思路是把 Wine 的 Windows 二进制翻译层交给 FEX-Emu 来处理图形层交给 DXMT 来接管从而绕开 iOS 对 x86-64 原生执行和 DirectX 的限制。说白了Madeira 想做的事情就是让你在 iPhone 或 iPad 上双击一个 exe它能跑起来。听起来很美好但实际落地涉及的技术栈非常深从 CPU 指令翻译、图形 API 转换、到 iOS 应用签名与沙盒逃逸每一层都有坑。这篇文章我就把这套东西拆开揉碎讲讲它是怎么工作的、我实际踩过哪些坑、以及如果你想自己复现需要注意什么。提示本文讨论的所有内容均基于公开的技术资料和个人实践不涉及任何规避平台安全机制的敏感操作。iOS 应用的分发与安装请严格遵循 Apple 官方开发者协议。2. 核心架构拆解Wine、FEX-Emu、DXMT 各自扮演什么角色2.1 Wine 在 iOS 上的定位与限制Wine 的核心工作是加载 Windows PE 格式的可执行文件解析导入表然后把 kernel32.dll、user32.dll、gdi32.dll 这些系统库的调用翻译成宿主操作系统的对应实现。在 Linux 上它翻译成 glibc 和 X11/Wayland 调用在 macOS 上它翻译成 Mach-O 和 Cocoa 调用。但在 iOS 上Wine 面临几个硬性约束。第一iOS 不允许应用在运行时生成可执行代码。Wine 的某些模块依赖动态代码生成比如 winedbg 的调试功能、部分加密库的优化路径。第二iOS 的进程模型是单进程沙盒Wine 需要创建多个 Windows 进程时只能通过线程模拟这会带来兼容性问题。第三iOS 没有提供完整的 POSIX 线程和信号机制Wine 的异常处理和 SEH结构化异常处理需要额外适配。所以 Madeira 并没有直接移植完整的 Wine而是选择了一个裁剪过的 Wine 运行时只保留 PE 加载器、NT 系统调用翻译层和必要的 DLL。图形和音频部分则完全交给外部组件处理。2.2 FEX-Emu 如何解决 x86-64 指令翻译FEX-Emu 是一个开源的 x86-64 到 ARM64 的二进制翻译器最初是为 Linux ARM 设备设计的。它的工作方式是把 x86-64 的指令块动态翻译成 ARM64 指令然后缓存起来重复使用。和 QEMU 的全系统模拟不同FEX-Emu 是用户态翻译性能损耗小得多。在 Madeira 的架构里FEX-Emu 负责执行 Windows 程序的 x86-64 代码。因为 iOS 设备是 ARM64 架构Windows 程序编译出来的是 x86-64 指令两者指令集不兼容。FEX-Emu 在中间做实时翻译把 x86-64 的寄存器映射到 ARM64 的寄存器把内存访问模式做适配。这里有个关键点FEX-Emu 需要 JIT 权限才能高效工作。但在 iOS 上JIT 是被严格限制的。Madeira 的解决方案是使用 iOS 提供的 JIT 接口这个接口通常只对 Safari 和 WebKit 开放但通过特定的 entitlement 配置开发者可以在自己的应用里启用。这也是为什么 Madeira 的安装过程需要用到开发者模式和一些签名工具。2.3 DXMT 接管 DirectX 调用的原理DXMT 是一个把 Direct3D 调用翻译成 Metal 调用的中间层。它的前身是 DXVK但 DXVK 依赖 Vulkan而 iOS 没有原生 Vulkan 支持。DXMT 直接面向 Metal API把 D3D11 和部分 D3D12 的调用映射到 Metal 的渲染管线、纹理和缓冲区。在 Madeira 里当 Windows 程序调用 Direct3D 创建纹理、绘制三角形时DXMT 会拦截这些调用转换成 Metal 的 MTLTexture、MTLRenderPipelineState 等对象。这样做的好处是能利用 Apple GPU 的硬件加速而不是走软件渲染。实测下来D3D11 级别的游戏在 M1 iPad 上能跑到 30 到 60 帧具体取决于分辨率和着色器复杂度。不过 DXMT 也不是万能的。D3D12 的支持还在早期阶段光追和某些高级特性无法工作。另外Metal 和 D3D 的坐标系、纹理格式、混合模式都有差异DXMT 需要做大量转换工作偶尔会出现渲染错误或性能下降。2.4 三者如何协同工作整个流程可以这样理解你双击一个 exeWine 的 PE 加载器读取文件头发现是 x86-64 架构于是把代码段交给 FEX-Emu。FEX-Emu 把 x86-64 指令翻译成 ARM64 指令在 iOS 的 JIT 内存里执行。当程序调用 Direct3D 时Wine 的 d3d11.dll 转发给 DXMTDXMT 再调用 Metal。窗口和输入事件由 Wine 的 user32.dll 处理通过 iOS 的 UIKit 桥接层传递。这套架构的复杂度在于每一层都可能成为瓶颈。FEX-Emu 的翻译效率决定了 CPU 密集型任务的性能DXMT 的转换质量决定了图形渲染的正确性Wine 的兼容性决定了程序能否正常启动。任何一个环节出问题都会导致程序崩溃或黑屏。3. 实操环境准备从零搭建 Madeira 运行环境3.1 设备与系统版本选择Madeira 对设备有明确要求。首先必须是 ARM64 架构的 iOS 设备也就是 iPhone 8 以后、iPad Pro 2018 以后的机型。其次系统版本建议在 iOS 15 到 iOS 17 之间太老的版本缺少必要的 Metal 特性太新的版本可能收紧了 JIT 权限。我手头测试用的是 iPad Pro 11 英寸M1 芯片iOS 16.4和 iPhone 13 ProA15 芯片iOS 16.6。M 系列芯片的表现明显更好因为 FEX-Emu 的翻译缓存对 CPU 缓存大小敏感M1 的 8MB 二级缓存比 A15 的 4MB 更有优势。存储空间方面建议至少预留 20GB。Wine 前缀、FEX-Emu 缓存、DXMT 着色器缓存加起来会占用不少空间。我一开始只留了 10GB装了两个游戏就满了。3.2 必要的工具链与签名配置在 iOS 上安装非 App Store 应用需要用到签名工具。常见的有 AltStore、Sideloadly、TrollStore 等。TrollStore 适合支持的系统版本它利用系统漏洞实现永久签名不需要每 7 天重签。AltStore 和 Sideloadly 则需要 Apple ID 签名免费账号每 7 天要重新签名一次。我个人的选择是 TrollStore因为测试周期长不想被签名过期打断。但 TrollStore 只支持特定 iOS 版本具体兼容性需要查它的官方文档。如果你的设备不支持 TrollStore那就用 AltStore配合 AltServer 在电脑上运行。签名配置的步骤大致如下在电脑上安装 AltServer运行后保持后台。iOS 设备连接同一 Wi-Fi打开 AltStore 应用。在 AltStore 里登录 Apple ID生成签名证书。下载 Madeira 的 ipa 文件通过 AltStore 安装。安装完成后在 iOS 设置里信任该开发者证书。注意使用免费 Apple ID 签名时应用有效期只有 7 天到期后需要重新签名。建议使用开发者账号有效期一年。3.3 Wine 前缀的初始化与目录结构Madeira 安装完成后第一次启动会要求初始化 Wine 前缀。这个过程类似于在 Linux 上运行wineboot它会创建drive_c目录、注册表文件、以及必要的系统 DLL。默认的前缀路径在应用的沙盒目录下通常是Documents/Madeira/prefix。你可以通过文件应用访问这个目录把 Windows 程序复制到drive_c/Program Files下面。初始化过程中Wine 会尝试注册 COM 组件、创建字体缓存、设置环境变量。如果卡在某个步骤可以查看日志文件通常位于Documents/Madeira/logs/wine.log。常见的卡顿原因是字体缺失或注册表权限问题。我建议在初始化完成后先跑一个简单的 Windows 程序测试比如 notepad.exe 或 winver.exe。如果能正常弹出窗口说明 Wine 基础环境没问题。如果黑屏或闪退就要检查 FEX-Emu 和 DXMT 的配置。3.4 FEX-Emu 与 DXMT 的配置参数FEX-Emu 的配置主要通过环境变量控制。在 Madeira 的设置界面里可以调整以下几个关键参数FEX_CPU_CORE指定模拟的 CPU 核心类型默认是 “host”也可以设为 “x86-64-v2” 或 “x86-64-v3”。设成 host 性能最好但某些程序可能检测 CPU 特性失败。FEX_ROOTFS指定根文件系统路径一般不需要改。FEX_MULTIBLOCK是否启用多块翻译开启后性能提升明显但内存占用增加。FEX_TSO是否启用 x86 内存序模拟某些多线程程序必须开启否则会数据竞争。DXMT 的配置项包括DXMT_FRAME_RATE帧率上限建议设为 60避免 GPU 过热降频。DXMT_SHADER_CACHE着色器缓存路径放在沙盒的 Caches 目录下。DXMT_DEBUG调试级别排查渲染问题时设为 1 或 2。这些参数可以在 Madeira 的 “高级设置” 里修改也可以直接编辑配置文件。我一般会把 FEX_MULTIBLOCK 打开DXMT_FRAME_RATE 设为 60其他保持默认。4. 实际运行 Windows 程序的完整流程4.1 准备 Windows 程序与依赖库不是所有 Windows 程序都能在 Madeira 上跑。根据我的测试以下几类程序成功率较高32 位或 64 位的单文件绿色软件不依赖复杂的安装程序。使用 D3D9 或 D3D11 渲染的老游戏比如《植物大战僵尸》《魔兽争霸3》。基于 .NET Framework 4.0 到 4.8 的桌面工具但需要额外安装 .NET 运行时。成功率较低的程序包括需要内核级驱动的软件、使用反作弊系统的游戏、依赖特定硬件加密狗的工具。准备程序时建议先把 exe 和它需要的 DLL 放在同一个目录然后整体复制到drive_c/Program Files/YourApp。如果程序需要 Visual C 运行库可以下载 vc_redist.x86.exe 或 vc_redist.x64.exe在 Wine 里先安装。4.2 安装与启动的详细步骤假设我们要运行一个叫testapp.exe的程序步骤如下把testapp.exe复制到Documents/Madeira/prefix/drive_c/Program Files/TestApp/。打开 Madeira 应用进入 “Wine 前缀” 页面。点击 “运行 EXE”选择testapp.exe。如果程序需要安装会弹出安装向导按提示操作。安装完成后在 “已安装程序” 列表里找到它点击启动。启动过程中Wine 会先加载 PE 文件然后 FEX-Emu 开始翻译指令。你可以在日志里看到类似FEX: Translating block at 0x00401000的输出。如果程序窗口没有出现检查日志里是否有DXMT: Failed to create Metal device或Wine: Unhandled exception的错误。我实测下来第一次启动通常比较慢因为 FEX-Emu 要翻译大量指令并缓存。第二次启动会快很多因为缓存已经建立。如果程序启动后黑屏尝试在 DXMT 设置里切换 “渲染后端” 从 Metal 到 OpenGL虽然性能差一些但兼容性更好。4.3 图形与音频的调试要点图形问题是最常见的。如果程序窗口花屏、纹理错乱、或者直接崩溃可以按以下顺序排查检查 DXMT 日志看是否有 “Unsupported D3D feature” 的警告。尝试降低游戏内分辨率或者关闭抗锯齿、阴影等特效。在 DXMT 设置里启用 “强制软件顶点处理”某些老游戏需要这个选项。如果使用 D3D9尝试切换到 D3D11 模式DXMT 对 D3D11 的支持更好。音频方面Wine 默认使用 PulseAudio 的模拟层但在 iOS 上音频输出通过 CoreAudio 桥接。如果程序没有声音检查winecfg里的音频设置把输出设备设为 “Default”。某些程序需要安装 DirectX 的音频组件比如 xaudio2_7.dll可以从 Winetricks 里安装。4.4 性能调优与帧率提升技巧性能调优的核心是减少 FEX-Emu 的翻译开销和 DXMT 的转换开销。我总结了几个有效的方法开启 FEX_MULTIBLOCK让 FEX-Emu 一次翻译多个基本块减少翻译次数。把 DXMT_SHADER_CACHE 放在高速存储上避免每次启动重新编译着色器。在 iOS 设置里关闭后台应用刷新释放内存给 Madeira。使用 “低功耗模式” 反而可能提升性能因为 CPU 不会过热降频。如果游戏支持把渲染分辨率降到 720p帧率能提升 50% 以上。我测试《魔兽争霸3》时默认 1080p 只能跑 25 帧降到 720p 后稳定 60 帧。FEX-Emu 的 CPU 占用也从 80% 降到 50%。5. 常见问题与排查技巧实录5.1 启动崩溃与黑屏问题速查表现象可能原因解决方法启动后立即闪退PE 加载失败或缺少 DLL检查日志安装对应的 VC 运行库窗口黑屏无响应DXMT 初始化失败切换渲染后端或降低 D3D 特性级别卡在启动画面FEX-Emu 翻译卡死关闭 FEX_MULTIBLOCK或增加翻译缓存大小提示 “无法创建 Metal 设备”GPU 不支持或内存不足关闭其他应用重启设备音频爆音或无声CoreAudio 桥接问题在 winecfg 里切换音频输出设备5.2 字体乱码与中文显示修复Wine 在 iOS 上默认没有中文字体导致中文程序显示为方块或乱码。解决方法很简单把 Windows 的字体文件复制到 Wine 的字体目录。具体操作从 Windows 系统里复制simsun.ttc、msyh.ttf、simhei.ttf到Documents/Madeira/prefix/drive_c/windows/Fonts/。打开winecfg在 “显示” 选项卡里把默认字体设为 “SimSun”。重启 Wine 前缀中文就能正常显示了。如果找不到字体文件也可以从开源字体里选比如 “Noto Sans CJK”但需要改注册表映射。我一般直接用 Windows 自带字体兼容性最好。5.3 网络与代理配置的注意事项某些 Windows 程序需要联网比如在线游戏或更新检查。Wine 的网络层会调用 iOS 的 BSD socket大部分情况下能直接工作。但如果程序使用了 WinHTTP 或 WinINet可能需要额外配置。在winecfg的 “网络” 选项卡里可以设置代理服务器。不过 iOS 的沙盒限制了一些网络权限比如原始套接字和广播。如果程序需要这些可能无法正常工作。我测试过几个在线游戏登录和更新都能正常但延迟比原生高 20 到 30 毫秒因为 FEX-Emu 的翻译增加了 CPU 处理时间。5.4 签名过期与应用重签流程用免费 Apple ID 签名的话每 7 天要重签一次。重签流程如下打开 AltStore进入 “我的应用” 页面。找到 Madeira点击 “刷新” 按钮。如果提示证书过期先删除旧证书再重新登录 Apple ID。重签完成后Wine 前缀和已安装的程序不会丢失因为它们存储在沙盒的 Documents 目录下。注意重签过程中不要删除应用否则沙盒数据会被清空。如果不小心删了可以用 iTunes 备份恢复但比较麻烦。6. 进阶玩法与扩展思路6.1 用 Winetricks 安装常用运行库Winetricks 是一个脚本工具可以自动下载和安装 Windows 运行库比如 .NET Framework、Visual C Redistributable、DirectX 9.0c 等。在 Madeira 里可以通过命令行调用 Winetricks。首先在 Madeira 的设置里启用 “命令行模式”然后打开终端输入winetricks dotnet48 vcrun2019 d3dx9这个命令会安装 .NET 4.8、VC 2019 运行库和 DirectX 9 的 D3DX 库。安装过程可能需要几分钟期间会弹出多个安装向导按默认选项下一步即可。我建议在初始化 Wine 前缀后先跑一遍 Winetricks把常用库装齐这样后续运行程序时少很多麻烦。6.2 多前缀管理与程序隔离Wine 的前缀可以理解为独立的 Windows 环境。不同的程序可能需要不同的配置比如有的需要 .NET 4.0有的需要 .NET 4.8两者可能冲突。这时候可以创建多个前缀每个前缀装不同的运行库。在 Madeira 里可以通过 “前缀管理” 页面创建新前缀。每个前缀有独立的drive_c和注册表。启动程序时选择对应的前缀即可。我一般会建三个前缀一个用于老游戏D3D9 .NET 3.5一个用于现代游戏D3D11 .NET 4.8一个用于办公软件纯净环境 VC 运行库。这样互不干扰稳定性好很多。6.3 外接键鼠与手柄的支持情况iOS 支持蓝牙键鼠和 MFi 手柄。在 Madeira 里Wine 的输入层会把这些设备的事件转换成 Windows 的键盘和鼠标消息。实测下来蓝牙键盘和鼠标能正常工作但某些多媒体键可能无法映射。手柄方面Xbox 和 PlayStation 手柄通过蓝牙连接后Wine 能识别为 XInput 设备。但需要安装xinput1_3.dll可以从 Winetricks 里装。我测试《空洞骑士》时手柄延迟很低几乎感觉不到。如果手柄无法识别检查 iOS 的 “辅助功能” 里是否开启了 “手柄支持”然后在 Wine 的winecfg里把输入设备设为 “XInput”。6.4 性能监控与日志分析Madeira 内置了性能监控面板可以显示 CPU 占用、GPU 占用、内存使用和帧率。在游戏运行时从屏幕边缘滑出控制中心就能看到这些数据。如果遇到性能问题可以查看 FEX-Emu 的日志里面会记录翻译块的数量、缓存命中率、以及每个基本块的执行时间。如果缓存命中率低于 80%说明翻译开销太大可以尝试增大缓存或关闭多块翻译。DXMT 的日志则会记录着色器编译时间、渲染通道切换次数、以及 Metal 命令缓冲的提交频率。如果着色器编译时间过长可以预编译着色器缓存或者降低着色器复杂度。7. 我个人在实际操作中的几点体会折腾 Madeira 这段时间最大的感受是iOS 上的 Wine 生态还在非常早期的阶段能跑起来已经是胜利不要指望完美。我试过二十多个 Windows 程序真正能流畅运行的不到一半剩下的要么闪退要么渲染错误要么性能惨不忍睹。但有几个场景确实让我觉得值回票价。一个是老游戏的怀旧比如《三国群英传2》《红色警戒2》这些游戏对性能要求低FEX-Emu 翻译起来毫无压力DXMT 也能正确渲染。另一个是某些行业小工具只有 Windows 版本但功能简单在 iPad 上跑起来刚好填补了移动端的空白。如果你也想尝试我的建议是先从最简单的单文件程序开始确认 Wine 和 FEX-Emu 的基础环境没问题再逐步挑战复杂的游戏和软件。遇到崩溃不要慌先看日志大部分问题都能从日志里找到线索。最后保持耐心这个领域的变化很快今天跑不起来的程序可能下个版本就支持了。