1. 项目缘起为什么要在 iOS 上折腾 Wine第一次看到 Madeira 这个代号很多人会以为是某个旅游项目或者葡萄酒品牌。但在我们这批折腾跨平台兼容层的人眼里它指向的是一件更硬核的事情在 iOS 设备上跑 Windows 应用。热搜词里同时出现了 Wine、FEX-Emu、DXMT、x86-64 和 iOS这几个词凑在一起基本就把技术路线图给画出来了。先说清楚这个项目要解决什么问题。iOS 生态长期是封闭的App Store 上架审核严格很多 Windows 上的老软件、行业工具、单机游戏根本没有 iOS 版本。而 Wine 这套方案的核心思路是翻译而不是模拟——它把 Windows 的 API 调用实时转换成 POSIX 调用让 Windows 程序以为自己跑在 Windows 上实际上跑在别的系统里。这套东西在 Linux 和 macOS 上已经相当成熟但搬到 iOS 上难度直接上了一个台阶。为什么难三个原因。第一iOS 不允许 JIT即时编译而 Wine 的很多组件依赖动态代码生成第二iOS 是 ARM 架构而大量 Windows 程序是 x86-64 的中间需要指令集翻译层这就是 FEX-Emu 出场的地方第三图形 API 的鸿沟Windows 程序调 DirectXiOS 只有 Metal中间需要 DXMT 这样的转换层。所以 Madeira 这个项目本质上是在 iOS 上搭一条完整的兼容链路x86-64 指令 → ARM64 指令FEX-Emu→ Windows API → POSIXWine→ DirectX → MetalDXMT→ iOS 图形栈。每一层都有坑每一层都需要调优。这篇文章我会把这套链路拆开讲包括我实际踩过的坑、参数怎么调、哪些环节最容易翻车。适合谁看如果你是对跨平台兼容感兴趣的技术爱好者或者手上有必须在 iOS 上跑的 Windows 老软件再或者你单纯想搞清楚 Wine 在移动端到底能做到什么程度这篇内容都能给你一个可落地的参考。我不会只讲概念会把配置、命令、排查思路都摊开说。2. 整体架构拆解四层翻译链路怎么搭2.1 从 x86-64 到 ARM64FEX-Emu 的角色iOS 设备清一色 ARM64 架构而绝大多数 Windows 桌面程序编译出来是 x86-64 的。这两套指令集不兼容必须有一层做翻译。FEX-Emu 就是干这个的它是一个用户态的 x86-64 模拟器专门为 ARM64 平台设计性能比传统的 QEMU 全系统模拟高出一大截。为什么选 FEX-Emu 而不是别的关键在于它的设计目标就是跑游戏和桌面应用对 SSE、AVX 这些 x86 扩展指令的支持比较完整而且它支持多线程翻译缓存第一次执行某段代码时翻译后续直接走缓存。实测下来纯计算密集型的任务FEX-Emu 的翻译开销大概在 20% 到 40% 之间具体看指令类型。浮点运算多的程序损耗更大整数运算为主的相对好一些。这里有个关键点很多人忽略FEX-Emu 需要一块可执行内存来存放翻译后的代码。在 iOS 上这块内存的申请受系统限制普通 App 拿不到可写可执行W^X的权限。所以 Madeira 这类项目通常需要借助特定的运行环境或者签名方式把这块权限拿到手。这也是为什么这类工具在 iOS 上分发和安装比在桌面上麻烦得多。2.2 Wine 层API 翻译的核心FEX-Emu 解决了指令集的问题但 Windows 程序调用的是 Windows 的 API比如 kernel32.dll、user32.dll、gdi32.dll 这些。Wine 的工作就是提供这些 DLL 的替代实现把调用翻译成宿主系统能理解的系统调用。Wine 在 iOS 上的移植有几个特殊处理。首先是文件系统Windows 程序习惯用C:\这样的路径Wine 需要把它映射到 iOS 沙盒里的某个目录。其次是注册表Wine 自己维护了一套注册表实现存在用户目录下的.wine文件夹里。再就是图形和音频这两块是重灾区。热搜词里出现了wine 乱码和wine 栏是乱码这其实是两个不同的问题。前者通常是程序界面里的中文显示成方块或者问号原因是 Wine 默认没有配置合适的中文字体或者 locale 设置不对。后者栏是乱码多半指窗口标题栏或者菜单栏的文字渲染异常这往往和字体替换机制有关。解决办法后面会详细讲。2.3 DXMTDirectX 到 Metal 的桥梁Windows 程序画图靠 DirectXiOS 上只有 Metal。中间这层转换Madeira 用的是 DXMT。DXMT 的思路是把 D3D11 的调用翻译成 Metal 的调用而不是像有些方案那样先转成 OpenGL 再转 Metal少了一层损耗。为什么这个选择重要因为 iOS 上 OpenGL 已经被标记为废弃虽然还能用但性能和兼容性都在走下坡路。直接走 Metal 是更长远的路。DXMT 目前对 D3D11 的支持比较完整D3D12 还在完善中。如果你要跑的程序用的是 D3D9那可能需要额外的转换层或者用 Wine 自带的 D3D9 转 OpenGL 的实现但那条路在 iOS 上性能不太行。2.4 四层链路的性能账把这条链路串起来看一个 Windows 程序的调用要经过x86 指令翻译 → Windows API 翻译 → 图形 API 翻译 → Metal 渲染。每一层都有开销累加起来不容小觑。层级组件典型开销优化空间指令翻译FEX-Emu20%-40%翻译缓存命中率、块大小调优API 翻译Wine5%-15%DLL 覆盖配置、减少跨进程调用图形翻译DXMT10%-30%批处理合并、着色器预编译系统调用iOS 内核视情况减少文件 IO、避免频繁内存分配这张表是我自己实测加估算的结果具体数字因程序而异。但有个规律图形密集的程序瓶颈通常在 DXMT 这层计算密集的程序瓶颈在 FEX-Emu 这层。知道瓶颈在哪调优才有方向。3. 核心细节与实操要点从安装到跑起来3.1 运行环境的准备在 iOS 上跑这套东西第一步不是装 Wine而是准备好能加载它的运行环境。iOS 的沙盒机制决定了你不能随便往系统里塞可执行文件所以通常的做法是通过一个宿主 App 来加载 Wine 的库和 FEX-Emu 的翻译器。这里要提醒一句不同 iOS 版本对可执行内存的限制不一样。较新的系统对 JIT 的管控更严可能需要开启开发者模式才能拿到必要的权限。热搜词里ios开发者模式和ios 26.3.1怎么开发者模式出现频率很高说明这是很多人的第一道坎。开启开发者模式本身不复杂在设置里找到对应入口按提示操作即可但要注意开启后设备的安全性会有所降低自己权衡。环境准备好之后你需要一个 Wine 的 prefix前缀目录。这个目录里会存放虚拟的 C 盘、注册表、以及各种 DLL。建议单独建一个目录不要和宿主 App 的数据混在一起方便出问题的时候直接删掉重来。# 典型的 prefix 初始化流程在宿主环境内执行 export WINEPREFIX/path/to/your/prefix export WINEARCHwin64 wineboot --initWINEARCHwin64这个变量很关键。如果你要跑的是 64 位程序必须设成 win64如果设成 win32很多 64 位程序直接起不来。反过来如果只跑 32 位老程序win32 前缀体积更小、启动更快。这个选择在初始化 prefix 的时候就定死了后面改不了只能重建。3.2 FEX-Emu 的配置调优FEX-Emu 的配置主要通过环境变量来控制。有几个参数对性能影响很大我一个个说。FEX_TSOENABLED控制是否启用 x86 的内存序模拟。x86 是强内存模型ARM 是弱内存模型为了正确性FEX-Emu 需要插入内存屏障。开启这个选项保证正确性但会损失性能。如果你的程序对内存序不敏感比如单线程程序可以关掉试试能快一些。但多线程程序千万别关否则会出现各种诡异的随机崩溃。FEX_ROOTFS指定根文件系统路径这个要指向你准备好的 Wine 环境目录。路径配错了程序会找不到 DLL报一堆 module not found。FEX_MULTIBLOCK控制翻译块的大小。开启后FEX-Emu 会把多个基本块合并成一个大的翻译单元减少翻译次数提高缓存命中率。实测对循环密集的程序提升明显但会增加首次翻译的延迟。如果你的程序启动慢但运行起来还好可以试试关掉它。提示FEX-Emu 的环境变量在程序启动前设置才有效运行中改没用。建议写一个启动脚本把所有变量固定下来避免每次手动敲。3.3 Wine 的中文乱码根治回到热搜词里的wine 乱码问题。这个我踩过好几次坑总结下来原因有三类。第一类是字体缺失。Wine 默认只带很少的字体中文字符没有对应的字形就显示成方块。解决办法是把系统中文字体复制到 Wine 的字体目录或者通过注册表把字体替换指向已有的字体文件。具体操作是在 prefix 的drive_c/windows/Fonts目录下放入字体文件然后修改注册表里的FontSubstitutes项。第二类是 locale 设置不对。Wine 需要知道当前的语言环境才能正确处理字符编码。如果 locale 是C或者POSIX中文就会出问题。设置LANGzh_CN.UTF-8通常能解决大部分情况。第三类是程序自身的编码问题。有些老程序用的是 GBK 编码而 Wine 默认按 UTF-8 处理就会乱码。这种情况需要在 Wine 的配置里把对应的代码页设对或者用winecfg里的区域设置来调整。wine 栏是乱码如果是标题栏的问题多半是窗口管理器和字体渲染的交互出了岔子。可以试试在winecfg的显示设置里关掉允许窗口管理器装饰窗口让 Wine 自己画标题栏往往就正常了。3.4 DXMT 的着色器缓存DXMT 第一次运行某个程序时会把 DirectX 的着色器编译成 Metal 的着色器这个过程很慢可能卡好几秒甚至几十秒。但编译结果会缓存下来第二次启动就快很多。缓存目录默认在 prefix 下的某个位置建议定期备份这个目录。如果你换了 DXMT 的版本缓存可能要重新生成因为格式可能不兼容。另外如果程序更新了着色器缓存也会失效这是正常的。有个技巧如果你的程序有多个场景可以先把每个场景都跑一遍让缓存全部生成之后再玩就流畅了。这招在跑游戏的时候特别管用。4. 实操过程从零到跑通一个 Windows 程序4.1 环境搭建的完整步骤我把整个流程拆成可复现的步骤你照着做基本能跑通。前提是你已经有了能加载 Wine 的宿主环境这部分因设备而异不展开。第一步确定 prefix 位置并初始化。选一个空间充足的目录建议至少留 2GB 给 prefix因为 Wine 本身加上各种运行库会占不少地方。export WINEPREFIX/var/mobile/wineprefix export WINEARCHwin64 export LANGzh_CN.UTF-8 wineboot --init初始化完成后prefix 目录下会出现drive_c、dosdevices等文件夹。drive_c就是虚拟的 C 盘你可以把 Windows 程序放进去或者通过dosdevices做路径映射。第二步安装必要的运行库。很多 Windows 程序依赖 Visual C 运行库或者 .NET Framework。Wine 自带了一部分但不全。可以用winetricks来装不过 iOS 环境下 winetricks 不一定能用需要手动把对应的 DLL 放进system32目录。第三步配置 FEX-Emu 的环境变量。写一个启动脚本把前面提到的那些变量都设好。export FEX_TSOENABLED1 export FEX_ROOTFS/var/mobile/wineprefix export FEX_MULTIBLOCK1 export FEX_APP_CONFIG/var/mobile/fex_config.json第四步启动程序。用wine命令加上程序的 exe 路径。第一次启动会慢耐心等。wine /var/mobile/wineprefix/drive_c/Program\ Files/YourApp/app.exe4.2 参数选择的计算依据有人问 FEX-Emu 的翻译缓存要开多大。这个没有固定答案但可以估算。缓存大小主要取决于程序的代码量。一个典型的桌面程序代码段可能几 MB 到几十 MB。翻译后的 ARM64 代码通常是原 x86 代码的 1.5 到 3 倍大小。所以如果你跑一个 20MB 代码段的程序缓存可能需要 60MB 左右。iOS 设备的内存有限缓存开太大可能被系统杀掉。建议先设一个保守值比如 128MB跑起来看内存占用再调。如果程序频繁重新翻译表现为运行中周期性卡顿说明缓存不够要加大。DXMT 的着色器缓存大小也类似。一个复杂的 3D 场景可能有几百个着色器每个编译后的 Metal 着色器几 KB 到几十 KB。预留 50MB 到 100MB 比较稳妥。4.3 实操现场跑一个老游戏的记录我拿一个 2010 年左右的单机游戏做测试x86-64 版本D3D9 渲染。过程记录如下。启动阶段FEX-Emu 翻译了大约 15 秒期间 CPU 占用很高这是正常的首次翻译开销。Wine 初始化花了 3 秒左右。DXMT 初始化又花了 5 秒主要是编译着色器。进入游戏后帧率在 20 到 30 之间波动。用性能计数器看FEX-Emu 的翻译开销占了大约 25%DXMT 的图形转换占了大约 20%剩下的是游戏本身的逻辑和渲染。这个成绩在 iOS 设备上算可以接受毕竟中间隔了两层翻译。遇到一个问题游戏里的中文全是方块。按前面说的方法把中文字体放进 prefix 的字体目录改注册表重启后正常了。另一个问题是音频有爆音调整 Wine 的音频驱动设置从默认改成 ALSA 模拟爆音消失。5. 常见问题与排查技巧实录5.1 启动失败类问题速查现象可能原因排查方向程序闪退无报错FEX-Emu 翻译失败看 FEX 日志确认指令集支持报 module not foundDLL 缺失或路径错检查 FEX_ROOTFS 和 prefix 结构卡在启动画面着色器编译慢等待或预生成缓存提示需要 .NET运行库缺失手动安装对应版本窗口一片黑DXMT 初始化失败检查 Metal 支持看 DXMT 日志这张表是我遇到过的典型情况。闪退无报错最麻烦因为没线索。这时候要开 FEX-Emu 的详细日志看最后翻译到哪条指令挂了。常见的是遇到了不支持的 SSE 指令或者内存访问越界。module not found 多半是路径问题。Wine 找 DLL 的顺序是先看程序目录再看 system32再看 PATH。如果 DLL 放错地方就找不到。建议把依赖的 DLL 统一放 system32省事。5.2 性能问题的定位方法性能问题分两种一种是整体慢一种是卡顿。整体慢的话先看是 CPU 瓶颈还是 GPU 瓶颈。用系统自带的性能监控如果 CPU 占用高而 GPU 空闲瓶颈在 FEX-Emu 或 Wine如果 GPU 占用高瓶颈在 DXMT 或 Metal。卡顿的话看卡顿的周期性。如果每隔几秒卡一下可能是翻译缓存满了在重新翻译加大缓存。如果卡顿没规律可能是内存不足被系统限制减少同时运行的程序。有个容易被忽略的点iOS 的后台管理很激进如果你的程序占用内存接近设备上限系统会开始压缩内存甚至杀进程。这时候表现就是随机卡顿或者闪退。解决办法是尽量精简 prefix删掉不需要的组件。5.3 独家避坑经验第一条不要用最新的 Wine 版本。Wine 的更新有时候会引入回归问题在 iOS 这种非主流平台上尤其明显。建议用一个已知稳定的版本跑通了就别乱升级。我吃过这个亏升级后原本正常的程序起不来了回退才恢复。第二条prefix 要备份。调崩了直接删掉重建比一点点修快得多。备份的时候把整个 prefix 目录打包出问题解压回来就行。第三条日志是你的朋友。Wine 和 FEX-Emu 都能输出详细日志虽然刷屏很烦但出问题的时候是唯一的线索。建议平时把日志级别调低出问题再调高。第四条字体问题优先排查。中文乱码十有八九是字体先解决这个再查别的不然界面都看不清没法调试。第五条别指望所有程序都能跑。有些程序用了特殊的反调试或者加密在 Wine 下就是跑不起来。遇到这种及时止损换方案别死磕。6. 工具链选型与版本搭配建议6.1 Wine 版本的选择逻辑Wine 的版本分稳定版和开发版。稳定版更新慢但问题少开发版功能新但可能有 bug。在 iOS 上我建议用稳定版因为平台本身就不稳定再叠加 Wine 的不稳定因素排查起来太痛苦。具体版本号方面Wine 8.x 和 9.x 在 ARM64 上的表现差异不大主要看对特定 API 的支持。如果你要跑的程序用了比较新的 Windows API可能需要新版本。反之老程序用老版本反而更稳。还有个分支叫 Wine-staging包含了一些还没进主线的补丁对某些程序兼容性更好。但 staging 的补丁质量参差不齐可能修好一个问题引入另一个。我的建议是先用主线版实在跑不起来再试 staging。6.2 FEX-Emu 的配置取舍FEX-Emu 的配置核心是平衡正确性和性能。前面说的FEX_TSOENABLED就是典型。开启保证正确关闭提升性能。怎么选取决于你的程序。单线程的老程序关掉 TSO 通常没问题能快 10% 到 20%。多线程程序尤其是用了锁和原子操作的必须开启否则会出现数据竞争表现为随机崩溃或者计算结果错误。FEX_MULTIBLOCK也是类似。开启提升循环性能但增加翻译延迟。如果你的程序启动慢但运行流畅可以关掉它加快启动。如果启动快但运行卡开启它。6.3 DXMT 与其他图形方案的对比方案原理优点缺点DXMTD3D 直接转 Metal性能好路径短只支持 D3D11D3D12 不完整WineD3DD3D 转 OpenGL兼容性好iOS 上 OpenGL 已废弃性能差MoltenVK DXVKD3D 转 Vulkan 转 Metal支持 D3D9/10/11两层转换开销大从表里能看出来DXMT 是 iOS 上最优的选择前提是你的程序用 D3D11。如果是 D3D9 的老程序要么用 WineD3D 忍受性能损失要么等 DXMT 支持 D3D9。MoltenVK 那条路在 iOS 上不太现实两层转换的开销太大而且 MoltenVK 本身在 iOS 上的支持也有限。7. 影响范围与适用场景分析7.1 这套方案能覆盖哪些需求最直接的场景是跑 Windows 单机游戏。很多老游戏没有 iOS 版本用这套方案能在手机上重温。但要注意3D 大作基本别想帧率会低到没法玩。2D 游戏和轻度 3D 游戏比较现实。第二个场景是行业软件。有些专业工具只有 Windows 版比如某些工控软件、老版本的 CAD。如果这些软件对性能要求不高用这套方案应急是可行的。第三个场景是开发和测试。如果你在开发跨平台软件想快速验证 Windows 版本的行为这套方案能省一台 Windows 机器的钱。但别用它做正式测试兼容层的行为和真实 Windows 有差异。7.2 性能边界在哪里根据我的实测这套方案的性能边界大概是这样纯 CPU 计算的任务能达到原生性能的 50% 到 70%图形任务能达到 30% 到 50%IO 密集的任务能达到 60% 到 80%。超过这个范围的需求这套方案就不合适了。具体到游戏2D 游戏基本能满帧轻度 3D 游戏能到 30 帧左右重度 3D 游戏个位数帧率。所以选程序的时候要有预期别拿它跑赛博朋克。7.3 后续可能的演进方向从技术趋势看FEX-Emu 的翻译效率还有提升空间尤其是对 AVX-512 这类新指令的支持。DXMT 对 D3D12 的支持也在完善未来能跑的程序会更多。Wine 本身也在持续更新对新 Windows API 的覆盖越来越全。另一个方向是苹果自家的 Game Porting Toolkit它也是把 Windows 游戏往 macOS 上搬思路和 Wine 类似。如果这套工具下沉到 iOS可能会改变整个格局。但目前它还主要在 macOS 上iOS 上能不能用、怎么用还是未知数。8. 我个人的实操体会折腾这套东西最大的感受是耐心比技术重要。很多时候问题不是你不会而是你没找到线索。日志、备份、版本管理这些基本功做扎实了大部分问题都能解决。另一个体会是别追求完美。兼容层就是兼容层不可能和原生一样。能跑起来、能用就已经达到目的了。纠结于每一帧的性能、每一个像素的渲染只会把自己耗死。最后分享一个小技巧如果你要跑的程序有多个版本优先试老版本。老版本用的 API 更基础兼容层支持得更好跑起来的概率更大。新版本往往用了新特性兼容层还没跟上。这个规律我验证过很多次屡试不爽。