☰
iOS上运行Windows程序:Wine+FEX-Emu+DXMT四层翻译栈实战
2026/10/1 13:31:07 网站建设 项目流程

1. 项目缘起:为什么要在 iOS 上折腾 Wine

第一次听到“Madeira”这个名字,很多人会以为是那个葡萄牙的旅游海岛,但在我们这群喜欢在移动设备上折腾桌面应用的人眼里,它指向的是另一件事——把 Wine 这套 Windows 兼容层,想办法搬到 iOS 设备上去跑。这个想法听起来有点疯狂,毕竟 iOS 的沙盒机制、签名限制、没有真正的本地可执行权限,每一条都在跟你作对。但偏偏就是有人不信邪,从 FEX-Emu 到 DXMT,从 x86-64 指令翻译到 Metal 图形转译,硬生生拼出了一条路。

我做这个方向的探索大概有两年多时间,中间踩过的坑比走通的路多得多。最开始是在桌面 Linux 上用 Wine 跑一些老旧的 Windows 工具,后来接触到 FEX-Emu 这个项目,发现它能在 ARM 设备上翻译 x86-64 指令,再后来看到有人把 Wine、FEX-Emu、DXMT 这几样东西串起来在 iOS 上跑起了 Windows 程序,当时第一反应是“这也能行?”第二反应就是“我得自己试试”。

这篇文章不是一篇官方文档式的教程,而是我把自己从零开始搭建、调试、踩坑、再调试的整个过程整理出来。适合哪些人看?如果你是对 iOS 底层机制好奇的开发者,如果你手上有越狱设备或者自签环境想跑点 Windows 老程序,如果你在做跨平台兼容层的研究,那这篇内容应该能帮你省下不少试错时间。如果你只是普通用户想装个 Windows 软件玩玩,我建议你先看完再决定要不要入坑,因为这条路确实不轻松。

核心关键词就几个:Wine、FEX-Emu、DXMT、iOS、x86-64。把这五个词之间的关系理清楚,整个项目的脉络就清楚了。Wine 负责提供 Windows API 的兼容实现,FEX-Emu 负责把 x86-64 指令翻译成 ARM64 能执行的指令,DXMT 负责把 Direct3D 调用转译成 Metal,iOS 是运行平台,x86-64 是目标程序的指令集架构。这四层叠在一起,才让一个 Windows 程序在 iPhone 或 iPad 上跑起来成为可能。

2. 整体架构拆解:四层翻译栈是怎么叠起来的

2.1 从 Windows 程序到 iOS 屏幕的完整链路

先把这个链路讲清楚,不然后面操作的时候你都不知道自己在干什么。一个 Windows 程序在 iOS 上运行,大致要经过这么几个阶段:

  • 第一层:PE 加载。Wine 的 loader 读取 Windows 的 PE 格式可执行文件,解析导入表、重定位表,把程序加载到内存里。这一步跟你在 Linux 上用 Wine 是一样的。
  • 第二层:指令翻译。程序里的 x86-64 指令不能直接在 ARM64 的 iOS 设备上跑,FEX-Emu 这时候介入,把 x86-64 指令块翻译成 ARM64 指令块,并且做缓存。第一次执行某段代码会慢,后面命中缓存就快了。
  • 第三层:API 转换。Windows 程序调用 kernel32.dll、user32.dll 这些系统库的时候,Wine 把这些调用转换成 POSIX 调用或者 iOS 能理解的接口。图形方面,如果程序用 Direct3D,DXMT 会把 D3D 调用转成 Metal 调用。
  • 第四层:图形输出。最终画面通过 Metal 渲染到 iOS 的屏幕上,输入事件从 iOS 的触摸或外接键鼠反向传递回 Wine 的消息循环。

这四层每一层都有性能损耗,叠在一起能跑起来就已经是奇迹了。我实测下来,一个简单的 Win32 记事本程序,在 A15 芯片的 iPhone 上启动大概要三到五秒,复杂一点的程序可能要十几秒甚至更久。所以如果你打算跑大型游戏或者专业软件,期望值要放低一点。

