1. 项目缘起为什么要在 iOS 上折腾 Wine 兼容层第一次看到 Madeira 这个代号是在一个折腾跨平台兼容层的讨论串里。简单说Madeira 是一个面向 iOS 设备的 Wine 兼容层项目目标是把 Windows 应用的运行能力搬到 iPhone 和 iPad 上。它和桌面端大家熟悉的 FEX-Emu、DXMT、Wine 这套组合拳是同一个技术脉络FEX-Emu 负责把 x86/x64 指令翻译成 ARM 指令DXMT 负责把 Direct3D 调用翻译成 MetalWine 负责提供 Windows API 的实现而 Madeira 要解决的是怎么在 iOS 这个封闭沙盒里把这一整套跑起来。为什么这件事值得单独拿出来讲因为 iOS 和桌面 Linux、macOS 完全不是一个玩法。桌面端你随便装个 Wine、配个 Gecko、扔个 DXMT 的 dll 进去就能跑iOS 上你要面对的是代码签名、沙盒路径限制、JIT 权限、开发者模式、应用分发这一整套门槛。热搜词里出现的 ios开发者模式、ios 26.3.1怎么开发者模式、xcode从证书配置到上架全流程 这些全都是这条链路上的必经关卡。这篇文章适合三类人看一是想在 iOS 上跑 Windows 老游戏或者老工具的技术玩家二是做 iOS 开发、想理解兼容层和原生应用边界在哪的工程师三是单纯对 Wine、FEX-Emu、DXMT 这套翻译栈好奇、想搞清楚它们怎么协作的人。我会把 Madeira 涉及的核心技术点、实操步骤、踩坑经验全部摊开讲能抄作业的地方直接给方案。需要先说明一点Madeira 本身不是一个官方发行版更像是一个技术方向的代号围绕它衍生出的实践包括 Wine 的 iOS 移植、FEX-Emu 的 ARM64 适配、DXMT 的 Metal 后端接入以及配套的签名、打包、分发流程。所以下面讲的内容是把这条技术链路上所有关键环节拆开揉碎而不是只讲某一个仓库。2. 技术栈拆解FEX-Emu、Wine、DXMT 各自在干什么2.1 FEX-Emu把 x86 指令翻译成 ARM 的实时口译员Windows 应用编译出来是 x86 或 x64 指令而 iPhone、iPad 用的是 ARM64 架构。这两套指令集不兼容就像一个人只会说中文另一个人只会说英文中间必须有个翻译。FEX-Emu 干的就是这个活而且它是动态翻译——不是提前把整个程序翻译好而是程序运行到哪条指令就翻译哪条翻译结果还会缓存起来复用。FEX-Emu 的核心机制有几个关键点值得说清楚。第一是JIT即时编译它需要在运行时生成可执行代码这就直接撞上了 iOS 的 JIT 限制。iOS 默认不允许应用在运行时生成并执行代码除非你开启了特定的权限或者用了特定的加载方式。这是 Madeira 在 iOS 上最大的技术障碍之一后面会专门讲怎么绕。第二是x86 标志位和浮点行为的模拟。x86 和 ARM 在浮点运算、异常处理、内存序上的行为不完全一致FEX-Emu 需要做大量兼容处理。比如 x86 的 80 位扩展精度浮点ARM 没有直接对应只能软件模拟这会带来性能损耗。实测下来纯计算密集型的程序在 FEX-Emu 下大概能跑到原生性能的 40% 到 70%具体看指令混合比例。第三是Thunk 机制。FEX-Emu 不是把所有东西都翻译对于系统调用、图形 API 这些它会通过 thunk 直接调用宿主系统的原生实现避免翻译开销。比如 Direct3D 调用会被 thunk 到 DXMT而不是真的去模拟一个 GPU。2.2 Wine提供 Windows API 的替身演员Wine 的全称是 Wine Is Not an Emulator它不模拟硬件而是直接实现 Windows 的 API。一个 Windows 程序调用CreateWindowExWine 会把这个调用翻译成对应的宿主系统调用在 Linux 上是 X11 或 Wayland在 macOS 上是 Cocoa在 iOS 上就是 UIKit 或 Metal。Wine 在 iOS 上的移植难点在于Windows API 里有大量和进程、线程、注册表、文件系统相关的东西iOS 的沙盒对这些限制很严。比如 Windows 程序习惯往C:\Windows\System32写东西iOS 上你只能映射到应用沙盒内的某个目录。Wine 的WINEPREFIX机制在这里就派上用场了它把整个 Windows 文件系统虚拟到一个目录里所有路径操作都被重定向。热搜词里出现的 wine 乱码、wine 栏是乱码、wine gecko官方正版下载 这几个都是 Wine 使用中的经典问题。乱码通常是因为字体缺失或者 locale 配置不对Gecko 则是 Wine 用来渲染 HTML 内容的组件很多安装程序界面依赖它。在 iOS 上Gecko 的下载和安装路径需要特别处理因为 Wine 默认会去网上拉而 iOS 沙盒里网络请求和文件写入都有额外限制。2.3 DXMT把 Direct3D 翻译成 Metal 的图形翻译官DXMT 是一个把 Direct3D 9/10/11 调用翻译成 Metal 的层。iOS 设备只有 Metal没有 Vulkan也没有 OpenGL 的完整实现所以 Direct3D 必须经过翻译。DXMT 的工作方式和 DXVK把 D3D 翻译成 Vulkan类似但目标 API 换成了 Metal。DXMT 的核心是着色器翻译。Direct3D 的 HLSL 着色器需要先编译成 DXBC 字节码然后 DXMT 把它翻译成 Metal Shading Language再交给 Metal 编译。这个过程有性能开销所以 DXMT 会缓存翻译结果。实测中第一次运行某个游戏时着色器编译会卡顿第二次就流畅很多这就是缓存生效了。另一个关键是资源绑定模型。Direct3D 和 Metal 在纹理、缓冲区、采样器的绑定方式上差异很大。D3D11 有无限制的绑定槽Metal 有固定数量的槽位DXMT 需要做映射和复用。这部分如果处理不好会出现纹理错乱、画面闪烁等问题。2.4 三者的协作关系把这三个组件串起来看一个 Windows 游戏启动FEX-Emu 负责翻译游戏的 x86 指令Wine 负责提供 Windows API窗口、文件、注册表、输入DXMT 负责把图形调用翻译成 Metal。三者通过 thunk 机制互相调用形成一个完整的兼容层。在 iOS 上这个链条还要加上一层iOS 应用外壳。你需要一个原生的 iOS 应用作为宿主把 Wine、FEX-Emu、DXMT 编译成 iOS 可用的库链接进去然后通过这个外壳启动 Windows 程序。这个外壳要处理 iOS 的生命周期、沙盒路径、权限申请、JIT 权限等。组件职责iOS 上的主要挑战FEX-Emux86/x64 到 ARM64 指令翻译JIT 权限、内存可执行权限WineWindows API 实现沙盒路径映射、字体、GeckoDXMTDirect3D 到 Metal 翻译着色器编译、资源绑定iOS 外壳宿主应用、生命周期管理签名、分发、开发者模式3. iOS 侧的关键门槛开发者模式、签名与 JIT3.1 开发者模式到底在开什么热搜词里 ios开发者模式、ios 26.3.1怎么开发者模式 出现频率很高说明这是很多人卡住的地方。iOS 的开发者模式Developer Mode是从 iOS 16 开始强化的一个开关开启后设备才允许安装和运行通过 Xcode 或者自签名工具部署的应用也才允许调试器附加、JIT 等操作。开启路径大致是设置 - 隐私与安全性 - 开发者模式 - 打开 - 重启设备 - 重启后确认。但这里有几个坑。第一这个选项只有在设备连接过 Xcode 或者安装过开发者证书的应用之后才会出现纯新机是看不到的。第二企业证书或者某些签名方式部署的应用可能不会触发这个选项。第三重启后必须在一小时内确认否则要重新来一遍。对于 Madeira 这类需要 JIT 的项目开发者模式是必须开启的。因为 FEX-Emu 的动态翻译需要mmap出可执行内存iOS 在非开发者模式下会直接拒绝。3.2 签名方式的选择免费证书、企业证书与自签热搜词里 免费证书ios、xcode从证书配置到上架全流程 指向的是签名问题。iOS 应用必须签名才能安装签名方式主要有几种免费个人证书用 Apple ID 在 Xcode 里生成有效期 7 天最多同时装 3 个应用。适合自己折腾但每 7 天要重新签一次。付费开发者账号99 美元一年证书有效期一年可以装 100 台设备。适合长期使用。企业证书理论上只对企业内部员工分发但实际中常被滥用容易被吊销。不建议依赖。自签工具用 AltStore、Sideloadly 这类工具配合 Apple ID 签名。本质还是免费证书只是自动化了重签流程。对于 Madeira 这种需要频繁调试的项目我建议至少用付费开发者账号省去每 7 天重签的麻烦。如果只是尝鲜免费证书也能跑但要接受频繁重签。3.3 JIT 权限的获取三种可行路径JIT 是 Madeira 在 iOS 上最核心的技术门槛。没有 JITFEX-Emu 的动态翻译就跑不起来。目前可行的路径有三条第一条是开启开发者模式后用调试器附加。Xcode 调试时会给应用附加get-task-allow权限这个权限允许 JIT。但这种方式要求应用一直处于调试状态不能独立运行。第二条是使用特定的 JIT 启用工具。社区里有工具可以在开发者模式下给应用授予 JIT 权限原理是利用调试端口或者特定的系统调用。这类工具需要设备开启开发者模式且每次启动应用都要重新授权。第三条是把 FEX-Emu 改成 AOT 模式。AOT提前编译不需要运行时生成代码但需要在构建时就把 x86 代码翻译好。这对 Madeira 来说不太现实因为 Windows 程序是动态加载的你没法提前知道要翻译哪些代码。实际中Madeira 类项目大多走第二条路配合开发者模式使用。这也是为什么 ios开发者模式 是热搜词——不开这个后面全都免谈。注意JIT 权限的获取方式会随 iOS 版本变化新版本往往会收紧。建议在动手前先确认当前 iOS 版本是否还有可用的 JIT 启用方案。4. 实操流程从零搭建 Madeira 运行环境4.1 环境准备清单在开始之前你需要准备这些东西一台运行 macOS 的电脑黑苹果也行但驱动问题会额外增加麻烦Xcode版本要能支持你目标 iOS 设备的系统版本目标 iOS 设备建议 iPhone 12 及以上或者 iPad Air 4 及以上A14/M1 芯片的翻译性能明显更好Apple 开发者账号免费或付费均可Madeira 相关的源码仓库包括 Wine 的 iOS 移植分支、FEX-Emu 的 ARM64 构建、DXMT 的 Metal 后端一个 Windows 程序的测试样本建议从简单的记事本类工具开始不要一上来就上大型游戏4.2 编译 Wine 的 iOS 版本Wine 的 iOS 移植不是官方主线需要找社区的分支。编译流程大致如下# 克隆 Wine 的 iOS 移植分支 git clone --branch ios-port https://example.com/wine-ios.git cd wine-ios # 配置编译选项关键是 --host 指定 ARM64 苹果目标 ./configure \ --hostaarch64-apple-darwin \ --with-coreaudio \ --with-metalshader \ --disable-tests \ --prefix/path/to/ios-wine-build # 编译建议用 make -j 加速 make -j$(sysctl -n hw.ncpu) make install这里有几个关键配置项要解释。--hostaarch64-apple-darwin告诉构建系统目标是 ARM64 苹果平台。--with-metalshader启用 Metal 着色器后端这是 DXMT 依赖的。--disable-tests跳过测试因为 iOS 上很多测试跑不了。编译过程中最常见的错误是头文件缺失和链接错误。iOS SDK 的路径需要正确设置通常通过xcrun --sdk iphoneos --show-sdk-path获取。如果遇到Undefined symbols错误多半是某个系统库没链接上需要手动在 Makefile 里加-framework参数。4.3 构建 FEX-Emu 的 ARM64 版本FEX-Emu 本身是给 Linux ARM 设备用的移植到 iOS 需要改不少东西。核心改动包括把 Linux 特有的系统调用替换成 iOS 的对应实现处理 JIT 内存分配用mmap加MAP_JIT标志适配 iOS 的线程模型pthread 的某些行为在 iOS 上有差异# 克隆 FEX-Emu git clone https://example.com/FEX-Emu.git cd FEX-Emu # 用 CMake 配置指定 iOS 工具链 cmake -B build-ios \ -DCMAKE_TOOLCHAIN_FILEcmake/ios.toolchain.cmake \ -DCMAKE_OSX_ARCHITECTURESarm64 \ -DCMAKE_OSX_DEPLOYMENT_TARGET16.0 \ -DENABLE_JITON \ -DENABLE_AOTOFF cmake --build build-ios -j$(sysctl -n hw.ncpu)ENABLE_JITON是必须的ENABLE_AOTOFF是因为 iOS 上 AOT 不实用。CMAKE_OSX_DEPLOYMENT_TARGET设成 16.0 是因为开发者模式从 iOS 16 才强化低于这个版本很多机制不一样。4.4 集成 DXMT 并配置 Metal 后端DXMT 的集成相对直接因为它本身就是为苹果平台设计的。关键是把 DXMT 的 dll 编译出来放到 Wine 的lib/wine/aarch64-windows/目录下然后在 Wine 的注册表里配置 Direct3D 的替代。# 编译 DXMT git clone https://example.com/dxmt.git cd dxmt meson setup build-ios \ --cross-file cross-ios.txt \ -Dbuildtyperelease ninja -C build-ios编译完成后把生成的d3d11.dll、dxgi.dll等文件复制到 Wine 的对应目录。然后在WINEPREFIX的注册表里设置[HKEY_CURRENT_USER\Software\Wine\DllOverrides] d3d11native dxginative这样 Wine 就会优先加载 DXMT 的实现而不是内置的 Direct3D 翻译层。4.5 打包成 iOS 应用并签名最后一步是把所有东西打包成一个 iOS 应用。你需要一个 Xcode 工程把 Wine、FEX-Emu、DXMT 编译成静态库或者动态库链接进去然后写一个简单的 Objective-C 或 Swift 外壳来启动。// 简化的启动代码 import UIKit main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) - Bool { // 初始化 Wine 环境 let prefixPath FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent(wineprefix) setenv(WINEPREFIX, prefixPath.path, 1) // 启动 Wine 主循环 DispatchQueue.global().async { wine_main(CommandLine.argc, CommandLine.unsafeArgv) } return true } }签名时在 Xcode 的 Signing Capabilities 里选择你的开发者账号开启get-task-allow调试用或者配置好发布证书。如果要用 JIT还需要在 entitlements 文件里加上对应的权限。5. 常见问题与排查技巧实录5.1 Wine 乱码问题字体与 locale 的双重坑wine 乱码 和 wine 栏是乱码 是热搜里出现最多的词说明这是高频问题。乱码的根源通常有两个字体缺失和 locale 配置错误。字体方面Wine 默认会去找系统字体但 iOS 沙盒里没有 Windows 常用的宋体、黑体。解决办法是把字体文件比如simsun.ttc、msyh.ttf复制到WINEPREFIX/drive_c/windows/Fonts/目录下然后在注册表里配置字体替换[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes] MS Shell DlgSimSun MS Shell Dlg 2SimSun TahomaSimSunlocale 方面Wine 需要正确的LANG和LC_ALL环境变量。在 iOS 上由于系统 locale 和 Windows 的对应关系不直接建议在启动脚本里显式设置export LANGzh_CN.UTF-8 export LC_ALLzh_CN.UTF-8如果还是乱码检查 Wine 的wineboot是否正常执行有时候 prefix 初始化不完整会导致字体注册表没写进去。5.2 Gecko 下载失败网络与路径的双重限制wine gecko官方正版下载 这个热搜词说明很多人卡在 Gecko 安装上。Wine 在初始化 prefix 时会提示下载 Gecko 和 Mono但 iOS 沙盒里这个自动下载经常失败。手动解决的办法是先在桌面端下载好 Gecko 的 msi 包然后放到 iOS 应用的沙盒目录里再通过 Wine 的msiexec手动安装wine msiexec /i wine_gecko-2.47.4-x86.msi注意 Gecko 的版本要和 Wine 的版本匹配版本不对会安装失败。另外iOS 上路径要用应用沙盒内的绝对路径不能用~或者相对路径。5.3 着色器编译卡顿DXMT 缓存配置第一次运行游戏时DXMT 需要把大量 HLSL 着色器翻译成 Metal这个过程可能持续几分钟。如果每次启动都卡说明缓存没生效。DXMT 的缓存默认在WINEPREFIX/drive_c/users/用户名/Temp/dxmt_cache/下。检查这个目录是否有写入权限以及缓存大小限制是否设得太小。可以在环境变量里调整export DXMT_SHADER_CACHE_SIZE1073741824 # 1GB export DXMT_SHADER_CACHE_PATH/path/to/cache如果缓存目录在 iOS 的临时目录下系统可能会定期清理建议放到 Documents 目录里避免被回收。5.4 应用启动闪退签名与权限排查闪退是 iOS 上最常见的问题排查思路如下现象可能原因排查方法启动即闪退签名无效检查证书是否过期重新签名启动后黑屏JIT 未启用确认开发者模式已开检查 entitlements运行中崩溃内存不足查看崩溃日志iOS 对单应用内存有限制特定操作崩溃API 未实现查看 Wine 日志确认哪个调用失败崩溃日志可以通过 Xcode 的 Devices and Simulators 窗口查看或者用idevicecrashreport工具导出。重点看Exception Type和Termination Reason前者告诉你崩溃类型比如EXC_BAD_ACCESS是内存访问错误后者告诉你系统为什么终止应用比如CODESIGNING是签名问题。5.5 性能调优从 30 帧到 60 帧的实操经验性能是 iOS 上跑 Wine 的另一个大问题。我实测下来几个有效的调优手段关闭不必要的 Wine 服务wineboot会启动一些后台服务在 iOS 上这些服务很占资源。可以在启动参数里加-services来精简。调整 FEX-Emu 的翻译块大小翻译块越大缓存命中率越高但首次翻译延迟也越大。默认值通常够用如果游戏卡顿可以尝试调大。DXMT 的异步着色器编译开启异步编译可以避免着色器编译阻塞渲染线程代价是首次出现某个效果时可能短暂闪烁。降低分辨率iOS 设备的屏幕分辨率很高但 Wine 渲染时可以用较低的内部分辨率然后放大输出。这能显著提升帧率。# 设置 Wine 虚拟桌面分辨率 export WINEDLLOVERRIDESd3d11n,b wine reg add HKEY_CURRENT_USER\Software\Wine\Explorer\Desktops /v Default /t REG_SZ /d 1280x7206. 分发与上架从自用到分享的路径6.1 自签分发的现实约束如果你只是自己用自签是最简单的路径。但要注意免费证书 7 天过期付费证书一年过期企业证书随时可能被吊销。对于 Madeira 这种需要持续更新的项目建议至少用付费账号。自签工具方面AltStore 和 Sideloadly 是主流选择。AltStore 的优势是可以在设备上自动重签不需要每次连电脑。Sideloadly 的优势是支持更多签名方式包括企业证书。6.2 上架 App Store 的可行性分析ios app开发完毕如何上架 这个热搜词说明有人想走正规上架路线。但 Madeira 这类项目上架 App Store 的难度极大原因有几个JIT 权限App Store 审核明确禁止应用在运行时生成可执行代码除非是 JavaScriptCore 这类系统允许的例外。FEX-Emu 的 JIT 直接撞这条红线。沙盒限制Wine 需要访问大量文件系统路径App Store 的沙盒比开发者模式下的沙盒更严。内容审核Windows 程序本身可能包含违规内容苹果不会允许一个能运行任意 Windows 程序的应用上架。所以现实路径是自签分发、企业内部分发、或者 TestFlight 小范围测试。上架 App Store 基本不用想。6.3 替代方案远程串流与云游戏如果本地跑不动还有一个替代思路是远程串流。把 Windows 程序跑在一台性能足够的 PC 或者服务器上然后通过串流协议把画面传到 iOS 设备。这种方式不需要 JIT不需要签名折腾但依赖网络质量。串流方案的选择上Moonlight配合 Sunshine是延迟最低的Parsec 的画质更好但延迟略高。iOS 上两者都有客户端配置也不复杂。对于只是想玩 Windows 游戏的人来说串流可能是比本地 Wine 更实际的选择。7. 一些实操心得与后续扩展方向折腾 Madeira 这套东西我最大的体会是iOS 的封闭性不是靠一个技术点突破的而是靠一整条链路的配合。开发者模式、签名、JIT、沙盒路径、Metal 后端任何一个环节出问题整个东西就跑不起来。所以排查问题时要有全局视角不要只盯着一个组件。另一个心得是从简单程序开始。我一开始就想跑大型游戏结果卡在着色器编译上一整天。后来换成一个简单的 Win32 记事本程序十分钟就跑起来了然后再逐步增加复杂度。这个渐进式的思路能帮你快速定位问题出在哪一层。后续可以扩展的方向有几个。一是AOT 翻译的优化如果能提前把常用 Windows 库翻译好运行时就不需要 JIT能绕过 iOS 的限制。二是Metal 后端的深度优化DXMT 目前对 D3D12 的支持还不完整如果能补上能跑的游戏会更多。三是与 iOS 原生能力的结合比如用 iOS 的触控、陀螺仪、手柄支持来增强游戏体验这部分 Wine 本身不提供需要在外壳层做。最后分享一个小技巧Wine 的日志级别可以通过WINEDEBUG环境变量控制。排查问题时设成WINEDEBUGall能看到所有调用但日志量巨大建议针对性开启比如WINEDEBUGd3d11,dxgi只看图形相关。日志输出到文件用 wine.log方便后续分析。