
1. 项目缘起为什么要在 Linux 上折腾 Windows 应用第一次接触 Madeira 这个项目名很多人会以为是某个旅游地或者饮料品牌。但在我们这群长期混迹于 Linux 桌面兼容层、模拟器圈子的老玩家眼里Madeira 代表的是一个非常具体的技术方向在非 Windows 平台上把 Windows 应用跑起来而且跑得足够稳、足够像原生。它不是一个单一软件而是一整套围绕 FEX-Emu、Wine、DXMT 这些组件搭建起来的兼容方案集合目标场景覆盖 x86-64 指令翻译、DirectX 图形转换、iOS 端侧模拟等多个维度。我之所以会认真研究这套东西起因很朴素手头有一台 ARM 架构的 Linux 设备想跑几个只有 Windows 版本的行业工具又不想为了几个软件专门再背一台笔记本。市面上现成的方案要么性能拉胯要么配置复杂到劝退直到我把 FEX-Emu 和 Wine 这条链路摸清楚才算真正把这件事做成了日常可用的状态。这篇文章就是把这套方案从选型、原理、实操到排坑的完整过程摊开讲适合三类人看一是想在 Linux 上跑 Windows 软件但被各种报错劝退的普通用户二是对指令翻译、图形兼容层感兴趣想搞懂底层逻辑的开发者三是做跨端应用、需要在 iOS 或 ARM 设备上验证 Windows 生态兼容性的工程人员。核心关键词先摆出来方便你对号入座FEX-Emu负责 x86-64 到 ARM64 的指令翻译Wine负责 Windows API 到 POSIX 的转译DXMT负责 Direct3D 到 Metal 的图形转换iOS和x86-64则分别代表了移动端场景和需要被翻译的指令集架构。这四个词串起来就是 Madeira 这类项目的技术骨架。2. 整体架构拆解FEX-Emu Wine DXMT 到底怎么配合2.1 三层翻译模型的分工逻辑很多人第一次听说在 ARM 上跑 x86 的 Windows 程序脑子里是一团浆糊觉得这得多少层转换才能跑起来。其实拆开看它就是一个清晰的三层结构每一层只解决一个问题层与层之间通过标准接口对接。最底层是CPU 指令层。你的设备是 ARM64 架构但 Windows 程序编译出来的是 x86-64 指令。这两套指令集互不兼容必须有人做实时翻译。FEX-Emu 干的就是这件事它在运行时把 x86-64 指令块动态翻译成 ARM64 指令块并且做了缓存同一个代码块只翻译一次后续直接复用。这个设计思路和早期的 QEMU 用户态模拟类似但 FEX-Emu 针对游戏和桌面应用做了大量优化尤其是对 SSE、AVX 这类 SIMD 指令的处理比通用模拟器效率高出一大截。中间层是系统 API 层。Windows 程序调用的是kernel32.dll、user32.dll、ntdll.dll这些系统库Linux 上根本没有这些东西。Wine 的作用就是提供一套同名同接口的替代实现把这些调用翻译成 Linux 的 POSIX 调用。比如 Windows 的CreateFile会被 Wine 映射到 Linux 的openCreateWindow会被映射到 X11 或 Wayland 的窗口创建流程。这一层不涉及指令翻译纯粹是 API 语义的转换。最上层是图形 API 层。现代 Windows 应用和游戏大量使用 Direct3D 渲染而 Linux 和 macOS 用的是 Vulkan、OpenGL 或 Metal。DXMT 的定位就是把 D3D 调用转换成 Metal 调用专门服务于 Apple 生态。如果你是在 Linux 上用通常会走 DXVK 这条线转 Vulkan但 Madeira 这个项目名在热词里和 DXMT 强绑定说明它的重点场景之一是Apple 设备上的 Windows 应用兼容这就把 iOS 和 macOS 都拉进了讨论范围。注意这三层不是必须全部启用。如果你跑的是纯 CPU 计算类程序图形层可以不管如果你跑的是原生 ARM 的 Windows 程序比如某些 UWP 应用指令翻译层也可以跳过。实际配置时要按需裁剪别一股脑全开那样只会增加排查难度。2.2 为什么选 FEX-Emu 而不是 QEMU 或 Box64这个问题我被问过不下十次。答案不复杂但需要从实际使用场景出发去理解。QEMU 的用户态模拟qemu-user是通用方案什么架构都能模拟但它的翻译策略偏保守对 x86-64 的 SIMD 指令支持虽然完整性能却很难让人满意。我实测过同一个 Windows 程序在 qemu-user Wine 下跑启动要四十多秒界面卡顿明显换成 FEX-Emu 之后启动降到十秒出头日常操作基本跟手。差距主要来自 FEX-Emu 的块缓存机制和针对 x86-64 的专门优化它不需要像 QEMU 那样兼顾几十种架构可以把所有精力放在 x86-64 到 ARM64 这一条路径上。Box64 是另一个常见选择它的优势是轻量、启动快对很多老程序兼容性不错。但 Box64 对 AVX、AVX2 这类较新的指令集支持是逐步补齐的遇到依赖这些指令的程序就容易崩。FEX-Emu 在这方面的覆盖更完整尤其是需要跑较新版本 Windows 应用的场景FEX-Emu 的兼容性明显更好。至于 Wine 本身它和上面两个不是竞争关系而是配合关系。你可以理解为FEX-Emu 负责让 x86-64 的机器码能在 ARM 上执行Wine 负责让 Windows 的系统调用能在 Linux 上执行两者缺一不可。DXMT 则是可选的图形加速层只在需要 D3D 渲染时介入。2.3 方案选型的决策树为了让你少走弯路我把选型逻辑整理成一张表按你的实际设备和使用目标对号入座即可。设备架构目标程序类型推荐组合说明ARM64 Linuxx86-64 Windows 程序FEX-Emu Wine最典型的 Madeira 场景ARM64 LinuxARM64 Windows 程序Wine无需 FEX指令集相同跳过翻译层x86-64 Linuxx86-64 Windows 程序Wine DXVK无需指令翻译图形走 VulkanApple Siliconx86-64 Windows 程序FEX-Emu Wine DXMT图形走 Metal性能关键Apple SiliconARM64 Windows 程序Wine DXMT跳过 FEX图形仍需转换这张表的核心逻辑是指令翻译只在架构不匹配时启用图形转换只在目标平台没有 D3D 原生支持时启用。把这两个判断做对方案就成功了一半。3. 核心组件实操从零把环境搭起来3.1 FEX-Emu 的安装与根文件系统配置FEX-Emu 的安装方式取决于你的发行版。在 Debian 系上官方推荐用 apt 源直接装在 Arch 系上AUR 里有现成的包。但不管哪种方式装完之后最关键的一步是配置RootFS也就是 x86-64 的根文件系统。这一步很多人会忽略导致 Wine 跑起来找不到基础库。RootFS 的本质是一个精简的 x86-64 Linux 环境里面包含 Wine 运行所需的动态链接库和基础工具。FEX-Emu 官方提供了一个脚本FEXRootFSFetcher可以自动下载并解压合适的 RootFS。我建议直接用这个脚本别自己手动拼因为版本匹配问题很容易踩坑。# 下载并运行 RootFS 获取脚本 curl -O https://raw.githubusercontent.com/FEX-Emu/FEX/main/Scripts/FEXRootFSFetcher chmod x FEXRootFSFetcher ./FEXRootFSFetcher脚本会列出可选的 RootFS 版本一般选最新的 Ubuntu 或 Debian 基础镜像即可。下载完成后它会自动配置好环境变量。你可以用下面这条命令验证是否生效FEXRootFSFetcher --check如果输出显示 RootFS 路径和版本信息说明配置成功。接下来就可以用FEXBash进入 x86-64 环境了FEXBash进去之后你会发现自己在一个 x86-64 的 shell 里可以正常执行uname -m看到x86_64。这个环境就是后续安装 Wine 的基础。实操心得RootFS 的存放路径建议放在 SSD 上不要放机械硬盘或网络挂载盘。FEX-Emu 在运行时会频繁读取 RootFS 里的库文件IO 延迟直接影响启动速度和运行流畅度。我一开始图省事放在 NAS 挂载目录里结果启动一个记事本都要等半分钟换到本地 SSD 后降到三秒。3.2 Wine 的编译与配置要点在 FEX-Emu 的 x86-64 环境里装 Wine和直接在 x86-64 Linux 上装 Wine 流程基本一致但有几个细节需要特别注意。首先是Wine 版本选择。稳定版stable适合日常使用开发版devel对新程序兼容性更好但可能有回归问题。我的建议是先用稳定版跑通流程遇到不兼容的程序再考虑换开发版。如果你用的是发行版自带的 Wine 包注意确认它是 x86-64 版本而不是 ARM64 版本否则在 FEX 环境里跑不起来。其次是Wineprefix 的创建。Wine 会为每个前缀prefix维护一套独立的 Windows 环境包括注册表、DLL 和程序目录。默认前缀在~/.wine但我强烈建议为不同程序创建独立前缀避免 DLL 冲突。# 创建一个 64 位前缀 WINEPREFIX~/.wine-madeira WINEARCHwin64 winecfg这条命令会创建新前缀并弹出配置窗口。第一次运行会提示安装 Wine Gecko 和 Wine Mono这两个组件分别提供 HTML 渲染和 .NET 运行时支持。热词里出现的wine gecko官方正版下载和wine 乱码其实都和这一步有关如果 Gecko 没装好某些程序的内嵌网页会显示乱码或空白如果 Mono 缺失.NET 程序直接无法启动。注意Wine Gecko 和 Mono 的安装包要从官方渠道获取不要用来路不明的第三方包。安装过程中如果网络不稳定导致下载失败可以手动下载对应的.msi文件放到 Wine 提示的目录里再重新运行winecfg让它自动识别。关于wine 乱码这个问题根因通常是字体缺失或 locale 配置不对。Wine 默认使用一组 Windows 字体来渲染界面如果系统里没有对应字体中文就会显示成方块或问号。解决办法是安装winetricks然后用它装核心字体winetricks corefonts winetricks cjkfontscorefonts提供英文字体cjkfonts提供中日韩字体。装完之后乱码问题基本就消失了。如果还有个别程序乱码可以在winecfg的显示选项卡里把字体替换规则调一下把默认字体指向系统里已有的中文字体。3.3 DXMT 的部署与图形层验证DXMT 的部署相对独立它不依赖 FEX-Emu但依赖 Wine 的前缀结构。基本流程是把编译好的 DXMT 动态库放到 Wine 前缀的system32和syswow64目录里然后通过 DLL 覆盖规则让 Wine 优先加载 DXMT 而不是自带的 D3D 实现。# 假设 DXMT 编译产物在 ./dxmt-build 目录 cp ./dxmt-build/*.dll ~/.wine-madeira/drive_c/windows/system32/ cp ./dxmt-build/*.dll ~/.wine-madeira/drive_c/windows/syswow64/ # 设置 DLL 覆盖 WINEPREFIX~/.wine-madeira winetricks d3d11native d3d10corenative dxginative这几条winetricks命令的作用是告诉 Wine遇到 d3d11、d3d10core、dxgi 这些库时优先用我们放进去的 DXMT 版本而不是 Wine 自带的转译实现。这一步做对了D3D 程序才会走 Metal 路径做错了程序要么黑屏要么直接崩溃。验证 DXMT 是否生效最直接的方法是跑一个 D3D11 的测试程序然后在终端里看日志输出。如果看到类似DXMT: Initializing Metal device的字样说明图形层已经接管成功。如果没有检查 DLL 是否放对位置、覆盖规则是否生效、以及 Wine 版本是否和 DXMT 兼容。实操心得DXMT 对 Wine 版本比较敏感版本不匹配时经常出现能初始化但渲染花屏的情况。我踩过一次坑Wine 用的是 8.x 稳定版DXMT 是最新编译的结果画面撕裂严重。换成和 DXMT 发布说明里标注的 Wine 版本后问题立刻消失。所以部署前一定要看 DXMT 的 README确认它推荐的 Wine 版本范围。4. 典型场景实战从桌面应用到 iOS 端侧4.1 桌面 Windows 应用的完整跑通流程拿一个具体的例子来说假设你要在 ARM64 Linux 上跑一个只有 Windows 版本的财务软件。整个流程可以拆成六步。第一步确认 FEX-Emu 和 RootFS 就绪用FEXBash能正常进入 x86-64 环境。第二步在 FEX 环境里确认 Wine 可用执行wine --version能看到版本号。第三步创建独立前缀执行WINEPREFIX~/.wine-finance WINEARCHwin64 winecfg在弹出的窗口里把 Windows 版本设为 Windows 10。第四步安装必要的运行库用winetricks装corefonts、cjkfonts、vcrun2019、dotnet48这几个常用组件。第五步把财务软件的安装包拷进前缀的drive_c目录用wine setup.exe启动安装。第六步安装完成后用wine命令启动主程序观察是否有报错。这六步里最容易出问题的是第四步和第六步。第四步的组件安装顺序有讲究先装字体再装 VC 运行库最后装 .NET。顺序反了可能导致 .NET 安装程序找不到依赖而失败。第六步如果程序启动时报缺 DLL用winetricks补装对应的运行库即可如果报图形相关错误检查 DXMT 或 DXVK 是否配置正确。4.2 iOS 端侧模拟的可行性边界热词里出现了大量 iOS 相关内容比如ios游戏、ios自动化、ios设备模拟、ios app开发完毕如何上架这说明 Madeira 这个项目名在传播过程中和 iOS 生态产生了关联。需要说清楚的是在 iOS 设备上直接跑 Windows 程序目前没有成熟的公开方案。iOS 的沙箱机制、代码签名要求和架构限制决定了它不可能像 Linux 那样自由地加载任意兼容层。那为什么会有这些热词我的判断是它们反映的是两类真实需求。一类是开发者在 macOS 上做 iOS 开发时需要验证某些 Windows 工具的兼容性比如用 Wine 跑一些只有 Windows 版的辅助工具再把结果同步到 iOS 项目里。另一类是用户对跨端运行这个概念有期待看到 FEX-Emu 和 Wine 的组合后自然联想到能不能在 iPhone 或 iPad 上实现类似效果。现实情况是Apple Silicon 上的 macOS 可以通过 FEX-Emu Wine DXMT 跑 Windows 程序但 iOS 不行。iOS 不允许 JIT 编译而 FEX-Emu 的动态翻译依赖 JIT这一条就直接堵死了。所以如果你看到有人宣称能在 iOS 上跑 Windows 程序基本可以判断是噱头或者远程串流方案不是本地执行。注意热词里出现的ios开发者模式、ios 26.3.1怎么开发者模式、ios延迟升级这些属于 iOS 开发和设备管理的常规话题和 Madeira 的技术栈没有直接关系。把它们放在一起更多是搜索行为的相关性而不是技术上的耦合。做技术选型时要区分相关搜索和相关技术别被搜索联想带偏。4.3 麒麟、统信等国产系统上的 Wine 兼容组件热词里麒麟wine助手、统信wine windows兼容组件下载、wine deepin无法下载这几条指向的是另一个真实场景国产 Linux 发行版上的 Windows 兼容方案。麒麟和统信都提供了自己的 Wine 封装组件本质上还是 Wine但做了大量本地化适配比如预置中文字体、集成常用运行库、提供图形化的安装向导。这类组件的优势和劣势都很明显。优势是开箱即用普通用户不需要懂 FEX-Emu 和 Wine 的配置细节点几下就能装 Windows 程序。劣势是版本更新慢底层 Wine 版本往往落后于官方遇到新程序兼容性就差一些。我的建议是如果你只是偶尔跑一两个老程序用系统自带的兼容组件最省事如果你需要跑较新的程序或者对性能有要求还是自己搭 FEX-Emu Wine 这条链路更可控。wine deepin无法下载这个问题通常是软件源配置问题。Deepin 的 Wine 包在官方源里如果下载失败先检查源是否可达再检查包名是否正确。有些教程里写的包名是旧版本的新版本已经改名或合并照着旧教程操作自然会失败。5. 常见问题与排查技巧实录5.1 启动失败类问题的排查路径启动失败是最高频的问题表现从命令执行后无任何输出到弹出错误窗口后闪退都有。我整理了一条排查路径按顺序走基本能定位到根因。先看终端输出。Wine 和 FEX-Emu 都会在终端打印日志很多错误信息直接就能说明问题。如果输出里有cannot open shared object file说明缺动态库如果有Unhandled page fault说明指令翻译出了问题可能是 FEX-Emu 版本太旧如果有DXGI error说明图形层配置不对。再看前缀是否完整。用WINEPREFIX~/.wine-madeira winecfg能不能正常打开配置窗口是判断前缀健康度的快速方法。如果winecfg都打不开说明前缀本身有问题重新创建比修更省时间。最后看依赖是否齐全。用winetricks list-installed列出已安装的组件对照程序的官方要求逐个核对。缺什么补什么别一次装一堆那样出了问题反而不好定位。现象可能原因排查方法解决方式命令无输出直接退出FEX-Emu 未正确初始化检查 RootFS 路径和环境变量重新运行 FEXRootFSFetcher提示缺 DLL运行库未安装winetricks list-installed补装对应 vcrun 或 dotnet界面乱码字体缺失检查 cjkfonts 是否安装winetricks cjkfonts图形花屏DXMT 版本不匹配查看 DXMT 日志换用推荐 Wine 版本启动极慢RootFS 在慢速存储上检查 RootFS 所在磁盘迁移到本地 SSD5.2 性能调优的几个关键参数跑通之后下一步就是让它跑得更好。FEX-Emu 和 Wine 都提供了一些调优参数合理使用能明显改善体验。FEX-Emu 这边最重要的参数是块缓存大小。默认配置下翻译后的代码块缓存有限程序运行一段时间后旧块被淘汰再次执行时又要重新翻译。如果你的设备内存充足可以调大缓存上限减少重复翻译的开销。具体配置在~/.fex-emu/Config.json里把BlockCacheSize调大即可。我一般设成 512MB再大收益就不明显了。Wine 这边关键是图形后端选择。如果你不需要 D3D 渲染把图形驱动设为null可以省掉大量初始化开销。如果需要渲染优先用 DXMT 或 DXVK别用 Wine 自带的 D3D 实现后者性能差距是数量级的。还有一个容易被忽略的点是CPU 亲和性。FEX-Emu 的翻译线程和程序执行线程如果被调度到同一个小核上性能会明显下降。在支持大小核的 ARM 设备上可以用taskset把 FEX 进程绑定到大核上taskset -c 4-7 FEXBash这条命令把 FEX 限制在 4 到 7 号核心上具体编号要看你的设备拓扑。用lscpu可以查看核心分布。5.3 那些文档里不会写的坑第一个坑是路径里的空格和中文。Wine 对路径的处理不如原生 Windows 健壮如果前缀路径或程序路径里包含空格、中文、特殊字符很容易出现找不到文件的问题。我的习惯是把所有相关路径都设成纯英文、无空格比如~/.wine-madeira、~/apps/finance省去大量排查时间。第二个坑是同时运行多个前缀。Wine 的某些全局资源比如 X11 连接、音频设备在多前缀并发时可能冲突表现为后启动的程序没有声音或窗口异常。如果确实需要同时跑多个程序尽量放在同一个前缀里或者用不同的显示变量隔离。第三个坑是FEX-Emu 和系统 Wine 的混淆。有些发行版同时装了 ARM64 的 Wine 和 FEX 环境里的 x86-64 Wine如果环境变量没设对可能调用到错误的版本。判断方法很简单在 FEXBash 里执行which wine看路径是否指向 RootFS 内部在系统 shell 里执行同样的命令看是否指向/usr/bin/wine。两者要区分清楚别混用。第四个坑是日志级别。默认日志级别下很多警告信息被隐藏了排查问题时看不到关键线索。可以通过环境变量临时提高日志级别FEX_LOG_LEVELdebug WINEDEBUGall wine program.exe 21 | tee debug.log这样会把所有调试信息输出到debug.log方便事后分析。注意日志量会非常大只在排查时用日常运行别开。6. 关于 Madeira 这类方案的边界与个人体会把 FEX-Emu、Wine、DXMT 这套组合用熟之后我对跨平台兼容这件事有了更务实的认知。它不是魔法不能让你在任意设备上跑任意程序。它的能力边界由三层翻译的完整性决定指令层覆盖不到的指令集会崩API 层没实现的系统调用会失败图形层不支持的 D3D 特性会渲染异常。你能做的是在这个边界内找到最适合自己需求的组合然后把配置调优到稳定状态。我在实际使用中的体会是稳定性比性能更重要。一开始我总想着把各种加速参数拉满结果程序时不时崩溃数据丢失过一次之后我就学乖了。现在的策略是先用默认配置跑通确认功能正常再逐项调整参数每调一项就观察一段时间确认没有引入新问题再继续。这样虽然慢但省去了大量返工。另外这套方案对使用者的技术要求确实不低。你需要懂基本的 Linux 命令行操作能看懂日志会查文档遇到问题能自己搜索和实验。如果你只是想安安静静用个软件不想折腾这些那国产系统自带的 Wine 兼容组件或者虚拟机方案可能更适合你。技术选型没有优劣只有匹配不匹配。最后分享一个小技巧把常用的配置和启动命令写成脚本放在~/bin里用的时候直接调用。比如我会为每个 Windows 程序写一个启动脚本里面预设好WINEPREFIX、FEX相关环境变量和必要的参数这样每次启动都是一致的不会因为手敲命令漏了某个变量而出现玄学问题。脚本化之后这套方案的日常使用体验其实相当接近原生应用点一下就能跑起来。