2.2 为什么选 FEX-Emu 而不是其他方案

在 ARM 设备上跑 x86 程序,市面上有几个选择:QEMU 做全系统模拟、Box64 做用户态翻译、FEX-Emu 也是用户态翻译。我选 FEX-Emu 的原因有几个:

第一,FEX-Emu 对 x86-64 指令集的覆盖比较完整,尤其是 SSE4.2、AVX 这些扩展指令的支持比 Box64 要早。很多 Windows 程序编译的时候默认开了 SSE4.2,如果翻译层不支持,直接就是非法指令崩溃。

第二,FEX-Emu 的 JIT 缓存机制比较成熟。它会把翻译过的指令块缓存起来,下次执行同样的代码直接走缓存,不用重新翻译。这在 iOS 这种内存受限的环境下特别重要,因为你可以控制缓存大小,避免被系统杀掉。

第三,FEX-Emu 的社区活跃度不错,遇到问题去翻 issue 和讨论区,大概率能找到答案或者至少找到方向。Box64 虽然也不错,但在 iOS 上的实践案例相对少一些。

当然 FEX-Emu 也不是没有缺点。它的配置相对复杂,需要调不少参数,而且对 32 位 x86 程序的支持不如 64 位那么好。如果你要跑的是老旧的 32 位程序,可能还得额外折腾。

2.3 DXMT 的角色:为什么不用 DXVK 或 WineD3D

图形转译这块,桌面 Linux 上大家常用 DXVK(把 D3D 转 Vulkan)或者 WineD3D(把 D3D 转 OpenGL)。但在 iOS 上,Vulkan 没有原生支持,OpenGL 也被苹果标记为废弃了,唯一可用的现代图形 API 就是 Metal。DXMT 就是专门做 D3D 到 Metal 转译的项目。

我试过用 WineD3D 跑一些老程序,在 iOS 上通过 OpenGL ES 转译,性能惨不忍睹,而且兼容性问题很多。DXMT 虽然还比较年轻,但对 D3D11 的支持已经能跑不少程序了。D3D9 的支持也在完善中,D3D12 目前基本不用想。

这里有个细节要注意:DXMT 需要 Metal 的一些高级特性,比如 argument buffer、indirect command buffer 这些。老设备上这些特性可能不支持或者支持不完整,所以 DXMT 在 iPhone X 及以后的设备上表现会好很多。如果你用的是更老的设备,可能只能退回 WineD3D 方案,性能就别指望了。

3. 环境准备:在 iOS 上搭建运行环境的前置条件

3.1 设备与系统版本的选择

不是所有 iOS 设备都能跑这套东西。根据我的实测经验,有几个硬性条件:

  • 芯片:A12 及以上。A11 及更早的芯片在 Metal 特性支持和内存带宽上都吃紧,跑起来体验很差。
  • 内存:建议 4GB 及以上。Wine 本身加上 FEX-Emu 的翻译缓存,再加上目标程序,2GB 内存的设备基本一启动就闪退。
  • 系统版本:iOS 15 到 iOS 17 之间的版本兼容性最好。太新的系统对 JIT 和内存管理的限制更严,太老的系统缺少一些必要的 API。
  • 存储空间:至少预留 10GB。Wine 的 prefix 目录、FEX-Emu 的缓存、目标程序本身,加起来很容易超过 5GB。

我手头测试用的是一台 iPhone 13 Pro(A15,6GB 内存)和一台 iPad Pro 11 寸(M1,8GB 内存)。M1 的 iPad 体验明显好很多,因为内存大、散热好,长时间跑不容易降频。

3.2 签名与权限:最容易被卡住的一步

iOS 的沙盒机制决定了你不能随便运行可执行文件。要让 Wine 和 FEX-Emu 跑起来,你需要解决几个权限问题:

