1. 从“Madeira”这个名字说起:它到底想解决什么问题
第一次看到“Madeira”这个项目名,很多人会以为是某个葡萄酒品牌或者旅游项目,但结合 Wine、FEX-Emu、DXMT、iOS、x86-64 这几个关键词,方向就很清楚了——这是一个围绕在非 x86 平台(尤其是 ARM 架构的移动设备)上运行 Windows 应用与游戏的兼容层整合项目。Madeira 的核心目标,是把 Wine 的 Windows API 翻译能力、FEX-Emu 的 x86-64 指令翻译能力、DXMT 的 Direct3D 到 Metal 图形翻译能力,打包成一套能在 iOS/iPadOS 设备上跑起来的东西。
说白了,它想做的事情是:让你手里那台 ARM 架构的 iPhone 或 iPad,能够运行原本只给 Windows x86-64 电脑写的程序。这件事听起来很疯狂,但拆开来看,每一层都有成熟的技术在支撑。Wine 负责把 Windows 的系统调用翻译成 POSIX 调用,FEX-Emu 负责把 x86-64 的机器指令翻译成 ARM64 指令,DXMT 负责把 DirectX 的图形调用翻译成 Metal 的图形调用。三者叠加,理论上就能让一个 Windows 的 exe 文件在 iOS 上跑起来。
这个项目适合谁来研究?我认为有三类人值得关注。第一类是移动端模拟器与兼容层的开发者,他们关心的是指令翻译效率、图形 API 映射的完整性、内存管理的边界问题。第二类是iOS 越狱与侧载生态的折腾党,他们关心的是怎么在设备上实际部署这套东西,需要哪些权限,签名怎么处理。第三类是跨平台游戏与应用的移植工程师,他们关心的是这套方案能不能作为一条低成本的移植路径,替代传统的重写或引擎移植。
需要提前说明的是,Madeira 目前并不是一个开箱即用的成品,它更像是一个技术整合的实验性项目。你不可能下载一个安装包就双击运行,它涉及到多个组件的编译、配置、签名和调试。这篇文章会从架构设计、核心组件、实操部署、问题排查几个维度,把这套东西讲清楚,让有能力动手的人知道从哪里开始,让暂时不动手的人也能理解其中的技术逻辑。
2. 整体架构拆解:三层翻译是怎么叠起来的
2.1 为什么需要三层翻译,而不是一层搞定
要理解 Madeira 的架构,先要理解一个基本事实:Windows 程序和 iOS 系统之间,隔着的不是一道墙,而是三道墙。
第一道墙是指令集。Windows 程序编译出来的是 x86-64 机器码,而 iOS 设备跑的是 ARM64 指令。这两套指令集完全不兼容,就像一个人说中文,另一个人说阿拉伯语,不是口音问题,是语言体系问题。FEX-Emu 就是翻译这层语言的。
第二道墙是操作系统 API。Windows 程序调用的是 kernel32.dll、user32.dll、ntdll.dll 这些系统库,而 iOS 提供的是 Darwin 内核的 POSIX 接口和 Cocoa 框架。Wine 就是做这层映射的,它把 Windows 的 API 调用翻译成 POSIX 调用。
第三道墙是图形 API。Windows 游戏大量使用 Direct3D 9/10/11/12 来渲染画面,而 iOS 只认 Metal。DXMT 就是把这层图形调用翻译成 Metal 的。
这三层缺一不可。你只做指令翻译,Windows 程序跑起来也找不到系统库;你只做 API 映射,x86 指令在 ARM 上根本执行不了;你只做图形翻译,前两层不通,画面也出不来。所以 Madeira 的本质是一个三层翻译栈的整合工程,难点不在于每一层单独实现,而在于三层之间的协调和性能优化。
2.2 FEX-Emu 在其中的角色与性能账
FEX-Emu 是一个用户态的 x86-64 到 ARM64 的二进制翻译器。它的工作方式是:读取 x86-64 的指令块,翻译成 ARM64 指令块,缓存起来,下次遇到同样的指令块就直接用缓存。这种块级翻译加缓存的策略,比逐条指令翻译效率高得多。
但这里有一个性能账要算。指令翻译本身是有开销的,尤其是第一次遇到某个代码块的时候,需要做完整的解码、优化、编码。根据公开的测试数据,FEX-Emu 在 ARM 设备上运行 x86-64 程序的性能损耗,通常在 30% 到 60% 之间,具体取决于程序的指令特征。整数运算密集的程序损耗小一些,浮点运算和 SIMD 指令密集的程序损耗大一些。
在 iOS 设备上,这个损耗还要叠加一层。因为 iOS 对可执行内存的管理非常严格,JIT(即时编译)权限不是随便就能拿到的。FEX-Emu 需要把翻译后的 ARM64 代码写到可执行内存里,这就涉及到 iOS 的代码签名和内存保护机制。在越狱设备上,这个问题相对好解决;在非越狱设备上,就需要借助开发者模式或者特定的签名技巧,这也是为什么热词里出现了“iOS 开发者模式”和“iOS 自动化”这些词。
2.3 DXMT 的图形翻译路径
DXMT 的全称是 DirectX Metal Translation,它的目标是把 Direct3D 的调用翻译成 Metal 的调用。这条路和 DXVK 把 D3D 翻译成 Vulkan 的思路类似,但目标 API 不同。
DXMT 的工作流程大致是这样的:Windows 程序调用 D3D11 的接口创建资源、设置管线、提交绘制命令;DXMT 拦截这些调用,转换成 Metal 的对应操作;Metal 驱动 GPU 执行渲染;渲染结果再通过一层合成,显示到 iOS 的窗口系统上。
这里面的技术难点有几个。第一是着色器翻译,D3D 的 HLSL 着色器需要先编译成 DXBC 字节码,再翻译成 Metal 的 AIR 或者 Metallib。这个翻译过程涉及到语义映射、资源绑定模型转换、常量缓冲区布局调整等细节。第二是资源管理,D3D 的纹理和缓冲区管理策略和 Metal 不一样,需要做一层抽象来桥接。第三是同步机制,D3D 的围栏和 Metal 的 event 语义有差异,需要仔细处理才能避免画面撕裂或者卡顿。
2.4 Wine 在 iOS 上的特殊挑战
Wine 在桌面 Linux 和 macOS 上已经相当成熟,但搬到 iOS 上会遇到一些特殊问题。
首先是文件系统。iOS 的应用沙盒机制限制了文件访问范围,Wine 需要模拟 Windows 的 C 盘、注册表、用户目录等结构,这些都需要在沙盒内重新组织。其次是进程管理,iOS 对后台进程和 fork 的限制比桌面系统严格得多,Wine 的某些多进程模型需要调整。第三是图形窗口系统,Windows 的窗口管理 API 需要映射到 iOS 的 UIView 层级上,这中间涉及到事件传递、坐标转换、焦点管理等细节。
热词里出现的“wine 乱码”和“wine 栏是乱码”,大概率就是字符编码或者字体映射的问题。Windows 程序默认使用 GBK 或者 UTF-16 编码,而 iOS 的字体系统对某些字符集的支持不完整,导致菜单栏或者对话框显示乱码。这个问题通常需要通过配置 Wine 的字体替换或者安装额外的字体包来解决。
3. 核心组件实操:从编译到部署的关键步骤
3.1 环境准备与工具链配置
在 iOS 上折腾 Madeira,第一步不是直接编译,而是把工具链准备好。你需要一台 macOS 机器作为构建主机,Xcode 是必须的,版本建议用较新的稳定版。另外需要安装 Homebrew,用来管理编译依赖。
编译 FEX-Emu 需要 CMake、Ninja、Clang 等工具。FEX-Emu 官方推荐用 Clang 编译,因为它的代码里有一些依赖 Clang 特性的部分。DXMT 的编译依赖 Meson 和 Ninja,还需要 Metal 的着色器编译器。Wine 的编译最复杂,需要 flex、bison、pkg-config 等一堆工具。
# 安装基础工具链 brew install cmake ninja meson flex bison pkg-config # 确认 Xcode 命令行工具已配置 xcode-select --install sudo xcode-select -s /Applications/Xcode.app/Contents/Developer这里有一个实操心得:编译顺序很重要。建议先编译 FEX-Emu,再编译 Wine,最后编译 DXMT。因为 Wine 在编译过程中可能需要链接 FEX-Emu 的某些库,而 DXMT 又依赖 Wine 的头文件。如果顺序搞反了,会遇到找不到符号或者头文件路径错误的问题。
注意:iOS 的编译目标和 macOS 不同,需要指定
-arch arm64 -mios-version-min=14.0这样的编译参数。如果你直接按桌面 Linux 的方式编译,出来的二进制在 iOS 上跑不了。
3.2 FEX-Emu 的编译与 RootFS 准备
FEX-Emu 的编译相对直接,但有一个关键点:它需要一个 RootFS(根文件系统)来提供 x86-64 的基础库。这个 RootFS 通常是一个精简的 Linux 文件系统,里面包含 libc、libm、libpthread 等基础库的 x86-64 版本。
# 克隆 FEX-Emu 仓库 git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive # 创建构建目录 mkdir build && cd build # 配置 CMake,指定目标为 ARM64 cmake .. -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_TOOLCHAIN_FILE=../Data/CMake/toolchain_ios.cmake # 编译 ninjaRootFS 的准备有两种方式。一种是使用 FEX-Emu 官方提供的 RootFS 脚本,它会下载一个精简的 Ubuntu 或者 Debian 文件系统,然后提取需要的库。另一种是手动从 Docker 镜像里提取,这种方式更灵活,但步骤多一些。
提示:RootFS 的体积直接影响最终包的体积。如果你只打算跑特定的几个程序,可以裁剪掉不需要的库和字体,把 RootFS 控制在 200MB 以内。
3.3 Wine 的交叉编译与配置
Wine 的交叉编译是整套流程里最耗时的部分。你需要先配置 Wine 的构建系统,指定目标平台为 iOS,然后编译出 ARM64 版本的 Wine 库和可执行文件。
# 克隆 Wine 源码 git clone https://github.com/wine-mirror/wine.git cd wine # 配置构建,指定交叉编译工具链 ./configure --host=aarch64-apple-darwin \ --with-wine-tools=../wine-tools \ --disable-tests \ --without-x \ --without-alsa \ --without-pulse # 编译 make -j$(sysctl -n hw.ncpu)编译完成后,你需要配置 Wine 的 prefix 目录。这个目录模拟了 Windows 的 C 盘结构,包含 system32、Program Files、用户目录等。在 iOS 上,这个 prefix 需要放在应用沙盒的可写目录里。
Wine 的配置文件user.reg和system.reg需要针对 iOS 做调整。比如字体替换、DLL 覆盖、图形驱动选择等。热词里提到的“wine 乱码”问题,通常就是在system.reg里配置字体替换来解决的。
[Software\\Wine\\Fonts\\Replacements] "MS Shell Dlg"="Arial" "MS Shell Dlg 2"="Arial" "Tahoma"="Arial"3.4 DXMT 的集成与 Metal 着色器编译
DXMT 的编译需要先确保 Metal 工具链可用。在 macOS 上,Metal 编译器是随 Xcode 一起安装的,你可以用xcrun metal来编译 .metal 文件。
# 克隆 DXMT 仓库 git clone https://github.com/3Shain/dxmt.git cd dxmt # 使用 Meson 配置构建 meson setup build --cross-file cross-ios.txt # 编译 ninja -C buildDXMT 编译完成后,会生成一个 d3d11.dll 和 d3d12.dll 的替代品,以及一个 Metal 后端库。这些文件需要放到 Wine 的 system32 目录里,并在 Wine 的 DLL 覆盖配置里指定使用 DXMT 而不是 Wine 自带的 D3D 实现。
Metal 着色器的编译是另一个关键点。DXMT 在运行时会动态翻译 D3D 着色器,但有些预编译的着色器可以在构建时提前编译好,减少运行时的开销。这个优化对于移动设备来说很重要,因为 iOS 设备的 GPU 驱动对运行时着色器编译的支持有限。
3.5 iOS 应用打包与签名
把编译好的 FEX-Emu、Wine、DXMT 打包成一个 iOS 应用,需要创建一个 Xcode 工程,把这些二进制文件和资源文件嵌入进去。这个工程的主要工作是:启动时初始化 FEX-Emu 的翻译环境,加载 Wine 的 prefix,启动 Wine 的 loader,然后加载目标 Windows 程序。
签名是这一步的难点。iOS 要求所有可执行代码都必须签名,而 FEX-Emu 在运行时会产生新的可执行代码(翻译后的 ARM64 代码),这些代码也需要签名或者绕过签名检查。在越狱设备上,可以用ldid或者codesign工具做伪签名;在非越狱设备上,需要利用开发者模式或者特定的 entitlement 来获得 JIT 权限。
# 对二进制文件做伪签名(越狱设备) ldid -S entitlements.plist FEXLoader ldid -S entitlements.plist wine ldid -S entitlements.plist dxmt.dylib注意:非越狱设备上的 JIT 权限获取是一个灰色地带,不同 iOS 版本的限制不同。iOS 14 到 iOS 16 相对宽松一些,iOS 17 之后收紧了。如果你打算在非越狱设备上折腾,建议先确认你的 iOS 版本是否支持。
4. 常见问题与排查技巧实录
4.1 Wine 乱码问题的三种成因与解法
“wine 乱码”是热词里出现频率最高的问题之一。根据我的经验,乱码通常有三种成因,需要分别处理。
第一种是字体缺失。Wine 默认使用的字体在 iOS 上可能不存在,导致文字显示为方块或者问号。解法是在 Wine 的 prefix 里安装一套完整的字体,比如文泉驿或者 Noto 系列,然后在注册表里配置字体替换。
第二种是编码不匹配。某些 Windows 程序使用 GBK 编码输出文本,而 Wine 在 iOS 上默认使用 UTF-8,导致中文显示乱码。解法是在 Wine 的 locale 配置里指定正确的编码,或者用LANG=zh_CN.GBK这样的环境变量启动。
第三种是渲染后端问题。Wine 的文字渲染依赖于图形后端,如果 DXMT 或者 Metal 后端的文字渲染路径有问题,也会导致乱码。这种情况的解法是切换 Wine 的渲染后端,比如从 DXMT 切到 Wine 自带的 GDI 渲染,看看是否恢复正常。
| 乱码类型 | 典型表现 | 排查方法 | 解决方案 |
|---|---|---|---|
| 字体缺失 | 全部显示方块 | 检查 prefix 字体目录 | 安装 Noto 或文泉驿字体 |
| 编码不匹配 | 中文显示为问号 | 检查 locale 设置 | 设置 LANG 环境变量 |
| 渲染后端 | 部分文字异常 | 切换渲染后端测试 | 改用 GDI 或重新编译 DXMT |
4.2 FEX-Emu 性能调优的几个关键参数
FEX-Emu 的性能调优空间比较大,几个关键参数值得关注。
块缓存大小:FEX-Emu 会缓存翻译后的代码块,缓存越大,重复执行的代码命中率越高。在 iOS 设备上,内存有限,缓存不能设太大,建议根据设备内存调整,一般 64MB 到 128MB 比较合适。
多线程翻译:FEX-Emu 支持多线程翻译,可以加快首次执行的翻译速度。但 iOS 对线程数量的限制比较严格,开太多线程反而会导致调度开销增加。建议设置为 2 到 4 个线程。
SIMD 优化:FEX-Emu 对 SSE 和 AVX 指令的翻译有不同的优化级别。如果你的目标程序大量使用 SIMD 指令,可以开启更激进的优化选项,但会增加翻译时间和代码体积。
# FEX-Emu 环境变量配置示例 export FEX_TSOENABLED=1 export FEX_BLOCKCACHE_SIZE=134217728 export FEX_MULTIBLOCK=1 export FEX_SMCCHECKS=14.3 DXMT 图形问题的排查思路
DXMT 的图形问题通常表现为黑屏、花屏、闪退或者性能极低。排查思路可以按以下顺序进行。
首先确认Metal 设备是否可用。在 iOS 上,Metal 是系统级框架,正常情况下都应该可用,但如果你的应用没有正确链接 Metal 框架,或者 entitlement 配置不对,就会导致 Metal 设备创建失败。
然后检查着色器翻译日志。DXMT 在翻译着色器时会输出日志,如果某个着色器翻译失败,日志里会有明确的错误信息。常见的失败原因包括:使用了 DXMT 不支持的 HLSL 特性、资源绑定数量超出 Metal 限制、常量缓冲区布局不匹配等。
最后看GPU 帧捕获。用 Xcode 的 GPU Frame Capture 工具可以抓取一帧的渲染过程,看到底是哪个绘制调用出了问题。这个工具对于定位花屏和黑屏问题非常有用。
4.4 iOS 部署中的签名与权限问题
iOS 部署中最常见的问题就是签名和权限。具体表现包括:应用安装后闪退、JIT 权限被拒绝、动态库加载失败等。
闪退问题通常是因为签名不完整或者 entitlement 配置错误。你可以用codesign -dv --verbose=4查看签名信息,确认所有可执行文件都签了名,并且 entitlement 里包含了必要的权限。
JIT 权限被拒绝的表现是 FEX-Emu 在翻译代码时崩溃,日志里会有mprotect failed或者mmap failed这样的错误。解法取决于你的设备状态:越狱设备可以用ldid加上dynamic-codesigningentitlement;非越狱设备需要利用开发者模式的 JIT 权限,或者使用特定的签名服务。
动态库加载失败通常是因为DYLD_LIBRARY_PATH没有正确设置,或者动态库的安装路径不对。在 iOS 上,动态库需要放在应用的 Frameworks 目录里,并且用@rpath来引用。
提示:如果你在非越狱设备上折腾,建议先用一个简单的 Windows 控制台程序做测试,确认 FEX-Emu 和 Wine 的基本链路通了,再去跑图形程序。这样可以把问题范围缩小,避免一上来就面对一堆图形相关的报错。
5. 这套方案的实际价值与适用边界
5.1 能跑什么,不能跑什么
Madeira 这套方案的能力边界,取决于三层翻译的完整性。从目前的实践来看,简单的 Windows 控制台程序和轻量级 Win32 应用跑起来问题不大,比如一些老的计算工具、文本编辑器、小游戏。中等复杂度的 D3D9 和 D3D11 游戏有可能跑起来,但性能损耗明显,帧率可能只有原生的三到五成。重度依赖 D3D12 或者需要特定硬件特性的游戏,目前基本跑不动,因为 DXMT 对 D3D12 的支持还不完整,而且 iOS 的 GPU 特性集和桌面 GPU 有差异。
另外,依赖 .NET 或者 Java 运行时的程序会更复杂,因为还需要额外翻译 .NET 或者 JVM 的运行时。依赖特定 Windows 驱动或者内核模块的程序基本没戏,因为 Wine 不模拟内核驱动。
5.2 和传统移植方案的对比
传统的 iOS 游戏移植方案,通常是用 Unity 或者 Unreal 重新打包,或者用引擎自带的跨平台导出功能。这种方案的优势是性能好、兼容性有保障,劣势是需要源代码和引擎支持,对于没有源码的老程序无能为力。
Madeira 这套方案的优势是不需要源码,理论上任何 Windows 程序都可以尝试跑起来。劣势是性能损耗大、兼容性问题多、部署流程复杂。它更适合作为最后的手段,而不是首选的移植方案。
| 对比维度 | Madeira 兼容层方案 | 传统引擎移植方案 |
|---|---|---|
| 源码需求 | 不需要 | 需要 |
| 性能损耗 | 30% 到 70% | 通常低于 10% |
| 兼容性 | 取决于翻译完整度 | 有保障 |
| 部署复杂度 | 高 | 中 |
| 适用场景 | 无源码的老程序 | 有源码的新项目 |
5.3 后续可以扩展的方向
如果你已经跑通了基本的链路,后续有几个方向可以继续折腾。一是性能优化,比如针对特定游戏做 FEX-Emu 的翻译缓存预热,或者针对 DXMT 做着色器预编译。二是兼容性扩展,比如增加对更多 D3D 特性的支持,或者补充 Wine 的 DLL 实现。三是部署简化,比如做一个自动化的打包脚本,把编译、签名、安装的流程串起来。
我个人在实际操作中的体会是,这套东西的技术门槛主要在编译和调试环节,真正跑起来之后,性能调优反而是更有意思的部分。因为每一层翻译都有优化空间,你可以通过调整参数、替换组件、修改配置,一点点把帧率往上推。这个过程很像早年折腾 Linux 游戏兼容层的感觉,需要耐心,但每次突破都会带来实实在在的成就感。
最后分享一个小技巧:如果你在 iOS 上跑 Windows 程序时遇到莫名其妙的崩溃,可以先在 macOS 上用同样的 Wine 和 DXMT 配置跑一遍。如果 macOS 上正常,那问题大概率出在 iOS 的沙盒或者签名环节;如果 macOS 上也崩,那就是 Wine 或者 DXMT 的兼容性问题。这个对照测试可以帮你快速定位问题所在的层级,省去很多盲目排查的时间。