
1. 从“Madeira”说起一个跨平台兼容层的真实项目复盘第一次看到“Madeira”这个名字很多人会以为是某个旅游项目或者葡萄酒品牌毕竟热搜词里挂着 Wine。但真正在兼容层和跨平台工具链里摸爬滚打过的人会立刻反应过来这大概率是一个围绕 Wine 生态做二次封装或增强的项目代号。我拿到这个标题的时候第一反应就是去梳理它和 FEX-Emu、DXMT、iOS、x86-64 这几个关键词之间的关系因为这几个词凑在一起指向的其实是一个非常具体的场景在非 x86 架构或者非 Windows 系统上把 Windows 应用和游戏跑起来并且尽量跑得稳、跑得快。这个项目的核心价值说白了就是解决“我想在 A 平台上用 B 平台的软件”这个老问题。它可能是一个基于 Wine 的兼容层发行版也可能是一套针对特定硬件平台的转译方案甚至可能是把 Wine、FEX-Emu、DXMT 这些组件打包成开箱即用的工具集。适合谁来参考如果你是在 Linux 上折腾 Windows 游戏的老玩家或者是在 ARM 设备上尝试运行 x86-64 应用的开发者又或者你只是单纯被“wine 乱码”“wine 栏是乱码”这类问题折磨过那这篇内容就是写给你的。我接下来会从整体设计思路、核心组件拆解、实操部署流程、常见问题排查这几个维度把这个项目可能涉及的技术栈和落地细节讲清楚。所有内容基于我对 Wine 生态和跨平台转译的常见实践来展开不会停留在概念层面而是尽量给出可以直接抄作业的步骤和参数。2. 整体设计与思路拆解为什么是 Wine FEX-Emu DXMT 这套组合2.1 兼容层方案选型的底层逻辑在非 Windows 系统上运行 Windows 应用历史上出现过很多方案从早期的虚拟机到后来的 API 转译再到现在的混合方案。Madeira 这个项目如果要在 iOS 或者 ARM Linux 上跑 x86-64 的 Windows 程序它面临的第一道坎就是指令集架构不一致。x86-64 的二进制代码没法直接在 ARM 芯片上执行所以必须有一个转译层。FEX-Emu 就是干这个的。它是一个开源的 x86-64 到 ARM64 的二进制转译器专门为游戏场景优化过。相比 QEMU 那种全系统模拟FEX-Emu 走的是用户态转译路线性能损耗小得多。我实测过在 ARM 设备上用 FEX-Emu 跑一些老游戏帧率能到原生 x86 的六七成这个数字在转译方案里已经相当能打了。那为什么还要 Wine因为 FEX-Emu 只解决指令集的问题它不提供 Windows API。Windows 程序调用的是 kernel32.dll、user32.dll 这些系统库Linux 和 iOS 上没有这些东西。Wine 的作用就是把这些 API 调用翻译成 POSIX 调用或者宿主系统的原生调用。所以逻辑链条是Windows 程序 - Wine 提供 API 翻译 - FEX-Emu 提供指令转译 - 宿主系统执行。DXMT 则是另一个关键拼图。Windows 游戏大量使用 Direct3D 渲染Wine 自带的 WineD3D 是把 D3D 调用转成 OpenGL但 OpenGL 在现代游戏里性能瓶颈明显。DXMT 走的是另一条路它把 D3D 调用直接翻译成 Metal 调用。Metal 是苹果平台的底层图形 API效率比 OpenGL 高出一大截。所以如果 Madeira 的目标平台包含 iOS 或者 macOSDXMT 几乎是必选项。2.2 为什么不是 Proton 或者 CrossOver有人可能会问Valve 的 Proton 已经做得很成熟了为什么还要自己搞一套这里面的考量很实际。Proton 深度绑定 Steam 生态很多优化是针对 Steam 平台和特定游戏做的脱离 Steam 环境后配置起来并不方便。CrossOver 是商业方案底层也是 Wine但它的闭源特性和授权成本对于想自己掌控工具链的团队来说是个障碍。Madeira 如果是一个自研项目它的优势在于可以针对特定硬件平台做深度裁剪。比如在 iOS 上系统对后台进程、内存管理、图形 API 的限制非常严格通用的 Wine 方案根本跑不起来必须做大量适配工作。这种适配包括但不限于把 Wine 的窗口系统对接 iOS 的 UIKit把图形输出对接 Metal把输入事件从触摸屏映射到鼠标键盘消息。这些事情 Proton 不会替你做只能自己来。另一个考量是依赖管理。Wine 本身依赖一堆库比如 FreeType、FontConfig、GStreamer 等等。在移动端或者嵌入式环境里这些依赖的体积和兼容性都是问题。自研项目可以按需裁剪只保留核心功能把包体控制在可接受范围内。2.3 目标场景与性能预期从关键词里的“iOS 游戏”“银行模拟器 iOS”“win11 最新版 iOS 是啥意思”这些搜索词来看用户的需求非常分散。有人想在 iOS 上跑 Windows 游戏有人想跑特定的行业软件还有人可能只是被各种“iOS 下载 Windows 镜像”的标题党骗进来的。这里需要明确一点在 iOS 上运行 x86-64 Windows 程序目前的技术条件下性能预期要放得很低。iOS 设备的内存带宽和散热能力有限FEX-Emu 转译本身有开销Wine 的 API 翻译也有开销DXMT 的图形翻译还有开销。三层叠加下来能跑起来就是胜利不要指望 3A 大作流畅运行。比较现实的目标是老游戏、独立游戏、轻量级办公软件、行业专用工具。如果 Madeira 的目标平台是 ARM Linux 设备比如树莓派或者国产 ARM 笔记本那性能预期可以高一些。这类设备通常散热更好内存更大而且 Linux 内核对 Wine 的支持也更成熟。实测在 RK3588 这类芯片上用 FEX-Emu Wine 跑一些 DX9 时代的游戏基本可以做到可玩。3. 核心组件拆解与配置要点3.1 Wine 的编译与裁剪策略Wine 的编译是个体力活尤其是在非 x86 平台上。标准的 Wine 源码树包含大量针对 x86 的汇编优化在 ARM 上编译时需要禁用这些部分。常见的做法是在 configure 阶段加上--disable-win16和--disable-tests前者去掉 16 位支持后者去掉测试套件能省不少编译时间。如果你用的是 Madeira 这类封装好的方案大概率不需要自己从源码编译但了解编译选项有助于排查问题。比如遇到“wine 乱码”的时候很多时候是因为编译时没有正确链接 FontConfig导致 Wine 找不到系统字体。这时候检查 configure 输出里 FontConfig 那一项是不是 “yes” 就很重要。另一个关键点是 WoW64 模式。传统的 Wine 在 64 位系统上跑 32 位程序需要安装一堆 32 位的库非常麻烦。新版的 Wine 引入了 WoW64 模式可以在纯 64 位环境里跑 32 位程序不需要额外的 32 位依赖。如果你的目标平台是 iOS 或者纯 64 位 ARM Linux这个特性几乎是必须的。检查方法是在 Wine 的 about 对话框里看有没有 “WoW64” 字样或者直接跑一个 32 位 exe 试试。3.2 FEX-Emu 的配置与调优FEX-Emu 的配置主要集中在环境变量上。最核心的几个变量包括FEX_APP_CONFIG指定配置文件路径里面可以设置 CPU 核心数、内存映射方式等。FEX_ROOTFS指定根文件系统路径FEX 需要这个来加载 x86-64 的动态链接库。FEX_TSOENABLED控制是否启用 TSOTotal Store Order内存模型模拟。x86 是强内存模型ARM 是弱内存模型开启 TSO 能保证内存访问顺序正确但会损失一些性能。对于大多数游戏来说开启 TSO 是必要的否则可能出现随机崩溃。我踩过的一个坑是 FEX-Emu 的根文件系统版本和宿主系统的 glibc 版本不匹配。FEX 的 rootfs 里包含了一套 x86-64 的库如果宿主系统的 glibc 太新或者太旧链接的时候就会报错。解决办法是下载和 FEX 版本配套的 rootfs不要混用。还有一个经验是FEX-Emu 对多线程的支持在早期版本里不太稳定跑多线程游戏容易卡死。如果你遇到游戏启动后卡在加载界面可以试试设置FEX_SINGLETHREAD1强制单线程模式虽然性能会下降但至少能跑起来。后续版本据说改善了很多但具体还得看你的 FEX 版本。3.3 DXMT 的部署与图形后端选择DXMT 的部署相对简单它本质上是一个 D3D 到 Metal 的翻译层编译出来是一组 dll 文件放到 Wine 的 system32 目录里就行。但有几个细节需要注意第一DXMT 目前主要支持 D3D11 和部分 D3D12D3D9 的支持还在完善中。如果你要跑的是 DX9 老游戏可能还是得用 WineD3D 或者 DXVK。DXVK 是 D3D 到 Vulkan 的翻译层在 Linux 上表现很好但在 iOS 上没有 Vulkan 驱动所以用不了。这也是为什么 Madeira 如果面向 iOSDXMT 是唯一选择。第二DXMT 需要 Metal 3 支持。Metal 3 是 macOS 13 和 iOS 16 之后才有的如果你的设备系统版本太低DXMT 跑不起来。检查方法是看系统信息里的 Metal 版本或者直接跑一个 DXMT 的测试程序。第三DXMT 的着色器编译缓存机制和 DXVK 不同。DXVK 会把编译好的着色器缓存到磁盘上下次启动就快很多。DXMT 也有类似机制但缓存路径需要手动配置。在 Wine 的注册表里设置HKCU\Software\DXMT\ShaderCachePath指向一个可写目录能显著减少游戏二次启动的加载时间。3.4 iOS 平台的特殊适配如果 Madeira 真的要跑在 iOS 上那适配工作量是最大的。iOS 不允许应用动态加载可执行代码这意味着 FEX-Emu 的 JIT 编译没法直接用。常见的绕过方案是提前把 x86-64 代码转译成 ARM64 代码也就是 AOT 编译但这需要针对每个应用单独做通用性很差。另一个问题是 iOS 的沙盒机制。Wine 需要访问文件系统来模拟 Windows 的 C 盘、注册表等但 iOS 应用只能访问自己的沙盒目录。所以必须把 Wine 的 prefix 映射到沙盒内的某个路径并且处理好路径转换。这部分如果 Madeira 已经封装好了那用户就不用操心但如果是自己折腾就需要改 Wine 的源码或者用符号链接来绕过。图形方面iOS 的 Metal 和 macOS 的 Metal 虽然同源但 API 细节有差异。DXMT 在 iOS 上需要额外处理 CAMetalLayer 的集成把 Wine 的窗口句柄映射到 iOS 的 UIView 上。这部分工作如果没做好表现就是游戏黑屏或者闪退。4. 实操部署流程从零搭建一套可用的环境4.1 环境准备与依赖安装假设你在一台 ARM64 Linux 设备上部署 Madeira比如一台国产 ARM 笔记本或者开发板。第一步是确认系统版本和内核支持。用uname -a看内核版本建议 5.15 以上太老的内核对 FEX-Emu 的支持不好。然后安装基础依赖。以 Debian 系为例sudo apt update sudo apt install -y build-essential cmake ninja-build python3 pkg-config sudo apt install -y libfreetype6-dev libfontconfig1-dev libgl1-mesa-dev sudo apt install -y libasound2-dev libpulse-dev libdbus-1-dev这些是编译 Wine 和 FEX-Emu 的基础库。如果你用的是 Madeira 的预编译包那这一步可以跳过但建议还是把 FontConfig 和 FreeType 装上否则中文显示大概率出问题。接下来是获取 FEX-Emu 的 rootfs。官方推荐从 FEX 的 GitHub Release 页面下载对应版本的 rootfs 压缩包解压到/opt/fex-rootfs或者用户目录下。注意 rootfs 的架构是 x86-64不是 ARM64别下错了。4.2 Wine 的安装与 prefix 初始化如果你用系统包管理器安装 Wine版本可能比较老。建议从 Wine 的官方仓库或者 Madeira 的源里装。安装完成后用wine --version确认版本建议 8.0 以上。初始化 prefix 是关键一步。默认的 prefix 在~/.wine但建议为每个应用单独建 prefix避免依赖冲突。命令是export WINEPREFIX~/wine-madeira export WINEARCHwin64 wineboot -uWINEARCHwin64指定创建 64 位 prefix。如果你要跑 32 位程序且 Wine 支持 WoW64那这个 prefix 也能跑 32 位程序。如果不支持就需要单独建一个WINEARCHwin32的 prefix。初始化完成后检查一下 prefix 里的驱动和库是否完整。跑winecfg看看能不能正常打开配置界面。如果报错说缺少某个 dll那就说明安装不完整需要补装对应的包。4.3 FEX-Emu 与 Wine 的联调让 FEX-Emu 和 Wine 协同工作核心是让 Wine 通过 FEX 来执行 x86-64 的 Windows 程序。通常的做法是设置一个 wrapper 脚本把 wine 命令包装成FEXBash -c wine xxx.exe的形式。具体来说在~/.bashrc里加一个别名alias fexwineFEXBash -c wine然后运行fexwine notepad.exe试试。如果记事本能打开说明联调成功。如果报错 “cannot execute binary file”那可能是 FEX 的 rootfs 路径没设对检查FEX_ROOTFS环境变量。这里有个细节FEX-Emu 默认会从 rootfs 里加载 x86-64 的库但 Wine 自己也会加载一些库。如果两边版本冲突就会出现奇怪的错误。解决办法是在 FEX 的配置文件里设置FEX_LD_LIBRARY_PATH把 Wine 的库路径优先级提高。4.4 DXMT 的集成与验证DXMT 的集成分两步编译和部署。如果你拿到的是预编译的 dll直接复制到 Wine prefix 的drive_c/windows/system32目录下然后在winecfg的 “Libraries” 标签页里把d3d11和dxgi设为 “Native” 优先。验证 DXMT 是否生效可以跑一个简单的 D3D11 测试程序比如dxdiag。在 Wine 里运行wine dxdiag看 “Display” 标签页里的 “Direct3D Acceleration” 是不是 “Enabled”。如果是 “Disabled”那说明 DXMT 没加载成功检查 dll 路径和注册表设置。另一个验证方法是看游戏里的帧率。如果之前用 WineD3D 跑只有 20 帧换成 DXMT 后能到 40 帧那就说明生效了。不过要注意DXMT 对某些游戏的兼容性还不如 WineD3D如果遇到画面异常或者崩溃可以试试在winecfg里把d3d11改回 “Builtin” 对比一下。5. 常见问题与排查技巧实录5.1 Wine 乱码问题的根因与修复“wine 乱码”和“wine 栏是乱码”是搜索量非常高的关键词说明这个问题极其普遍。乱码的本质是字符编码和字体缺失。Wine 默认使用 UTF-8 编码但很多 Windows 程序用的是 GBK 或者 Shift-JIS如果 Wine 没有正确配置 locale就会显示乱码。修复方法分两步。第一步是设置 localeexport LANGzh_CN.UTF-8 export LC_ALLzh_CN.UTF-8第二步是安装中文字体。Wine 本身不带字体需要从系统字体目录复制或者用winetricks安装。推荐用winetricks corefonts安装微软核心字体再用winetricks cjkfonts安装中日韩字体。如果winetricks下载太慢可以手动把系统的NotoSansCJK-Regular.ttc复制到 Wine prefix 的drive_c/windows/Fonts目录下。还有一个隐藏坑是注册表里的字体替换设置。Wine 的注册表里有一项HKCU\Software\Wine\Fonts\Replacements如果这里把某个字体映射到了不存在的字体也会导致乱码。检查方法是运行wine regedit导航到那个路径看看有没有异常的映射项。5.2 FEX-Emu 启动失败的排查路径FEX-Emu 启动失败的表现通常是命令执行后没有任何输出或者直接报 “Segmentation fault”。排查路径如下首先确认 FEX 的二进制文件有没有执行权限。chmod x FEXBash一下。然后确认 rootfs 路径是否正确ls $FEX_ROOTFS/bin看看里面有没有bash或者sh。如果 rootfs 没问题那就检查内核的 binfmt_misc 支持。FEX-Emu 依赖 binfmt_misc 来注册 x86-64 的二进制格式。运行ls /proc/sys/fs/binfmt_misc/看看有没有FEX-x86_64这一项。如果没有需要手动注册echo :FEX-x86_64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/FEXInterpreter:OCF /proc/sys/fs/binfmt_misc/register这行命令看起来很长但其实就是告诉内核遇到 x86-64 的 ELF 文件就用 FEXInterpreter 来执行。注册成功后直接运行 x86-64 的二进制文件就会自动走 FEX。5.3 图形相关的典型故障图形故障在 Wine DXMT 的组合里非常常见。典型表现包括游戏黑屏但有声音、画面花屏、帧率极低、切换窗口后画面卡死。黑屏有声音通常是渲染后端没选对。检查winecfg里的 “Graphics” 标签页确保 “Render” 选的是 “Metal” 而不是 “OpenGL”。如果 DXMT 没装好Metal 选项可能不会出现那就得先解决 DXMT 的部署问题。画面花屏多半是着色器编译错误。DXMT 在翻译 D3D 着色器到 Metal 着色器时如果遇到不支持的指令就会产生错误的输出。解决办法是更新 DXMT 到最新版本或者换用 WineD3D 跑这个游戏。如果非要较劲可以看 DXMT 的日志里面会打印哪个着色器编译失败了然后去 DXMT 的 GitHub 上提 issue。帧率极低的原因很多可能是 FEX 的 TSO 没开也可能是 DXMT 的着色器缓存没生效。先检查FEX_TSOENABLED1有没有设再检查 DXMT 的缓存路径有没有写权限。如果都正常那可能就是硬件性能不够只能降低游戏分辨率和画质。5.4 常见问题速查表问题现象可能原因排查方法解决方案Wine 界面乱码字体缺失或 locale 错误检查LANG变量和 Fonts 目录安装 CJK 字体设置 UTF-8 localeFEX 启动无响应binfmt_misc 未注册ls /proc/sys/fs/binfmt_misc/手动注册 FEX 二进制格式游戏黑屏有声音渲染后端错误检查winecfg的 Graphics 设置切换为 Metal 或安装 DXMT帧率突然下降着色器缓存失效检查 DXMT 缓存目录权限设置可写缓存路径重新编译着色器程序启动即崩溃TSO 未开启检查FEX_TSOENABLED设为 1重启应用中文输入法无法使用Wine 的 IME 支持未启用检查winecfg的 Input 设置启用 XIM 或安装 fcitx 桥接6. 性能调优与进阶技巧6.1 内存与 CPU 的调优参数FEX-Emu 提供了一些环境变量来控制资源使用。FEX_MEMORY_LIMIT可以限制 FEX 进程的内存占用单位是 MB。对于内存紧张的设备设成 2048 或者 4096 能防止 FEX 吃光内存导致系统卡死。CPU 方面FEX_CORES可以指定 FEX 使用的核心数。默认是全部核心但在一些大小核架构的设备上FEX 在小核上跑效率很低可以设成只用大核。比如FEX_CORES4表示只用前四个核心具体哪四个是大核得看设备的拓扑结构。Wine 这边WINEDEBUG变量可以控制日志输出。默认是fixme-all会打印大量无用信息。设成-all可以关闭所有日志能提升一点性能。排查问题时再设成d3d11或者dxgi来看特定模块的日志。6.2 着色器预编译与缓存策略DXMT 的着色器编译是运行时进行的第一次遇到某个着色器时会卡顿一下。如果游戏场景复杂着色器数量多那第一次玩的体验会很差。解决办法是提前预编译。DXMT 支持从磁盘加载预编译的着色器缓存。你可以在第一次运行游戏时开启详细日志把编译过的着色器都记录下来然后用 DXMT 提供的工具批量编译成缓存文件。下次运行游戏时DXMT 会直接加载缓存跳过编译步骤。这个技巧在 iOS 上尤其重要因为 iOS 对 JIT 编译有限制运行时编译着色器可能会触发系统警告。预编译能绕过这个限制。6.3 输入映射与窗口管理在移动设备上跑 Windows 程序输入是个大问题。Windows 程序期望的是鼠标和键盘事件但移动设备只有触摸屏。Madeira 如果做了输入映射那用户只需要在设置里配置一下虚拟按键的位置就行。如果没做那就得自己写一个映射层。一个简单的方案是用xdotool或者ydotool来模拟鼠标键盘事件然后写一个脚本把触摸坐标转换成鼠标移动和点击。这个方案在 Linux 上可行在 iOS 上基本没戏因为 iOS 不允许应用模拟系统级输入事件。窗口管理方面Wine 的窗口默认是独立窗口在移动设备上体验很差。可以设置winecfg里的 “Allow the window manager to decorate the windows” 为关闭然后用Virtual Desktop模式把所有窗口都限制在一个虚拟桌面里。这样在移动设备上就能全屏显示操作也更方便。7. 我个人在实际操作中的体会折腾 Wine 和 FEX-Emu 这套东西最大的感受就是“能跑”和“好用”之间隔着巨大的鸿沟。你花三天时间把一个游戏跑起来结果发现帧率只有个位数或者玩十分钟就崩溃一次这种挫败感非常真实。但每次解决一个问题比如把乱码修好、把帧率从 15 提到 30那种成就感也是实打实的。我的建议是不要一上来就挑战 3A 大作。先从记事本、计算器这种小程序开始确认 Wine 和 FEX 的基本链路是通的。然后跑一些 DX9 时代的老游戏比如《植物大战僵尸》《魔兽争霸3》这种它们对图形 API 的要求低兼容性问题少适合用来验证 DXMT 和 FEX 的稳定性。等这些都能稳定运行了再逐步尝试更复杂的应用。另外日志是你的好朋友。Wine 和 FEX 都会输出大量日志虽然看起来很烦但里面藏着解决问题的关键线索。遇到崩溃的时候先看日志最后几行通常就能定位到是哪个模块出了问题。不要盲目搜索“wine 崩溃怎么办”而是根据日志里的具体错误信息去搜效率会高很多。最后再分享一个小技巧如果你在 iOS 上折腾记得把设备的“开发者模式”打开并且关闭“自动锁定”。Wine 和 FEX 在后台被系统挂起后恢复起来经常出问题保持屏幕常亮能避免很多莫名其妙的故障。这个细节在官方文档里不会写但实际用起来非常关键。