JIT 权限。FEX-Emu 需要动态生成 ARM64 代码并执行,这需要 JIT 权限。在 iOS 上,只有通过特定的调试方式或者越狱环境才能获得 JIT 权限。如果你用的是自签证书,需要开启get-task-allow权限,并且通过调试器附加的方式启动应用。

可执行内存。iOS 默认不允许同时可写和可执行的内存页(W^X 保护)。FEX-Emu 的 JIT 缓存需要可执行内存,所以要么越狱后关闭这个保护,要么使用特定的 API 来申请可执行内存。

文件系统访问。Wine 需要创建和管理 prefix 目录,里面会有大量的文件读写操作。在沙盒环境下,你只能在自己的应用容器内操作,不能访问其他应用的数据。

我自己的做法是在一台越狱的 iPad 上做主要测试,因为越狱环境省去了很多签名和权限的麻烦。但如果你不想越狱,也可以走自签加调试器附加的路线,只是每次启动都要连电脑,比较麻烦。

注意:越狱和自签都涉及设备安全策略的调整,操作前请确认你了解相关风险,并且只在你自己拥有的设备上进行测试。

3.3 必备组件清单与获取方式

搭建这套环境需要以下几个核心组件:

组件作用获取方式
WineWindows API 兼容层从官方源码编译 ARM64 版本
FEX-Emux86-64 到 ARM64 指令翻译从官方仓库编译 iOS 版本
DXMTD3D 到 Metal 转译从项目仓库编译
MoltenVKVulkan 到 Metal 转译(可选)从官方仓库获取
依赖库各种运行时库通过包管理器或手动编译

编译这些组件是个大工程,尤其是 Wine 和 FEX-Emu,在 macOS 上交叉编译到 iOS 需要配置不少工具链。如果你不想自己编译,也可以找社区里已经编译好的版本,但要注意版本匹配问题。Wine 的版本和 FEX-Emu 的版本如果不匹配,可能会出现各种奇怪的崩溃。

我建议的版本组合是:Wine 8.x 系列 + FEX-Emu 最新稳定版 + DXMT 最新版。这个组合我实测下来稳定性最好。Wine 9.x 虽然新,但和 FEX-Emu 的配合还有些问题,容易出现翻译错误。

4. 实操过程:从零开始搭建运行环境

4.1 编译 Wine 的 ARM64 版本

这一步是整个流程里最耗时的。Wine 的代码量很大,交叉编译到 iOS 需要不少耐心。

首先你需要准备一台 macOS 电脑,安装 Xcode 和 Command Line Tools。然后获取 iOS 的 SDK,配置交叉编译工具链。Wine 官方没有提供 iOS 的编译脚本,你需要自己写一个 configure 的参数配置。

关键的 configure 参数包括:

./configure \ --host=arm-apple-darwin \ --with-wine-tools=../wine-tools \ --disable-tests \ --without-x \ --without-freetype \ --without-alsa \ --without-pulse \ --without-cups \ --without-dbus \ --without-gnutls \ --without-krb5 \ --without-netapi \ --without-opencl \ --without-osmesa \ --without-oss \ --without-pcap \ --without-sane \ --without-udev \ --without-usb \ --without-v4l2 \ --without-vulkan \ --without-wayland \ --without-xcomposite \ --without-xcursor \ --without-xfixes \ --without-xinerama \ --without-xinput \ --without-xinput2 \ --without-xrandr \ --without-xrender \ --without-xshape \ --without-xshm \ --without-xinerama

这些--without参数是为了去掉 iOS 上不需要或者不支持的依赖。编译过程中可能会遇到各种头文件缺失的问题,需要手动补一些 stub 头文件。

编译完成后,你会得到wine、wineserver、wineboot等可执行文件,以及一堆.dll和.so文件。这些文件需要打包到 iOS 应用的 bundle 里。

