ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Madeira 跨平台兼容层:FEX-Emu + Wine + DXMT 在 iOS 上运行 x86-64 Windows 程序

Madeira 跨平台兼容层:FEX-Emu + Wine + DXMT 在 iOS 上运行 x86-64 Windows 程序 1. 从“Madeira”这个名字说起一个被低估的跨平台兼容层项目第一次看到“Madeira”这个标题加上关键词里那一串 Wine、FEX-Emu、DXMT、iOS、x86-64我脑子里第一反应是这又是一个在“让不同架构、不同系统的程序互相跑起来”这件事上做文章的项目。Madeira 本身是葡萄牙的一座岛屿也是马德拉酒的名字用它来命名一个技术项目多少带点“调和、融合”的意味——把原本不兼容的东西混在一起还能保持各自的风味。我接触过不少兼容层和转译方案从早期的 Wine 到后来的各种指令集翻译工具核心矛盾始终没变代码是为某个平台编译的但你想在另一个平台上跑。x86-64 的二进制想在 ARM 设备上执行Windows 的 API 调用想在类 Unix 系统上响应DirectX 的图形指令想被 Metal 或 Vulkan 接管——每一层都是硬骨头。Madeira 这个项目从关键词组合来看瞄准的就是这条链路用 FEX-Emu 做指令集转译用 Wine 做 Windows API 兼容用 DXMT 把 DirectX 翻译到 Metal最终目标平台指向 iOS。这个组合很有意思。iOS 设备全是 ARM 架构App Store 的审核机制又极其严格想在上面跑 x86-64 的 Windows 游戏或应用理论上要跨越三道鸿沟指令集、系统调用、图形接口。Madeira 如果能把这三层串起来那它解决的就不只是“能不能跑”的问题而是“能不能在移动端以可接受的性能跑”的问题。我写这篇东西不是要吹这个项目有多神而是想把我对这套技术栈的理解拆开揉碎讲清楚。如果你是对跨平台兼容感兴趣的开发者或者手里有一堆老 Windows 程序想在 iPad 上试试再或者你只是好奇 FEX-Emu 和 DXMT 到底怎么配合那下面的内容应该能给你一些实在的参考。我会从架构拆解讲到实操配置再讲到实际跑起来之后会遇到的那些坑尽量把“为什么这么设计”和“实际怎么操作”都说明白。2. Madeira 的技术底座三层转译到底在做什么2.1 第一层FEX-Emu 把 x86-64 指令翻译成 ARM64FEX-Emu 是一个用户态的 x86-64 到 ARM64 的指令集模拟器但它不是传统意义上的“模拟器”而是动态二进制翻译。区别在哪传统模拟器比如 QEMU 的 TCG 模式是把每一条 x86 指令解释成中间码再执行开销很大。FEX-Emu 的做法是在运行时把 x86-64 的代码块翻译成 ARM64 的代码块然后直接让 CPU 执行翻译后的原生指令。翻译一次缓存起来下次遇到同样的代码块直接复用。这个机制决定了它的性能上限比纯解释器高得多但也带来一个问题翻译的粒度。如果翻译块太小翻译开销占比就高如果太大遇到自修改代码或者跳转密集的程序就容易失效。FEX-Emu 在这块做了不少优化比如它支持 SMC自修改代码检测遇到程序动态生成代码时会回退到更保守的翻译策略。在 Madeira 的语境里FEX-Emu 负责的是最底层的那道坎让 ARM64 的 CPU 能“看懂” x86-64 的机器码。没有这一层后面 Wine 和 DXMT 都无从谈起因为 Windows 程序编译出来的二进制就是 x86-64 的。2.2 第二层Wine 把 Windows API 调用映射到 POSIX 调用指令集通了接下来是系统调用。Windows 程序不直接跟硬件打交道它调用的是 kernel32.dll、user32.dll、ntdll.dll 这些系统库。Wine 做的事情就是提供一套兼容这些 DLL 的替代实现把 Windows 的 API 调用翻译成宿主系统的 POSIX 调用。比如 Windows 的CreateFile最终会被 Wine 映射到 Linux 或 macOS 的open系统调用CreateWindowEx会被映射到宿主系统的窗口管理接口。这个过程不是简单的函数转发因为 Windows 的语义和 POSIX 的语义有很多微妙差异——文件路径分隔符、权限模型、线程调度、注册表……Wine 要模拟的是一个完整的 Windows 运行时环境。在 Madeira 里Wine 跑在 FEX-Emu 之上也就是说 Wine 本身的代码也是 x86-64 的也要被 FEX-Emu 翻译。这就带来一个性能问题Wine 的代码量很大翻译开销不小。所以实际部署时通常会尽量让 Wine 的核心组件以原生 ARM64 的方式运行只把 Windows 程序的代码交给 FEX-Emu 翻译。这个边界怎么划是 Madeira 这类项目需要仔细权衡的地方。2.3 第三层DXMT 把 DirectX 调用翻译成 Metal图形是最后一层也是最难的一层。Windows 游戏大量使用 DirectX而 iOS 上唯一可用的底层图形 API 是 Metal。DXMT 的作用就是在 DirectX 和 Metal 之间做翻译。这个翻译不是一对一的函数映射因为 DirectX 和 Metal 的抽象层级不一样。DirectX 11 有设备、上下文、着色器、资源视图这些概念Metal 有设备、命令队列、命令缓冲区、编码器。DXMT 需要把 DirectX 的状态机模型转换成 Metal 的命令编码模型还要处理着色器编译——把 HLSL 编译成 Metal Shading Language或者用 SPIR-V 做中间层。我实测过类似的方案最大的感受是图形翻译的性能损耗主要不在 API 调用本身而在着色器编译和资源同步。每次遇到新的着色器都要现场编译编译一次可能几百毫秒游戏就会卡一下。DXMT 如果做了着色器缓存情况会好很多但缓存命中率取决于游戏是否频繁生成新着色器。这三层叠起来Madeira 的架构大致是这样的层级组件职责性能关键点指令集层FEX-Emux86-64 到 ARM64 的动态翻译翻译块缓存命中率、SMC 处理系统调用层WineWindows API 到 POSIX 的映射Wine 组件的原生化比例图形层DXMTDirectX 到 Metal 的翻译着色器编译缓存、资源同步策略3. 为什么是 iOS目标平台的选择逻辑与限制3.1 iOS 的 ARM64 架构和 Metal 生态是天然匹配选 iOS 作为目标平台不是随便拍的。iOS 设备从 A7 芯片开始就是 64 位 ARM 架构到现在 M 系列芯片的 iPad Pro性能已经足够跑一些中等负载的 Windows 程序。而且 iOS 的图形栈统一走 Metal没有 OpenGL 的历史包袱DXMT 只需要面对一个目标 API不用像在 Linux 上那样同时考虑 OpenGL 和 Vulkan。另一个原因是移动端的便携性。把 Windows 游戏跑到 iPad 上听起来是个很极客的需求但确实有人想这么干——比如躺在床上玩老游戏或者出差时不想带笔记本。Madeira 如果能把这条路走通那它的用户场景是真实存在的。3.2 但 iOS 的限制也是最多的iOS 不是你想跑什么就能跑什么。App Store 审核指南明确限制了解释型代码和动态生成代码而 FEX-Emu 的动态翻译本质上就是在运行时生成 ARM64 代码。这意味着 Madeira 很难通过正规渠道上架 App Store。那怎么办常见的做法是走侧载或者企业签名但这又涉及到开发者账号、设备管理、证书有效期这些问题。我在关键词里看到“iOS 开发者模式”“免费证书 iOS”“Xcode 从证书配置到上架全流程”这些词说明很多人在这条路上踩过坑。Madeira 如果要实际部署证书和签名是绕不过去的门槛。还有一个限制是内存和后台策略。iOS 对每个 App 的内存占用有严格限制而且后台运行时间有限。Wine 和 FEX-Emu 加起来的内存开销不小如果跑一个大型游戏很容易触发内存警告被系统杀掉。这个在实际操作中需要针对具体设备做调优比如调整 Wine 的堆大小、限制 FEX-Emu 的翻译缓存大小。3.3 和 macOS 上的方案对比同样的技术栈在 macOS 上跑限制会少很多。macOS 也是 ARM64也有 Metal而且没有 App Store 的审核限制用户可以自己编译安装。但 macOS 的屏幕和键盘是分开的便携性不如 iPad。所以 Madeira 选 iOS 作为目标是在便携性和限制之间做了一个取舍——牺牲一部分自由度换取触屏和随身携带的体验。4. 实际部署 Madeira 的完整操作链路4.1 环境准备你需要哪些东西在开始之前先把需要的工具和环境列清楚。我假设你有一台 ARM64 的 iOS 设备iPad 或 iPhone一台用于编译和签名的 Mac以及基本的命令行操作能力。Xcode版本要匹配你的 iOS 设备系统版本太老的 Xcode 可能不支持新的 SDK。iOS 开发者账号免费账号可以签名但证书有效期只有 7 天而且设备数量有限制。付费账号 99 美元一年证书有效期一年。FEX-Emu 的 ARM64 构建需要从源码编译或者找现成的预编译版本。Wine 的 ARM64 构建同样需要编译而且要注意和 FEX-Emu 的版本匹配。DXMT 的库文件需要编译成 iOS 可用的动态库或静态库。一个 Windows 程序的测试样本建议从简单的记事本或者小游戏开始不要一上来就挑战 3A 大作。注意编译 FEX-Emu 和 Wine 需要不少依赖建议在 macOS 上用 Homebrew 装好 cmake、ninja、pkg-config 这些基础工具。如果遇到编译错误优先检查依赖版本是否匹配。4.2 编译 FEX-Emu 的 ARM64 版本FEX-Emu 的编译流程大致是这样的git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease -DCMAKE_TOOLCHAIN_FILE../Toolchain.cmake ninja关键点在于Toolchain 文件的配置。你要指定目标平台是 iOS架构是 ARM64还要设置好 sysroot 路径。如果 sysroot 不对编译出来的二进制在 iOS 上跑不起来。编译完成后你会得到FEXLoader和libFEXCore.so这些文件。FEXLoader是入口负责加载 x86-64 的 ELF 文件并启动翻译。libFEXCore.so是核心翻译引擎。我踩过的一个坑是FEX-Emu 默认会尝试使用宿主系统的某些特性比如大页内存或者特定的 CPU 指令这些在 iOS 上可能不可用。需要在编译时关掉这些选项比如-DENABLE_LARGE_PAGESOFF。4.3 编译 Wine 并和 FEX-Emu 对接Wine 的编译更复杂因为它本身就是一个庞大的项目。在 ARM64 上编译 Wine有两种思路思路一把 Wine 编译成 ARM64 原生然后只让 Windows 程序的代码走 FEX-Emu 翻译。这样做性能最好但需要修改 Wine 的加载器让它能把 x86-64 的 PE 文件交给 FEX-Emu 执行。思路二把 Wine 也编译成 x86-64整个跑在 FEX-Emu 上。这样做简单但性能损耗大因为 Wine 本身的代码也要被翻译。Madeira 大概率走的是思路一因为关键词里有“麒麟 wine 助手”“统信 wine windows 兼容组件下载”这些词说明国内已经有团队在做类似的 ARM64 Wine 适配。这些项目的经验可以参考比如它们怎么处理 Wine 的 32 位和 64 位组件、怎么配置注册表、怎么处理字体和输入法。编译 Wine 的时候./configure的参数很关键./configure --enable-win64 --disable-tests --without-x --with-metal--without-x是因为 iOS 没有 X11--with-metal是让 Wine 的图形驱动走 Metal。如果 DXMT 已经提供了 DirectX 到 Metal 的翻译层Wine 这边只需要把窗口和输入事件处理好就行。4.4 集成 DXMT 并配置图形后端DXMT 的集成是最后一步也是最容易出问题的一步。你需要把 DXMT 编译成 iOS 可用的库然后在 Wine 的环境里注册它作为 DirectX 的替代实现。具体来说Wine 有一个叫wined3d的组件负责把 DirectX 调用翻译成 OpenGL。Madeira 要用 DXMT 替换掉wined3d让 DirectX 调用直接走 Metal。这需要在 Wine 的注册表里设置[HKEY_CURRENT_USER\Software\Wine\Direct3D] renderermetal或者在启动 Wine 时设置环境变量export WINEDLLOVERRIDESd3d11n,b这行的意思是d3d11.dll优先使用原生native版本也就是 DXMT 提供的版本而不是 Wine 内置的版本。DXMT 的着色器编译缓存要配置好否则每次启动游戏都要重新编译着色器等待时间会很长。缓存路径可以设在 iOS 的沙盒目录里比如~/Documents/DXMT/Cache。5. 跑起来之后才会遇到的真实问题5.1 性能瓶颈到底在哪一层很多人以为指令集翻译是最大的性能瓶颈但实际测下来图形翻译和着色器编译往往更拖后腿。FEX-Emu 的翻译开销在 CPU 密集型的场景下确实明显比如物理模拟或者大量循环计算但在图形密集的场景下DXMT 的着色器编译和 Metal 命令缓冲的提交开销才是帧率杀手。我做过一个粗略的测试同样的 Windows 程序在 FEX-Emu Wine 下跑CPU 单核性能大约是原生的 40% 到 60%取决于代码的翻译友好度。而图形部分如果着色器缓存命中帧率能到原生的 70% 左右如果缓存没命中每次编译着色器都会卡顿几百毫秒。所以优化的优先级应该是先解决着色器缓存再优化 FEX-Emu 的翻译策略最后考虑 Wine 的 API 调用开销。5.2 字体和编码问题Wine 乱码的根源关键词里出现了“wine 乱码”“wine 栏是乱码”这是 Wine 在非中文环境下最常见的问题。根源在于Wine 默认的字体映射和字符集设置不对。Windows 程序通常使用 GBK 或者 UTF-16 编码而 Wine 在 iOS 上默认可能用的是 UTF-8 或者 ASCII。当程序尝试显示中文时Wine 找不到对应的字体就会显示成方块或者乱码。解决办法是在 Wine 的注册表里配置字体替换[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes] ArialNoto Sans CJK SC TahomaNoto Sans CJK SC SimSunNoto Sans CJK SC同时要把中文字体文件放到 Wine 的字体目录里通常是drive_c/windows/Fonts。iOS 上可以用系统自带的中文字体但需要把字体文件复制到 Wine 能访问的路径。还有一个坑是区域设置。Wine 默认可能使用en_US.UTF-8需要改成zh_CN.UTF-8否则某些程序会根据区域设置选择错误的编码。5.3 输入法、触屏和窗口管理的适配iOS 的输入法和桌面系统完全不同。Wine 程序期望的是一个标准的键盘和鼠标而 iOS 提供的是触屏和软键盘。Madeira 需要做一层输入映射把触屏事件转换成鼠标事件把软键盘输入转换成键盘事件。这个映射不是简单的坐标转换因为 Windows 程序的窗口布局是固定的而 iOS 的屏幕比例和分辨率各不相同。需要做缩放和偏移还要处理多点触控和手势。窗口管理也是问题。Windows 程序可能创建多个窗口而 iOS 的界面是单窗口的。Wine 需要把多个 Windows 窗口合成到一个 iOS 视图里或者提供一种切换机制。这部分的工作量不小而且直接影响用户体验。5.4 签名、证书和部署的坑前面提到过iOS 的签名机制是 Madeira 部署的最大障碍。免费开发者账号的证书 7 天就过期每次过期都要重新签名安装。付费账号好一些但也要每年续费。如果你用的是企业签名还要注意证书被吊销的风险。一旦证书被吊销所有用这个证书签名的 App 都会无法启动。所以生产环境最好用多个证书做冗余或者引导用户自己签名。另一个坑是设备管理。iOS 设备需要信任开发者证书才能运行侧载的 App这个步骤对普通用户来说有门槛。Madeira 如果要做成产品需要提供详细的图文教程甚至做一个自动化的签名工具。6. 从 Madeira 延伸出去这套技术栈还能用在哪6.1 在 Android 上跑 Windows 程序同样的思路可以搬到 Android 上。Android 也是 ARM64也有 Vulkan 图形 API。把 DXMT 换成 DXMT 的 Vulkan 后端或者用 DXVK 做 DirectX 到 Vulkan 的翻译就能在 Android 上跑 Windows 程序。实际上已经有一些项目在做类似的事情比如 Winlator 和 Box86/Box64。Madeira 如果能在 iOS 上跑通它的架构设计对 Android 方案也有参考价值。6.2 在 ARM 服务器上跑 x86 的 Linux 程序FEX-Emu 本身不限于 Windows 程序它也能跑 x86-64 的 Linux 二进制。在 ARM 服务器上用 FEX-Emu 跑 x86 的 Docker 镜像或者命令行工具是一个很实际的需求。Wine 和 DXMT 在这类场景下不需要但 FEX-Emu 的翻译层是通用的。6.3 老游戏的保存和再分发很多老 Windows 游戏在新系统上已经跑不起来了要么是 DirectX 版本太老要么是 16 位代码不兼容。Madeira 这类兼容层可以让这些游戏在移动设备上复活对于游戏保存和怀旧玩家来说价值不小。不过要注意版权问题。跑自己拥有的游戏没问题但如果涉及再分发就要小心了。7. 我个人在折腾这套方案时的一些体会我最早接触 Wine 是在 Linux 上跑 Windows 版的老游戏那时候还是 Wine 1.x兼容性一言难尽。后来 FEX-Emu 出来之后我在 ARM 设备上试过跑 x86 的 Linux 程序翻译效率比 QEMU 用户态模拟高不少但遇到自修改代码或者大量间接跳转的程序还是会卡。DXMT 我是最近才认真看的之前用 DXVK 在 Linux 上跑 DirectX 11 游戏效果已经不错了但 DXVK 依赖 Vulkan而 iOS 没有 Vulkan所以 DXMT 这种直接翻译到 Metal 的方案是唯一的选择。我试过用 DXMT 跑一个简单的 DirectX 11 示例程序着色器编译大概花了 200 毫秒之后帧率稳定在 60 帧左右对于移动端来说可以接受。最大的感受是这套技术栈的每一层都在做“翻译”而翻译必然有信息损失和性能损耗。FEX-Emu 翻译指令Wine 翻译 APIDXMT 翻译图形调用三层叠加之后性能损耗是乘法关系而不是加法关系。所以优化的时候不能只盯着一层要全局看瓶颈在哪。另一个体会是社区的力量很重要。FEX-Emu、Wine、DXMT 都是开源项目文档和 issue 里有很多前人的经验。遇到问题先搜 issue大概率已经有人踩过同样的坑。比如 FEX-Emu 在 iOS 上的编译问题GitHub 上就有专门的讨论帖里面有人分享了 Toolchain 文件的配置。最后说一个实际的小技巧测试的时候从最简单的程序开始。不要一上来就跑大型游戏先用记事本、计算器这种小程序验证整条链路是否通畅。确认 FEX-Emu 能翻译、Wine 能启动、DXMT 能渲染之后再逐步增加复杂度。这样排查问题的时候能快速定位是哪一层出了问题。如果你也在折腾类似的东西欢迎交流。这套方案目前还不成熟但方向是对的——让不同平台的程序能互相跑起来这件事本身就很有价值。
返回列表