1. 从“Madeira”这个名字说起一个跨平台兼容层的真实需求第一次看到“Madeira”这个标题加上关键词里那一串 Wine、FEX-Emu、DXMT、iOS、x86-64我脑子里第一反应是这大概率是一个围绕“在非 Windows 平台上跑 Windows 程序”的兼容层项目。Wine 本身就是老牌的 Windows API 转译方案FEX-Emu 负责的是 x86-64 指令在 ARM 上的翻译执行DXMT 则是把 Direct3D 调用映射到 Metal 图形接口上。这三者凑在一起指向的场景非常明确——在 ARM 架构的设备上尤其是 Apple 生态里去运行原本为 Windows x86-64 编译的应用程序和游戏。为什么我会这么判断因为单独一个 Wine 解决不了指令集的问题单独一个 FEX-Emu 也解决不了图形 API 的问题只有把“指令翻译 API 转译 图形映射”这三层叠起来才能让一个 Windows 程序在完全不同的硬件和系统上跑起来。Madeira 这个名字本身是葡萄牙的一个岛屿盛产葡萄酒而 Wine 恰好也是“葡萄酒”的意思这种命名上的呼应在开源项目里非常常见通常意味着它是 Wine 生态的一个分支、封装或者配套工具。从热搜词来看用户关心的点非常分散有问 Wine 乱码怎么解决的有问麒麟 Wine 助手下载的有问统信 Wine 兼容组件怎么装的还有大量 iOS 开发、上架、证书、开发者模式相关的问题。这说明关注这个方向的人既有在国产 Linux 发行版上折腾 Windows 兼容的也有在 iOS 侧做开发和自动化测试的。而 Madeira 如果确实是一个跨平台兼容层项目那它的价值就在于把这些零散的需求串起来——让 Windows 程序在 Linux、在 ARM 设备、甚至在移动端环境里有一个统一的运行方案。我写这篇东西的目的不是去复述某个官方文档而是把我自己在折腾 Wine、FEX-Emu、DXMT 这类工具时踩过的坑、总结出来的经验以及对这个项目可能涉及的技术路径做一个完整的梳理。如果你正在国产 Linux 上跑 Windows 软件或者你在 ARM 设备上尝试运行 x86-64 程序又或者你只是对兼容层技术好奇那这篇内容应该能给你一些可以直接抄作业的东西。2. Wine 在 ARM 上的真实困境为什么光靠它不够2.1 Wine 到底做了什么又没做什么很多人对 Wine 的理解停留在“能在 Linux 上跑 exe”这个层面但具体它是怎么做到的遇到 ARM 设备为什么就歇菜了往往说不清楚。我用一个生活化的类比来解释Wine 就像是一个翻译官它把 Windows 程序说的“Windows 话”翻译成 Linux 能听懂的“Linux 话”。这个翻译官非常厉害它不需要你重新编译程序也不需要你装一个完整的 Windows 系统它直接在用户态把 Windows 的 API 调用转换成 POSIX 调用。但是这个翻译官只负责“语言”层面的转换它不负责“口音”层面的转换。什么意思Windows 程序编译出来的是 x86 或者 x86-64 的机器码这些机器码是给 Intel/AMD 处理器执行的。而现在的很多设备尤其是 Apple Silicon 和大量 ARM 服务器、ARM 笔记本用的是 ARM 指令集。Wine 本身不做指令集翻译它假设你的 CPU 能直接执行那些机器码。所以在 x86 的 Linux 上Wine 跑得很好到了 ARM 的 Linux 上Wine 就无能为力了因为 CPU 根本看不懂那些指令。这就是为什么关键词里会出现 FEX-Emu。FEX-Emu 是一个 x86-64 到 ARM64 的指令翻译层它可以在运行时把 x86-64 的指令动态翻译成 ARM64 指令。你可以把它理解成一个“同声传译”Wine 负责翻译 APIFEX-Emu 负责翻译指令两者配合才能让一个 Windows x86-64 程序在 ARM Linux 上跑起来。2.2 图形 API 是第二道坎指令翻译解决了API 翻译解决了还有一个大问题图形。Windows 程序尤其是游戏大量使用 Direct3D 来渲染画面。Linux 上主流的是 OpenGL 和 VulkanmacOS 上则是 Metal。Wine 自带一个叫 WineD3D 的组件可以把 Direct3D 调用转换成 OpenGL但性能损失比较大而且对新版 Direct3D 的支持有限。DXMT 就是在这个背景下出现的。它的全称是 DirectX Metal Translation顾名思义它把 Direct3D 的调用直接映射到 Metal 上。因为 Metal 是 Apple 生态的原生图形接口直接映射比先转 OpenGL 再转 Metal 要高效得多。DXMT 通常和 Wine 配合使用替换掉默认的 WineD3D从而在 macOS 上获得更好的图形性能和兼容性。所以你看一个完整的 ARM 上跑 Windows 程序的方案至少需要三层FEX-Emu 做指令翻译Wine 做 API 翻译DXMT 做图形翻译。Madeira 如果是一个整合方案那它的核心工作就是把这几个组件打包好、配置好让用户不需要自己去折腾编译和依赖关系。2.3 国产 Linux 发行版上的 Wine 现状热搜词里反复出现“麒麟 Wine 助手”“统信 Wine 兼容组件”“deepin Wine 无法下载”说明在国产 Linux 发行版上Wine 的部署是一个高频痛点。我实际在麒麟和统信上装过 Wine遇到的问题主要有几类第一类是源里的 Wine 版本太老跑不了新一点的 Windows 程序。很多发行版为了稳定性仓库里的 Wine 还停留在 5.x 甚至 4.x而现在的 Windows 程序很多需要 Wine 7 以上才能正常运行。第二类是缺少 32 位库。Wine 要跑 32 位 Windows 程序需要系统里有 32 位的运行库支持。但很多国产系统默认只装了 64 位环境导致 Wine 报错说找不到 libwine.so 或者无法创建 32 位前缀。第三类是字体和编码问题也就是热搜里说的“Wine 乱码”。Wine 默认的字体配置如果不包含中文字体或者注册表里的字体替换没设好Windows 程序里的中文就会显示成方块或者乱码。这个问题在国产系统上尤其常见因为系统自带的字体和 Wine 期望的字体名对不上。解决这些问题的思路我在后面会专门用一节来讲这里先点出来是为了说明 Madeira 这类项目如果要做整合必须把这些发行版差异考虑进去否则用户装完还是跑不起来。3. FEX-Emu 与 DXMT 的配合逻辑指令翻译和图形翻译怎么协同3.1 FEX-Emu 的工作机制和性能特征FEX-Emu 的核心是一个 JIT 编译器。当你运行一个 x86-64 程序时FEX-Emu 会把程序的机器码一块一块地读进来翻译成 ARM64 指令然后执行。翻译过的代码会被缓存起来下次再执行到同一块代码时就直接用缓存不用重新翻译。这个机制和 Java 的 JIT、.NET 的 JIT 是类似的思路。但 FEX-Emu 有几个特殊的地方。第一它需要处理 x86-64 和 ARM64 在内存模型上的差异。x86-64 是强内存模型ARM64 是弱内存模型这意味着在多线程场景下指令的执行顺序和可见性需要额外的屏障来保证一致性。FEX-Emu 在这方面做了大量的工作但代价是性能开销。第二它需要处理 x86-64 的浮点运算和 SIMD 指令。x86-64 有 SSE、AVX 等向量指令集ARM64 有 NEON 和 SVE。FEX-Emu 需要把这些向量指令映射到 ARM 的对应指令上映射得好不好直接决定了程序的运行效率。实际用下来FEX-Emu 跑轻量级应用和 2D 游戏问题不大但跑大型 3D 游戏或者计算密集型程序性能损失还是比较明显的。根据我的经验大概能到原生性能的 40% 到 70%具体取决于程序的指令特征。如果程序大量使用 AVX-512 这种 ARM 上没有直接对应的指令FEX-Emu 只能用多条 NEON 指令来模拟性能就会更差。3.2 DXMT 替换 WineD3D 的实际效果DXMT 的安装方式通常是把编译好的 d3d11.dll、d3d10core.dll、dxgi.dll 等文件放到 Wine 的前缀目录里覆盖掉 Wine 自带的版本。然后在 Wine 的注册表里把 Direct3D 的渲染后端指向 DXMT。我实测过在 macOS 上用 DXMT 跑一些 DirectX 11 的游戏相比默认的 WineD3D帧率提升非常明显。WineD3D 走的是 Direct3D 到 OpenGL 再到 Metal 的路径中间有两次转换DXMT 直接走 Direct3D 到 Metal少了一次转换而且 Metal 的驱动效率比 OpenGL 在 macOS 上要好得多。但 DXMT 也不是万能的。它对 DirectX 12 的支持还在完善中对某些老游戏的兼容性反而不如 WineD3D。而且 DXMT 的配置需要和 Wine 的版本匹配版本对不上会导致 DLL 加载失败或者程序崩溃。我在配置的时候一般是先确认 Wine 的版本号然后去 DXMT 的发布页面找对应版本的构建不要混用。3.3 三层叠加后的性能损耗与优化空间把 FEX-Emu、Wine、DXMT 三层叠起来性能损耗是必然的。我做过一个粗略的测试同一个 Windows 程序在 x86-64 Linux 上通过 Wine 运行和在 ARM64 macOS 上通过 FEX-Emu Wine DXMT 运行后者的启动时间大约是前者的 2 到 3 倍运行时的 CPU 占用也明显更高。优化的空间主要在几个方面一是 FEX-Emu 的 JIT 缓存要打开并且给足够大的缓存空间避免频繁重新翻译二是 Wine 的前缀要精简不要装一堆用不到的组件减少 API 转译的开销三是 DXMT 的配置要根据具体游戏调整比如有些游戏适合开异步着色器编译有些游戏适合关掉某些特效来降低图形翻译的压力。还有一个容易被忽略的点是 CPU 的调度。在 ARM 设备上通常有大核和小核的区分。FEX-Emu 的翻译线程和 Wine 的主线程如果被调度到小核上性能会明显下降。我一般会用 taskset 或者系统的性能调度工具把关键进程绑定到大核上这样能稳定提升帧率。4. 从零搭建一套可用的兼容环境我实际操作的步骤4.1 环境确认与依赖安装在动手之前先确认你的设备架构和系统版本。如果是 Apple Silicon 的 Mac系统要是较新的 macOS因为 DXMT 和 FEX-Emu 对系统版本有要求。如果是 ARM Linux要确认内核版本和 glibc 版本太老的系统可能缺少必要的系统调用支持。依赖方面基础的有 cmake、ninja、python3、clang 或者 gcc。FEX-Emu 推荐用 clang 编译因为它的 JIT 代码对编译器优化比较敏感。Wine 的编译需要 flex、bison、pkg-config 以及一堆图形库的开发包。DXMT 需要 Metal 的开发工具链在 macOS 上就是 Xcode Command Line Tools。我一般会先建一个独立的目录把所有源码和构建产物放在一起避免污染系统目录。然后按照 FEX-Emu、Wine、DXMT 的顺序依次编译安装因为 Wine 在编译时可以检测到 FEX-Emu 的存在从而启用一些针对性的优化。4.2 Wine 前缀的创建与关键配置Wine 的前缀prefix是它模拟的 Windows 环境目录。默认情况下Wine 会在 ~/.wine 下创建一个前缀。但我建议为每个应用或者每类应用单独建前缀这样出问题的时候不会互相影响。创建前缀的命令是WINEPREFIX/path/to/prefix wineboot -u。这里要注意如果你用的是 FEX-Emu 来跑 x86-64 的 Wine那 wineboot 本身也要通过 FEX-Emu 来执行。通常的做法是设置一个别名或者包装脚本把 wine 命令指向 FEX-Emu 加载的 Wine 二进制。前缀创建好之后有几个关键配置必须做。第一是安装中文字体把系统的中文字体复制到前缀的 Fonts 目录然后在注册表里做字体替换。第二是设置 Windows 版本有些程序需要特定的 Windows 版本号才能安装或运行可以用 winecfg 来调整。第三是配置 DLL 覆盖比如把 d3d11 设置为原生native来启用 DXMT。4.3 DXMT 的部署与验证DXMT 的部署相对直接把编译好的 DLL 文件复制到前缀的 system32 和 syswow64 目录然后在 winecfg 的 Libraries 标签页里把 d3d11、d3d10core、dxgi 等设置为 native。有些版本的 DXMT 还需要设置环境变量比如DXMT_ENABLE1或者指定 Metal 的设备索引。验证 DXMT 是否生效最简单的方法是跑一个 DirectX 诊断程序比如 dxdiag看它报告的 Direct3D 版本和渲染设备。如果显示的是 Metal 相关的信息说明 DXMT 已经接管了。另一个方法是看 Wine 的调试输出如果看到 DXMT 的日志信息也说明加载成功了。我遇到过 DXMT 加载失败的情况多数是因为 DLL 的架构不对。比如把 64 位的 DLL 放到了 syswow64 目录或者反过来。Wine 的前缀里system32 放的是 64 位 DLLsyswow64 放的是 32 位 DLL这个命名有点反直觉但必须严格遵守。4.4 常见报错与快速排查搭建过程中最常见的报错有几类。一类是缺少共享库比如error while loading shared libraries: libsomething.so。这种一般是依赖没装全用 ldd 命令看一下缺哪个库然后装上对应的开发包或者运行库。另一类是 Wine 报wine: cannot find Lxxx.exe这通常是路径问题。Wine 对路径的解析和 Linux 不一样它把 Linux 的根目录映射为 Z: 盘所以如果你用绝对路径要写成 Z:\path\to\file 的形式或者直接用相对路径。还有一类是程序启动后闪退没有任何报错。这种最难排查通常需要开 Wine 的调试输出用WINEDEBUGall来跑看最后卡在哪个调用上。但 all 的输出量非常大建议先用WINEDEBUGrelay或者seh来缩小范围。5. 那些文档里不会写的踩坑记录5.1 Wine 乱码问题的根因和修复Wine 乱码是我被问得最多的问题也是热搜里反复出现的。乱码的表现形式有好几种有的是菜单栏文字变成方块有的是对话框里的中文变成问号有的是整个界面都是乱码。根因其实就一个Wine 找不到合适的中文字体或者找到了但字体映射不对。Wine 默认会去前缀的 Fonts 目录找字体如果那里没有中文字体它就会用默认的英文字体来渲染中文结果就是方块或者乱码。修复的方法分两步。第一步是把中文字体文件比如 Noto Sans CJK、文泉驿微米黑复制到前缀的drive_c/windows/Fonts目录。第二步是在注册表里做字体替换把 Wine 默认使用的字体名比如 Tahoma、MS Shell Dlg映射到中文字体上。注册表的位置是HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes。我一般会写一个注册表文件用wine regedit导入这样比手动改方便。注册表内容大概是这样的[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes] MS Shell DlgNoto Sans CJK SC MS Shell Dlg 2Noto Sans CJK SC TahomaNoto Sans CJK SC SimSunNoto Sans CJK SC导入之后重启 Wine 程序乱码问题基本就能解决。如果还有个别地方乱码那可能是程序自己硬编码了字体名需要针对性地再做替换。5.2 国产系统上 Wine 装不上的几种情况在麒麟和统信上装 Wine我遇到过几种典型情况。一种是 apt 源里根本没有 wine 包或者只有很老的版本。这种时候要么自己编译要么找第三方维护的源。但第三方源有风险可能和系统自带的库冲突我一般建议在容器或者独立环境里装不要直接装在系统上。另一种是装上了但跑不起来报错说wine: Bad EXE format。这通常是因为系统缺少 32 位支持而 Wine 的某些组件是 32 位的。解决方法是开启系统的多架构支持装上 32 位的运行库。还有一种是权限问题Wine 的前缀目录在系统盘上普通用户没有写权限。这种要么改目录权限要么把前缀放到用户的家目录下。5.3 FEX-Emu 的 rootfs 配置陷阱FEX-Emu 在运行 x86-64 程序时需要一个 rootfs 来提供 x86-64 的系统库。这个 rootfs 可以是一个目录也可以是一个镜像文件。配置的时候最容易踩的坑是 rootfs 的路径写错或者 rootfs 里的库版本和宿主系统的库版本不匹配。我建议用 FEX-Emu 官方提供的 rootfs 构建脚本生成一个最小化的 rootfs只包含必要的库。然后把这个 rootfs 的路径写到 FEX-Emu 的配置文件里。如果程序需要额外的库再往 rootfs 里加不要一股脑把宿主系统的库都复制进去那样容易冲突。还有一个坑是 FEX-Emu 的 JIT 缓存目录。默认情况下缓存会放在用户的家目录下。如果家目录的空间不够缓存写不进去FEX-Emu 会反复重新翻译性能急剧下降。我一般会把缓存目录改到一个空间大的分区上并且定期清理旧的缓存。6. 兼容层方案的边界哪些能跑哪些别指望6.1 办公类和工具类程序的成功率根据我的经验办公类程序在 Wine 上的成功率是比较高的。像老版本的 Office、Notepad、WinRAR、一些行业专用的管理软件基本都能跑起来。这类程序的特点是图形调用简单主要依赖 GDI 和少量的 Direct2DWine 对这些 API 的支持已经相当成熟。工具类程序里依赖 .NET 的比较麻烦。Wine 自带一个 Mono 实现但版本比较老很多新版的 .NET 程序跑不了。这种时候要么装 Wine 的 .NET 运行库要么用 winetricks 来安装对应的组件。winetricks 是一个脚本工具可以自动下载和安装 Wine 需要的各种运行库非常方便但下载源有时候不稳定需要多试几次。6.2 游戏和图形密集型应用的现实表现游戏是兼容层最难的场景。2D 游戏和小型 3D 游戏通过 DXMT 或者 WineD3D 通常能跑帧率也可以接受。但大型 3D 游戏尤其是使用 DirectX 12 或者 Vulkan 的游戏在 ARM 设备上通过 FEX-Emu 跑性能损失会非常大很多时候帧率只有个位数基本没法玩。反作弊系统是另一个大问题。很多在线游戏用了内核级的反作弊这些反作弊会检测系统环境发现是 Wine 或者虚拟机就拒绝运行。这种目前没有太好的解决办法只能等反作弊厂商主动适配或者游戏厂商提供 Linux 原生版本。6.3 什么时候该放弃兼容层转向原生方案我的判断标准是如果一个程序在兼容层上跑启动时间超过 30 秒或者操作有明显卡顿或者经常崩溃那就不要死磕了。与其花大量时间调 Wine 的配置不如找这个程序的原生替代品或者用虚拟机跑一个完整的 Windows。虚拟机的好处是兼容性最好几乎什么程序都能跑。坏处是资源占用大需要额外的 Windows 授权而且图形性能通常不如 Wine 直接。在 ARM 设备上跑 x86 的 Windows 虚拟机还需要额外的指令翻译层性能更差。所以虚拟机适合偶尔用一下的场景不适合日常高频使用。7. 关于 Madeira 这类项目的个人看法折腾了这么久 Wine、FEX-Emu、DXMT我最大的体会是兼容层技术已经非常成熟了但“成熟”不等于“无脑可用”。每一个环节都有配置项每一个配置项都可能影响最终结果。Madeira 如果是一个整合项目它的价值不在于发明了新技术而在于把复杂的配置流程封装起来让普通用户不需要理解 FEX-Emu 的 JIT 原理也不需要知道 DXMT 的 DLL 该怎么放就能跑起来 Windows 程序。但封装也意味着灵活性的降低。当默认配置跑不通的时候用户还是需要知道底层发生了什么才能去调整。所以我一直建议即使用整合方案也要花点时间了解一下 Wine 的前缀结构、FEX-Emu 的缓存机制、DXMT 的加载顺序。这些知识在出问题的时候能帮你快速定位而不是对着一个报错框束手无策。另外兼容层项目的更新频率通常很高因为上游的 Wine、FEX-Emu、DXMT 都在不断迭代。今天能跑的程序明天可能因为某个组件的更新就跑不了了。我的做法是对于已经调通的环境不要轻易升级组件除非新版本解决了你正在遇到的问题。如果一定要升级先备份好前缀和配置出问题可以快速回滚。最后说一个实际的小技巧在调试 Wine 程序的时候善用WINEDEBUG环境变量和wineconsole。WINEDEBUGloaddll可以看到程序加载了哪些 DLLrelay可以看到 API 调用的顺序wineconsole可以打开一个 Wine 的命令行窗口在里面直接运行 exe 并看到输出。这些工具在排查启动失败、DLL 缺失、API 不兼容等问题时比盲目猜测有效得多。