实操心得:编译 Wine 的时候建议用make -j$(sysctl -n hw.ncpu)并行编译,能省不少时间。但注意内存占用,如果 Mac 内存不够,并行数调低一点,不然会频繁 swap。

4.2 编译 FEX-Emu 并配置 JIT

FEX-Emu 的编译相对 Wine 要简单一些,但 JIT 配置是个关键点。

FEX-Emu 的源码里有一个Source/Tools/FEXLoader目录,这是加载器的入口。编译的时候需要开启ENABLE_JIT选项,并且配置好 JIT 缓存的大小。在 iOS 上,我建议把 JIT 缓存限制在 256MB 到 512MB 之间,太大了容易被系统杀掉,太小了又频繁触发重新翻译。

FEX-Emu 的配置文件Config.json里有几个关键参数:

{ "JITCacheSize": 268435456, "JITInlineCacheSize": 16777216, "TSOEnabled": true, "SMCChecks": "mtrack", "X87ReducedPrecision": true, "Multiblock": true, "DynamicL1Cache": true }

TSOEnabled是开启 x86 的内存序模型模拟,这个必须开,不然多线程程序会出各种数据竞争问题。SMCChecks是自修改代码检测,有些程序会动态修改自己的代码,不开这个会跑飞。Multiblock是开启多块编译,能提升性能,但会增加编译时间。

JIT 权限的获取是另一个难点。在越狱环境下,你可以直接调用mprotect把内存页设成可执行。在非越狱环境下,你需要通过pthread_jit_write_protect_np这个 API 来切换内存页的写和执行权限。这个 API 在 iOS 14 以后才可用,而且需要特定的 entitlement。

4.3 集成 DXMT 并配置 Metal 后端

DXMT 的编译需要 Metal 的 SDK,在 macOS 上编译到 iOS 需要配置 Metal 的交叉编译工具链。

DXMT 的核心是把 D3D 的着色器字节码转译成 Metal 的着色器语言。它内部有一个 D3D 字节码解析器和一个 Metal 代码生成器。编译的时候需要链接 Metal 框架和 Foundation 框架。

配置 DXMT 的时候有几个关键点:

  • Metal 设备选择:在 iOS 上只有一个 Metal 设备,直接调用MTLCreateSystemDefaultDevice()就行。
  • 命令队列:DXMT 会创建一个或多个 Metal 命令队列,建议根据 CPU 核心数来配置,一般 2 到 4 个队列比较合适。
  • 纹理格式映射:D3D 的纹理格式和 Metal 的纹理格式不是一一对应的,DXMT 内部做了映射表,但有些格式可能不支持,需要 fallback 到软件渲染。

我实测下来,DXMT 对 D3D11 的支持已经能跑不少程序了,比如一些老版本的 3D 建模软件、简单的游戏。但 D3D9 的支持还有些问题,部分程序会出现画面闪烁或者纹理错乱。

4.4 打包成 iOS 应用并签名

把 Wine、FEX-Emu、DXMT 和你的目标程序打包成一个 iOS 应用,需要创建一个 Xcode 工程,把这些组件作为资源文件嵌入,然后写一个启动器来调用它们。

启动器的核心逻辑是:

  1. 初始化 FEX-Emu 的 JIT 环境。
  2. 设置 Wine 的环境变量,比如WINEPREFIX、WINEDLLPATH等。
  3. 调用wine的入口函数,传入目标程序的路径。
  4. 处理 iOS 的生命周期事件,比如进入后台时暂停 Wine 的消息循环。

签名的时候需要注意,如果你的应用需要 JIT 权限,需要在 entitlements 文件里加上com.apple.security.cs.allow-jit和com.apple.security.cs.allow-unsigned-executable-memory。这两个 entitlement 在自签环境下需要特殊的配置才能生效。

注意:自签应用的证书有效期通常只有 7 天,过期后需要重新签名。如果你要长期使用,建议考虑开发者账号或者越狱环境。

5. 常见问题与排查技巧实录

