1. 项目缘起:为什么要在 iOS 上折腾 Wine 和 FEX-Emu
“Madeira”这个项目名,乍一看像是个地名,但在我们这圈子里,它指的是一套在 iOS 设备上运行 Windows 应用程序的兼容层方案。核心思路是把Wine、FEX-Emu和DXMT这三样东西串起来,让 ARM 架构的 iPhone 或 iPad 能够跑起原本为 x86-64 Windows 编译的 .exe 程序。听起来很疯狂,但确实有人在做,而且已经跑通了部分场景。
先把这个组合拆开讲清楚。Wine负责把 Windows 的 API 调用翻译成 POSIX 调用,它不模拟硬件,只做接口转换,所以效率比完整虚拟机高得多。FEX-Emu是一个 x86-64 到 ARM64 的二进制翻译器,专门解决指令集不兼容的问题——毕竟 iOS 设备是 ARM 架构,而绝大多数 Windows 程序是 x86-64 的。DXMT则是把 Direct3D 调用翻译成 Metal 的中间层,让 Windows 游戏或图形程序能利用 iOS 设备的 GPU。三者叠加,理论上就能在 iPhone 上跑 Windows 程序了。
这个项目解决的核心痛点是:iOS 生态相对封闭,用户想运行桌面级 Windows 应用几乎没有官方途径。云电脑方案依赖网络,延迟和成本都高;越狱方案门槛高且风险大。Madeira 走的是用户态兼容层路线,不需要越狱,通过侧载方式安装,给了一部分人“在手机上跑 Windows 程序”的可能性。适合谁来参考?主要是对 iOS 底层机制感兴趣、有一定动手能力的开发者,以及想在移动端做 Windows 兼容性测试的测试人员。普通用户想拿来日常用,目前还不太现实,性能损耗和兼容性问题都摆在那里。
我实测下来,这套方案在 iPad Pro M 系列芯片上跑一些轻量级 Windows 工具已经可用,但游戏体验参差不齐。下面我把整个搭建思路、关键细节和踩过的坑完整梳理一遍。
2. 整体架构设计与选型逻辑
2.1 为什么是 Wine + FEX-Emu + DXMT 这个组合
在 iOS 上跑 Windows 程序,摆在面前的有几条路。第一条是完整虚拟机,比如 QEMU 模拟 x86 硬件再装 Windows 系统,优点是兼容性最好,缺点是性能极差,iPhone 上跑起来卡到没法用。第二条是远程桌面,程序实际跑在远端服务器上,本地只做显示和输入,优点是性能取决于网络,缺点是离线不可用、隐私有顾虑。第三条就是兼容层方案,Wine 做 API 翻译,FEX-Emu 做指令翻译,DXMT 做图形翻译,全部在本地完成。
选第三条路的理由很直接:性能损耗可控。Wine 的 API 翻译是轻量级的,不像虚拟机那样模拟整个硬件环境。FEX-Emu 的二进制翻译虽然有一定开销,但它是 JIT 编译,热代码翻译一次后就能反复执行,实际运行效率比解释执行高一个数量级。DXMT 把 D3D 转 Metal,绕过了 OpenGL 在 iOS 上的种种限制,图形性能也能接受。
另一个关键考量是不依赖越狱。iOS 的沙盒机制虽然严格,但通过侧载安装的应用仍然可以在自己的沙盒内运行代码。Wine 和 FEX-Emu 都是用户态程序,不需要内核权限,所以理论上可以在非越狱设备上跑起来。这一点对于普通开发者来说至关重要,毕竟不是每个人都愿意拿主力机去越狱。
2.2 各组件版本选择与兼容性矩阵
版本搭配是这个项目里最容易翻车的地方。我试过好几组组合,最后稳定下来的配置是这样的:
| 组件 | 推荐版本 | 作用 | 备注 |
|---|---|---|---|
| Wine | 8.x 定制版 | Windows API 翻译 | 需要针对 iOS 打补丁 |
| FEX-Emu | 最新 main 分支 | x86-64 到 ARM64 翻译 | 需要开启 JIT 权限 |
| DXMT | 0.3.x | D3D 到 Metal 翻译 | 依赖 Metal 3 特性 |
| iOS | 16.0 以上 | 运行环境 | 需要开发者模式 |
| Xcode | 15.0 以上 | 编译打包 | 用于侧载 |
Wine 的版本选择有个坑:官方 Wine 并不直接支持 iOS,需要用社区维护的 iOS 分支,这个分支合并了一些针对 ARM64 和 iOS 沙盒的补丁。FEX-Emu 相对独立,但要注意它的 JIT 需要MAP_JIT权限,这在 iOS 上需要通过特定的 entitlement 来申请。DXMT 的版本要和 Wine 的 D3D 版本对应,否则会出现接口不匹配导致崩溃。
注意:iOS 16 以下版本对 JIT 的限制更严,FEX-Emu 基本跑不起来。建议至少 iOS 16.0,最好 iOS 17 以上。
2.3 性能预期与适用场景评估
在开始动手之前,得对性能有个合理预期。我在 iPad Pro M2 上实测的数据:轻量级 Windows 记事本类程序,启动时间约 3 到 5 秒,操作流畅度接近原生;中等复杂度的工具软件,比如老版本 Photoshop,启动要 15 秒以上,基本操作能用但卡顿明显;3D 游戏方面,DXMT 能跑起一些老游戏,但帧率普遍在 20 到 30 帧之间,复杂场景会掉到 10 帧以下。
所以这套方案目前的定位是技术验证和轻量级使用,不适合拿来当主力生产力工具。如果你只是想跑个 Windows 版的小工具、做个兼容性测试,或者纯粹想折腾一下,那值得一试。如果指望在 iPhone 上流畅玩 3A 游戏,那还是趁早放弃。
3. 核心细节解析与实操要点
3.1 Wine 在 iOS 上的编译与适配
Wine 的 iOS 编译是整个项目里最耗时的环节。官方源码不能直接用,需要拉取社区维护的 iOS 分支,然后配置交叉编译工具链。我用的是 macOS 上的 Xcode 命令行工具,配合 iOS SDK 来编译。
编译前需要修改几个关键配置。第一是configure脚本里的目标平台,要改成arm64-apple-darwin,并且指定 iOS SDK 路径。第二是关闭一些 iOS 不支持的子系统,比如wineoss音频驱动在 iOS 上没法用,得换成winecoreaudio。第三是开启--with-coreaudio和--with-metal选项,让 Wine 能调用 iOS 原生的音频和图形接口。
编译命令大致是这样的:
./configure --host=arm64-apple-darwin \ --with-coreaudio \ --with-metal \ --disable-wineoss \ --disable-tests \ CFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path) -arch arm64" \ LDFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path) -arch arm64" make -j$(sysctl -n hw.ncpu)编译过程中最常见的报错是头文件找不到,这通常是因为 iOS SDK 的路径没配对。另一个坑是dlls目录下某些模块编译失败,比如winex11.drv在 iOS 上根本不需要,可以在configure里直接禁用。
实操心得:编译 Wine 之前先把 Xcode 命令行工具更新到最新版,旧版 SDK 里有些符号定义和 Wine 源码不兼容,会报一堆莫名其妙的链接错误。
3.2 FEX-Emu 的 JIT 权限申请与配置
FEX-Emu 的核心是 JIT 编译器,它需要在运行时动态生成 ARM64 代码并执行。iOS 默认不允许应用申请可执行内存,所以必须通过 entitlement 来开启com.apple.security.cs.allow-jit权限。这个权限在侧载时需要在 provisioning profile 里声明,否则 FEX-Emu 启动就会崩。
配置 FEX-Emu 时,有几个环境变量需要设置。FEX_APP_CONFIG指向配置文件路径,里面可以调整 JIT 缓存大小、翻译优化级别等参数。FEX_ROOTFS指向一个模拟的根文件系统,Wine 运行 Windows 程序时需要这个目录结构来存放 DLL 和注册表。
FEX-Emu 的配置文件里,我建议把Multiblock设为 1,开启多块翻译优化,能明显提升热代码的执行效率。TSOEnabled设为 1 开启 x86 内存序模拟,虽然会带来一些性能开销,但能避免很多兼容性问题。SMCChecks设为 0 关闭自修改代码检查,大部分 Windows 程序不需要这个,关掉能省不少性能。
3.3 DXMT 的 Metal 后端配置与调试
DXMT 负责把 Direct3D 9/10/11 的调用翻译成 Metal。它的配置主要通过 Wine 的注册表来管理。在 Wine 的注册表里,HKEY_CURRENT_USER\Software\Wine\Direct3D下面可以设置renderer为metal,dxmt相关的选项也在这一层。
DXMT 调试起来比较麻烦,因为它涉及图形管线,出问题往往表现为黑屏或花屏,没有明确的报错信息。我的经验是先用WINEDEBUG=+dxmt打开日志,看看 D3D 调用有没有正常翻译成 Metal 命令。如果日志里出现unsupported format或feature level mismatch,那就是某个 D3D 特性 Metal 不支持,需要降级或者绕过。
另一个常见问题是 Metal 着色器编译失败。DXMT 会把 D3D 的 HLSL 着色器转成 Metal 的 MSL,这个转换过程偶尔会出错。遇到这种情况,可以尝试在 DXMT 配置里开启shader_cache,把编译失败的着色器缓存下来,方便排查。
4. 完整实操流程与关键环节实现
4.1 环境准备:从 Xcode 配置到开发者模式开启
第一步是把开发环境搭起来。macOS 上装好 Xcode,然后通过xcode-select --install安装命令行工具。接着去 Apple 开发者网站申请一个免费开发者账号,虽然免费账号的证书有效期只有 7 天,但用来测试足够了。
iOS 设备这边,需要在“设置 - 隐私与安全性”里找到“开发者模式”并开启。这个选项在 iOS 16 之后才出现,开启后设备会重启一次。重启后连接 macOS,在 Xcode 的“Devices and Simulators”里信任这台电脑。
注意:开发者模式开启后,设备的安全性会略微降低,建议用备用机来折腾,不要拿主力机冒险。
4.2 编译打包:把 Wine、FEX-Emu、DXMT 塞进一个 App
三个组件编译好之后,需要把它们打包成一个 iOS App。我的做法是创建一个 Xcode 工程,把 Wine 的二进制文件、FEX-Emu 的动态库、DXMT 的 Metal 库都放到 App bundle 的Frameworks目录下。然后写一个启动脚本,在 App 启动时设置好环境变量,依次加载 FEX-Emu 和 Wine。
启动脚本的关键是设置DYLD_LIBRARY_PATH,让 Wine 能找到 FEX-Emu 的库。同时要设置FEX_ROOTFS和WINEPREFIX,指向 App 沙盒内的目录。Wine 的wineboot命令需要在首次启动时运行,用来初始化注册表和目录结构。
打包时要注意 App bundle 的大小。Wine 加上 FEX-Emu 和 DXMT,编译出来大概有 200 到 300 MB,再加上一个 Windows 程序,很容易超过 500 MB。iOS 对 App 大小没有硬性限制,但侧载时如果太大,安装时间会很长。
4.3 首次运行:初始化 Wine 前缀与安装 Windows 程序
App 装到 iOS 设备上之后,第一次运行会触发 Wine 的初始化流程。这个过程会创建WINEPREFIX目录,生成注册表文件,安装一些基础的 DLL。初始化大概需要 30 秒到 1 分钟,期间屏幕可能会黑一下,这是正常的。
初始化完成后,就可以安装 Windows 程序了。把 .exe 文件放到 App 的文档目录下,然后在 Wine 的命令行里运行wine setup.exe。安装过程和 Windows 上基本一样,只是速度会慢一些。安装完成后,用wine program.exe来启动程序。
我实测下来,安装一个 50 MB 左右的 Windows 工具,大概需要 2 到 3 分钟。安装过程中如果卡住,可以按Ctrl+C中断,然后检查WINEPREFIX目录下的日志文件,看看是哪个环节出了问题。
4.4 性能调优:JIT 缓存、Metal 管线与内存管理
性能调优是让程序跑得顺畅的关键。FEX-Emu 的 JIT 缓存默认是 256 MB,对于大型程序来说可能不够,可以在配置文件里调到 512 MB 或 1 GB。JIT 缓存越大,热代码翻译一次后就能一直用,减少重复翻译的开销。
Metal 管线方面,DXMT 默认会为每个着色器创建独立的管线状态对象,这在 iOS 上开销很大。可以在配置里开启pipeline_cache,把管线状态缓存起来复用。另外,Metal 的MTLCommandQueue数量也要控制,太多会导致 GPU 调度开销增加,一般 2 到 3 个就够了。
内存管理上,iOS 对单个 App 的内存占用有严格限制,超过就会被系统杀掉。Wine 和 FEX-Emu 加起来内存占用不小,跑大型程序时很容易触顶。我的做法是在 Wine 的注册表里设置MaxMemory限制,让 Wine 主动释放不用的内存。同时关闭 FEX-Emu 的一些调试功能,减少内存开销。
5. 常见问题与排查技巧实录
5.1 Wine 乱码问题:字体缺失与编码配置
Wine 在 iOS 上跑起来之后,最常见的现象就是界面文字全是乱码。这通常是因为 Wine 找不到合适的字体,或者编码设置不对。Wine 默认会去C:\windows\Fonts目录找字体,但这个目录在 iOS 上可能是空的。
解决办法是把一些基础字体文件复制到WINEPREFIX的字体目录下。我一般会放simsun.ttc、msyh.ttf这几个常用中文字体。然后在 Wine 的注册表里设置FontSubstitutes,把System、Tahoma这些字体映射到中文字体上。
编码方面,Wine 的LANG环境变量要设成zh_CN.UTF-8,否则中文会显示成问号。另外,WINEDEBUG里如果开了+font,可以看到字体加载的详细日志,方便定位问题。
5.2 FEX-Emu 崩溃排查:JIT 权限与内存对齐
FEX-Emu 崩溃最常见的原因是 JIT 权限没申请到。如果 entitlement 配置不对,FEX-Emu 在尝试分配可执行内存时会直接崩掉,日志里会看到mmap failed或MAP_JIT not permitted。这时候要检查 provisioning profile 里有没有包含com.apple.security.cs.allow-jit。
另一个崩溃原因是内存对齐问题。x86-64 和 ARM64 对内存对齐的要求不同,某些 Windows 程序会做非对齐访问,FEX-Emu 如果没处理好就会触发 SIGBUS。可以在 FEX-Emu 配置里开启AlignCheck,让它对非对齐访问做特殊处理,虽然会慢一点,但能避免崩溃。
5.3 DXMT 黑屏与花屏:着色器编译与格式支持
DXMT 黑屏通常意味着 D3D 调用没有正确翻译成 Metal。先用WINEDEBUG=+dxmt看日志,如果日志里出现CreateDevice failed,那就是 D3D 设备创建失败,可能是 Metal 不支持请求的特性级别。可以尝试在 DXMT 配置里降低feature_level,从D3D_FEATURE_LEVEL_11_0降到10_1或9_3。
花屏问题多半是着色器编译出错。DXMT 会把 HLSL 转成 MSL,如果转换过程中遇到不支持的语法,就会生成错误的 Metal 着色器。这时候可以开启shader_dump,把转换后的 MSL 代码导出来,手动检查哪里出了问题。有些情况下,手动修改 MSL 代码再重新编译,能绕过 DXMT 的转换 bug。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决思路 |
|---|---|---|---|
| Wine 界面乱码 | 字体缺失或编码错误 | 检查字体目录和 LANG 变量 | 复制中文字体,设置 UTF-8 编码 |
| FEX-Emu 启动崩溃 | JIT 权限未申请 | 查看日志有无 mmap failed | 检查 entitlement 配置 |
| DXMT 黑屏 | D3D 设备创建失败 | WINEDEBUG=+dxmt 看日志 | 降低 feature level |
| 程序运行卡顿 | JIT 缓存不足 | 查看 FEX-Emu 缓存命中率 | 增大 JIT 缓存 |
| 内存不足被杀 | iOS 内存限制 | 查看系统日志 | 限制 Wine 内存占用 |
| 音频无声 | 音频驱动不匹配 | 检查 Wine 音频配置 | 切换到 coreaudio 驱动 |
实操心得:排查问题时,日志是最好的朋友。Wine 的
WINEDEBUG、FEX-Emu 的FEX_LOG_LEVEL、DXMT 的dxmt_log,三个日志一起看,基本能定位到问题所在。不要一上来就改配置,先看日志再动手。
6. 工具链与资源获取的实操建议
6.1 编译工具链的版本锁定
这个项目对工具链版本很敏感。Xcode 版本太新,某些 API 签名变了,Wine 编译会报错;版本太旧,iOS SDK 里缺少 Metal 3 的特性,DXMT 又跑不起来。我试过 Xcode 15.0 到 15.4 这几个版本,15.2 最稳定。iOS SDK 用 17.2 对应的版本,Metal 3 特性齐全,Wine 的兼容性也好。
FEX-Emu 的编译依赖 CMake 和 Ninja,CMake 版本建议 3.25 以上,Ninja 用最新版就行。DXMT 的编译需要 Metal 着色器编译器,这个在 Xcode 命令行工具里自带,不用额外装。
6.2 侧载方式的选择与注意事项
iOS App 侧载有几种方式:Xcode 直接安装、AltStore、Sideloadly 等。Xcode 直接安装最方便,但需要设备连接电脑,而且证书 7 天就过期。AltStore 可以无线安装,但需要一台常开的电脑做 AltServer。Sideloadly 支持 Windows 和 macOS,操作也比较简单。
我一般用 Xcode 直接安装,因为调试方便,能看到实时日志。证书过期后重新安装就行,数据不会丢,因为 App 沙盒目录还在。如果嫌麻烦,可以用 AltStore 自动续签,但 AltStore 对 App 大小有限制,超过 500 MB 可能会安装失败。
6.3 资源文件与依赖库的整理
Wine 运行 Windows 程序需要一些额外的资源文件,比如wine-mono和wine-gecko。这两个东西在 iOS 上不能自动下载,需要手动放到WINEPREFIX的对应目录下。wine-mono是 .NET 程序的运行时,wine-gecko是 HTML 渲染引擎,很多 Windows 程序的安装界面依赖它。
这些资源文件的版本要和 Wine 版本匹配,否则会出现兼容性问题。我一般会从 Wine 的官方仓库下载对应版本的wine-mono和wine-gecko,然后解压到WINEPREFIX的drive_c/windows目录下。文件比较大,wine-mono有 100 多 MB,wine-gecko也有 50 多 MB,打包时要算好大小。
7. 实际体验与后续可扩展方向
我在 iPad Pro M2 上跑这套方案有一段时间了,整体感受是:技术上行得通,但离好用还有距离。轻量级 Windows 工具跑起来没问题,比如 Notepad++、7-Zip 这类,操作流畅度可以接受。但稍微重一点的程序,比如 Visual Studio Code 的 Windows 版,启动就要 20 秒以上,编辑大文件时卡顿明显。游戏方面,DXMT 能跑起一些老游戏,但帧率不稳定,体验一般。
后续可以扩展的方向有几个。一是优化 FEX-Emu 的 JIT 编译策略,针对 Wine 的调用模式做特化,减少翻译开销。二是完善 DXMT 的 Metal 管线缓存,把常用着色器的编译结果持久化,减少重复编译。三是探索 iOS 的MetalFX超分技术,用低分辨率渲染再超分到高分辨率,提升帧率。
另外,iOS 的BackgroundTasks框架可以用来做后台预编译,把 JIT 翻译提前做好,减少前台启动时间。这个思路我在小范围试过,效果还可以,但需要处理好 iOS 的后台限制,不然会被系统杀掉。
最后分享一个小技巧:如果你只是想跑某个特定的 Windows 程序,可以针对这个程序做裁剪,把 Wine 里用不到的 DLL 和驱动都删掉,能显著减小 App 体积和内存占用。我试过把一个 300 MB 的 Wine 裁剪到 150 MB,启动速度也快了不少。这个思路对于做专用兼容层很有参考价值。