5.1 Wine 乱码问题:从根源到解决

Wine 乱码是我遇到最多的问题,没有之一。表现就是程序界面上的中文显示成方块、问号或者一堆乱码字符。这个问题的根源是字体和字符集配置。

Wine 默认使用它自带的字体,但这些字体对中文的支持不完整。解决方法有几个:

方法一:安装中文字体到 Wine 的字体目录。把simsun.ttc、msyh.ttf这些中文字体复制到$WINEPREFIX/drive_c/windows/Fonts/目录下,然后在注册表里配置字体替换。

方法二:修改注册表的字体链接。在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink里添加字体映射,把Tahoma、MS Sans Serif这些默认字体链接到中文字体。

方法三:设置LANG和LC_ALL环境变量。在启动 Wine 之前设置LANG=zh_CN.UTF-8,让 Wine 知道你要用中文环境。

我自己的做法是方法一加方法二组合使用,先把字体文件放进去,再改注册表。这样大部分程序的中文显示就正常了。但有些程序用的是自己的字体渲染引擎,可能还是会有问题,那就只能针对具体程序来调。

还有一个坑是wine 栏是乱码,这个通常是指 Wine 的窗口标题栏乱码。这个问题的原因是 Wine 的窗口管理器没有正确加载字体。解决方法是在WINEPREFIX的user.reg文件里找到[Software\\Wine\\Fonts]段,把Replacements的值改成你安装的中文字体。

5.2 FEX-Emu 崩溃与非法指令排查

FEX-Emu 崩溃通常有几个原因:

非法指令。表现是程序启动后立刻崩溃,日志里会有SIGILL信号。这通常是因为 FEX-Emu 不支持某条 x86-64 指令。解决方法是更新 FEX-Emu 到最新版本,或者在配置里开启X87ReducedPrecision和AVXEnabled这些选项。

内存访问错误。表现是SIGSEGV信号,程序随机崩溃。这可能是 JIT 缓存太小,翻译代码被换出后重新执行时出错。解决方法是增大JITCacheSize,或者关闭DynamicL1Cache。

翻译缓存冲突。表现是程序运行一段时间后崩溃,日志里有Translation cache full之类的信息。解决方法是清理 FEX-Emu 的缓存目录,或者增大缓存上限。

我整理了一个排查速查表:

症状可能原因解决方法
启动即崩溃,SIGILL不支持的指令更新 FEX-Emu,开启兼容选项
随机崩溃,SIGSEGVJIT 缓存不足增大 JITCacheSize
运行一段时间后崩溃翻译缓存满清理缓存或增大上限
多线程程序数据错乱TSO 未开启开启 TSOEnabled
自修改代码程序崩溃SMC 检测未开设置 SMCChecks 为 mtrack

5.3 DXMT 图形问题:黑屏、闪烁、纹理错乱

DXMT 的图形问题也比较常见,我遇到过的有:

黑屏。程序启动了,有声音,但画面全黑。这通常是 Metal 渲染管线创建失败。检查日志里有没有Failed to create pipeline state之类的错误。解决方法可能是目标程序用了 DXMT 不支持的着色器特性,或者 Metal 设备不支持某些纹理格式。

画面闪烁。这通常是帧同步问题。DXMT 默认使用三重缓冲,但在 iOS 上可能和系统的显示刷新不同步。解决方法是调整 DXMT 的PresentInterval参数,改成 1(垂直同步)或者 0(立即呈现)试试。

纹理错乱。这通常是纹理格式映射错误。D3D 的某些压缩纹理格式在 Metal 上没有直接对应,DXMT 会做转换,但转换过程中可能出错。解决方法是开启 DXMT 的TextureFormatFallback选项,让不支持的格式回退到软件解码。

实操心得:DXMT 的日志级别可以调到 debug,这样能看到详细的图形调用信息。虽然日志量很大,但排查问题的时候非常有用。建议在Config.json里把LogLevel设成debug,复现问题后再调回info。

5.4 性能优化:让程序跑得更流畅

性能优化是个持续的过程,我总结了几条经验:

关闭不必要的 Wine 服务。Wine 默认会启动一些后台服务,比如wineserver、plugplay、svchost等。在 iOS 上这些服务会占用宝贵的内存和 CPU。可以在启动参数里加上WINEDLLOVERRIDES来禁用一些不需要的 DLL。

调整 FEX-Emu 的编译策略。Multiblock开启后,FEX-Emu 会把多个基本块一起编译,减少翻译次数,但会增加单次编译时间。对于交互式程序,建议开启;对于批处理程序,可以关闭。

限制帧率。iOS 设备的屏幕刷新率是 60Hz 或 120Hz,但 Windows 程序可能以更高的帧率渲染。在 DXMT 里设置帧率上限,可以减少 GPU 负载和发热。

使用外接散热。iPhone 跑这套东西发热量很大,一热就降频,降频就卡。我测试的时候用了一个半导体散热背夹,帧率稳定性提升明显。

6. 扩展玩法:这套环境还能做什么

6.1 跑老游戏和怀旧软件

这套环境最直接的用途就是跑那些 Windows 老游戏和老软件。我测试过一些 2000 年代的单机游戏,比如策略类、角色扮演类的,大部分都能跑起来,帧率在 20 到 30 之间,对于这类游戏来说够用了。

一些老版本的办公软件、图像处理软件也能跑,但体验一般,主要是屏幕适配和输入方式的问题。iOS 的触摸操作和 Windows 的鼠标操作差异很大,需要外接蓝牙键鼠才能正常使用。

6.2 作为开发测试环境

如果你在做 Windows 程序的跨平台适配,这套环境可以作为一个轻量级的测试平台。不需要开虚拟机,直接在 iOS 设备上就能验证程序在 ARM 架构上的兼容性。

我有时候会用它来测试一些自己写的小工具,编译成 Windows 版本后直接在 iPad 上跑,看看有没有架构相关的问题。虽然不能完全替代真实的 Windows 环境,但作为一个快速验证的手段还是不错的。

6.3 与 iOS 原生功能的结合

这套环境跑起来之后,理论上可以和 iOS 的原生功能做一些结合。比如把 Wine 程序的输出通过 iOS 的分享接口导出,或者用 iOS 的快捷指令来触发 Wine 程序的运行。

我试过用快捷指令启动 Wine 程序,然后自动把生成的文件保存到 iCloud Drive。这个流程跑通之后,用起来还挺方便的。但要注意,Wine 程序的文件操作是在沙盒内进行的,要访问外部文件需要额外的配置。

7. 个人经验与后续折腾方向

这套东西我断断续续折腾了两年多,中间有几次差点放弃。最崩溃的一次是编译了一整天的 Wine,结果在 iOS 上启动就崩溃,查了半天发现是一个依赖库的版本不匹配。还有一次是 FEX-Emu 的 JIT 缓存配置不对,程序跑几分钟就闪退,排查了好几天才找到原因。

但每次跑通一个新程序的时候,那种成就感还是很足的。尤其是看到一些十几年前的 Windows 软件在 iPhone 上流畅运行,会觉得这些折腾都值了。

后续我打算继续跟进 FEX-Emu 和 DXMT 的更新,看看能不能支持更多的程序和更好的性能。另外也在研究怎么把 Vulkan 的支持加进来,通过 MoltenVK 让一些用 Vulkan 的 Windows 程序也能跑。这条路还很长,但方向是清晰的。

如果你也在折腾这个方向,我的建议是:先从简单的程序开始,把整个流程跑通,再逐步增加复杂度。不要一上来就挑战大型游戏或者专业软件,那样很容易受挫。遇到问题多查日志,多翻社区讨论,大部分问题别人都遇到过。最后,保持耐心,这套东西的调试周期是以天为单位的,急不来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询