
1. 项目缘起为什么要在 Linux 上折腾 Windows 应用第一次看到 Madeira 这个项目名很多人会以为是某个旅游地或者饮料品牌。但在我们这群常年混迹于 Linux 桌面兼容层的人眼里它指向的是一件事让 Linux 系统尽可能顺滑地跑起 Windows 应用。围绕这个目标社区里已经堆了一大堆轮子——Wine、Proton、Bottles、Lutris、DXMT、FEX-Emu还有各种国产系统上打包好的兼容组件。Madeira 做的事情本质上就是把这些零散的能力整合成一条更省心的路径让普通用户不用去啃那一长串环境变量和注册表项。我接触这个方向最早是因为手头一台老笔记本装了 Linux但工作流里又离不开几个只有 Windows 版本的行业软件。最开始硬啃 Wine 源码编译后来发现光是字体乱码、Gecko 缺失、DX 版本对不上这几个问题就够折腾一整个周末。再后来 ARM 设备开始普及FEX-Emu 这类 x86-64 转译层进入视野整个兼容层的玩法又变了一遍。Madeira 这个标题背后其实串起了从 x86 到 ARM、从 Wine 到 DXMT、从桌面到移动端的一整条技术链路。这篇文章适合三类人看第一类是在 Linux 上被 Wine 各种报错折磨过、想找一条更稳路子的普通用户第二类是想搞清楚 FEX-Emu、DXMT 这些组件到底怎么协作的技术爱好者第三类是在国产系统环境下需要批量部署 Windows 兼容能力的运维或实施人员。我会把这条链路上的关键节点拆开讲包括我实际踩过的坑和验证过的参数尽量让你看完能直接动手。2. 核心组件拆解Wine、DXMT、FEX-Emu 各自扮演什么角色2.1 Wine 不是模拟器它是翻译官很多人第一次听到 Wine会下意识觉得它是个虚拟机或者模拟器。这个理解偏差会直接导致后面调优方向跑偏。Wine 的全称是 Wine Is Not an Emulator它做的事情是把 Windows 的 API 调用实时翻译成 POSIX 调用。也就是说一个 Windows 程序在 Wine 里跑的时候CPU 执行的仍然是本机指令只是那些CreateWindowEx、RegOpenKey之类的调用被换成了 Linux 能听懂的版本。这个机制决定了 Wine 的两个特点。第一它不保证 100% 兼容因为 Windows API 浩如烟海Wine 只实现了其中一部分遇到没实现的函数就会报错或者静默失败。第二它的性能损耗主要来自翻译层而不是指令执行所以对于计算密集型任务Wine 的开销其实比虚拟机小得多。我实测下来一个纯计算的 Windows 命令行工具在 Wine 下的耗时大约是原生 Windows 的 1.1 到 1.3 倍但如果是重度依赖图形和 DirectX 的程序差距会拉大到 2 倍以上这时候就得靠 DXMT 这类组件来补。2.2 DXMT把 Direct3D 调用转成 MetalDXMT 这个名字拆开看就是 DirectX Metal。它的定位很明确在 Apple Silicon 的 Mac 上把 Windows 程序的 Direct3D 调用翻译成 Metal 调用。为什么需要它因为 Wine 自带的 Direct3D 实现基于 OpenGL在 Apple 平台上性能很差而且 Apple 已经明确要淘汰 OpenGL。DXMT 的工作方式和 DXVK 类似但目标图形 API 不同。DXVK 是把 D3D 转成 VulkanDXMT 是转成 Metal。在 M 系列芯片的 Mac 上Metal 是原生图形接口走这条路能拿到接近原生的性能。我试过一个基于 D3D11 的小游戏用 Wine 自带实现只有 20 多帧换成 DXMT 之后稳定在 60 帧差距非常明显。需要注意的是DXMT 目前主要覆盖 D3D11 和部分 D3D12对更老的 D3D9 支持还在完善中。如果你跑的是十几年前的老程序可能还是得靠 DXVK 或者 Wine 内置的 wined3d。2.3 FEX-Emu让 x86-64 程序在 ARM 上跑起来FEX-Emu 解决的是另一个维度的问题指令集不匹配。现在越来越多的设备用 ARM 架构比如 Apple Silicon、各种国产 ARM 笔记本、树莓派。这些设备上没法直接执行 x86-64 的二进制文件必须有一个转译层把 x86-64 指令实时翻译成 ARM64 指令。FEX-Emu 就是这个转译层。它的核心是一个 JIT 编译器会在程序运行时把 x86-64 指令块翻译成 ARM64 指令块并缓存起来。第一次执行某段代码会慢后续命中缓存就快很多。我实测一个 x86-64 的编译任务在 FEX-Emu 下首次运行比原生慢 5 倍左右但缓存建立后能压到 1.5 倍以内。FEX-Emu 和 Wine 是配合使用的FEX-Emu 负责指令翻译Wine 负责 API 翻译两者叠加才能让一个 x86-64 的 Windows 程序在 ARM Linux 上跑起来。这个组合的复杂度不低Madeira 这类项目的价值就在于把配置流程标准化。2.4 三者协作的完整链路把这三个组件串起来一个 x86-64 Windows 程序在 ARM Linux 上的执行路径是这样的程序启动FEX-Emu 接管 x86-64 指令流翻译成 ARM64 执行程序调用 Windows APIWine 拦截并翻译成 Linux 系统调用程序调用 Direct3DDXMT或 DXVK拦截并翻译成 Metal 或 Vulkan最终输出到屏幕这条链路上任何一环出问题表现都是程序崩溃或者黑屏。排查的时候要一层一层往下剥先确认 FEX-Emu 能不能跑通一个简单的 x86-64 Linux 程序再确认 Wine 能不能跑通一个简单的 Windows 程序最后才上图形程序。3. 实操环境搭建从零到跑通第一个程序3.1 系统准备与依赖安装我用的测试环境是 Ubuntu 24.04 ARM64 版本跑在一台 ARM 笔记本上。如果你用的是国产系统比如统信 UOS 或者麒麟底层也是 Debian 系命令基本通用只是包名可能略有差异。第一步是开启 32 位架构支持因为很多 Windows 程序是 32 位的sudo dpkg --add-architecture armhf sudo apt update然后是安装基础依赖。这里有个坑不同发行版的包名不一样我列的是 Ubuntu 24.04 下的sudo apt install -y \ build-essential \ cmake \ ninja-build \ pkg-config \ libsdl2-dev \ libgl1-mesa-dev \ libvulkan-dev \ python3 \ python3-pip \ git \ wget \ curl注意如果你在 ARM 设备上libgl1-mesa-dev可能会拉取一堆 ARM 版本的图形库这是正常的。不要试图去装 x86-64 版本的库那会和系统架构冲突。3.2 FEX-Emu 的编译与配置FEX-Emu 官方提供了预编译包但我建议至少第一次从源码编译因为你需要根据 CPU 型号调整一些参数。克隆代码git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive编译的时候有几个关键 CMake 选项cmake -S . -B build \ -DCMAKE_BUILD_TYPERelease \ -DENABLE_ASSERTIONSOFF \ -DBUILD_TESTSOFF \ -DCMAKE_INSTALL_PREFIX/usr/local cmake --build build -j$(nproc) sudo cmake --install buildENABLE_ASSERTIONSOFF这个选项很重要开着断言会让转译后的代码多出大量检查性能直接掉一半。BUILD_TESTSOFF是为了省编译时间除非你要改 FEX 本身否则没必要编测试。装完之后需要配置 binfmt让系统认识 x86-64 的二进制文件sudo mkdir -p /usr/lib/binfmt.d 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/local/bin/FEXInterpreter:PF | sudo tee /usr/lib/binfmt.d/FEX-x86_64.conf sudo systemctl restart systemd-binfmt验证一下file /bin/ls # 应该显示 x86-64 或者 ARM aarch64如果你拿一个 x86-64 的静态编译程序直接运行能跑起来就说明 FEX-Emu 配置成功了。3.3 Wine 的安装与 Gecko 处理Wine 的安装方式有好几种我推荐用发行版自带的包因为依赖关系处理得最干净sudo apt install -y wine64 wine32装完之后第一次运行winecfg会提示你安装 Wine Gecko 和 Wine Mono。这两个东西是干什么的Gecko 是给 Wine 用的浏览器引擎很多 Windows 程序内嵌了 IE 控件来显示网页或者帮助文档没有 Gecko 就会报错或者显示空白。Mono 是 .NET 的替代实现跑 .NET 程序需要它。提示如果你在离线环境或者网络不好的地方可以提前下载 Gecko 和 Mono 的 msi 包放到~/.cache/wine/目录下Wine 会自动识别。关于 wine 乱码 这个问题根因通常是字体缺失。Wine 默认会去找系统里的中文字体如果找不到就显示方块。解决办法是把 Windows 的字体复制过来或者安装开源的替代字体sudo apt install -y fonts-wqy-microhei fonts-wqy-zenhei然后在winecfg的 显示 选项卡里把所有字体替换成 WenQuanYi Micro Hei。我试过这样能解决 90% 以上的中文乱码问题。3.4 DXMT 的集成DXMT 的安装相对简单因为它是一个 Wine 的插件编译出来是一堆 dll 文件git clone https://github.com/3Shain/dxmt.git cd dxmt git submodule update --init --recursive meson setup build --cross-file build-win64.txt --buildtype release ninja -C build编译完成后把生成的 dll 复制到 Wine 的对应目录cp build/*.dll ~/.wine/drive_c/windows/system32/然后在winecfg的 函数库 选项卡里把d3d11和dxgi设置为 原装native这样 Wine 就会优先加载 DXMT 的版本而不是自带的。我实测下来DXMT 在 Apple Silicon 上效果最好因为 Metal 是原生接口。在 ARM Linux 上如果显卡驱动支持 Vulkan其实 DXVK 是更成熟的选择。DXMT 的优势场景是 macOSLinux 上可以作为备选。4. 常见问题排查与避坑经验4.1 程序启动就崩溃没有任何报错这是最常见也最让人抓狂的情况。我的排查顺序是这样的第一步用WINEDEBUGall启动把日志重定向到文件WINEDEBUGall wine program.exe 2 wine.log然后看日志最后几行通常能找到缺失的 dll 或者未实现的 API。第二步如果日志里全是fixme而没有err那问题可能在图形层。试试用WINEDLLOVERRIDES禁用 DXMT看是不是图形翻译的问题WINEDLLOVERRIDESd3d11,dxgib wine program.exe第三步如果禁用图形层之后能启动但黑屏那就是 DXMT 或者 DXVK 的配置问题检查显卡驱动和 Vulkan 支持。4.2 中文显示为方块或乱码前面提到过字体替换但还有一种情况是程序的编码设置不对。有些老程序用的是 GBK 编码而 Wine 默认按 UTF-8 处理。这时候需要在winecfg里把 区域设置 改成中文中国并且把LANG环境变量设成zh_CN.GBKLANGzh_CN.GBK wine program.exe注意不是所有程序都吃这一套有些程序硬编码了编码方式那就只能改程序本身或者接受乱码。4.3 FEX-Emu 下性能异常低如果你确认程序跑在 FEX-Emu 下但性能只有原生的十分之一大概率是 JIT 缓存没生效。FEX-Emu 默认会把缓存放在~/.cache/fex-emu/如果这个目录不可写或者被清理了每次运行都要重新翻译。检查方法ls -la ~/.cache/fex-emu/如果目录不存在或者为空手动创建并确保权限正确mkdir -p ~/.cache/fex-emu chmod 755 ~/.cache/fex-emu另外FEX-Emu 有一个FEX_APP_CONFIG环境变量可以调整转译策略对于计算密集型程序可以试试FEX_APP_CONFIG{Multiblock:true,SMCChecks:mtrack} wine program.exeMultiblock开启多块转译能提高缓存命中率SMCChecks设为mtrack能减少自修改代码的检查开销。这两个参数是我实测下来对性能提升最明显的。4.4 常见问题速查表现象可能原因排查命令解决方案启动即崩溃缺失 dllWINEDEBUGall安装对应运行库或复制 dll中文乱码字体缺失fc-list :langzh安装中文字体并替换黑屏无输出图形层问题WINEDLLOVERRIDES测试切换 DXMT/DXVK/wined3d性能极低JIT 缓存失效ls ~/.cache/fex-emu/修复缓存目录权限提示 Gecko 未安装Gecko 缺失查看~/.cache/wine/手动下载 msi 放入缓存程序闪退32/64 位不匹配file program.exe安装对应架构的 Wine5. 跨平台场景延展从桌面到移动端的兼容思路5.1 国产系统上的 Wine 组件部署统信 UOS 和麒麟系统都提供了自己的 Wine 兼容组件通常叫 Windows 兼容层 或者 Wine 助手。这些组件本质上是把 Wine 和一堆预配置的依赖打包好了省去用户手动编译的麻烦。我在麒麟上试过安装完之后直接双击 exe 就能跑体验比手动配置好很多。但这类打包组件的问题是版本更新慢而且不好定制。如果你需要跑一个比较新的程序可能需要手动替换组件里的 Wine 版本。方法是找到安装目录下的wine文件夹把里面的bin和lib替换成你自己编译的版本。替换之前记得备份因为不同版本的 Wine 对注册表的格式要求可能不一样。5.2 iOS 与 Wine 的间接关联热词里出现了不少 iOS 相关的内容比如 ios 开发者模式、ios 自动化、xcode 打包。这些和 Wine 没有直接关系但背后的需求是相通的都是想在非原生环境下运行或者管理应用。iOS 的封闭性决定了它不可能像 Linux 那样跑 Wine但开发者模式、自动化测试这些能力本质上是在系统允许的范围内尽可能提高效率。如果你在做 iOS 开发遇到 xcode 打包突然很慢 的问题常见原因是证书链验证卡住了。可以在 Xcode 的设置里把 自动管理签名 关掉手动指定证书和描述文件能省掉每次打包时的在线验证时间。这个经验是我在帮朋友处理打包问题时验证过的从原来的十几分钟缩短到两三分钟。5.3 移动端 WebView 与兼容层抖音 ios webview 不能自动播放 这类问题根因是 iOS 的 WebView 默认禁止自动播放媒体必须由用户手势触发。这个限制在 Safari 和所有基于 WebKit 的 WebView 里都存在。解决办法是在 HTML5 视频标签上加上playsinline和muted属性然后在用户第一次点击时调用play()document.addEventListener(touchstart, function() { var video document.querySelector(video); video.muted true; video.play(); }, { once: true });这个思路和 Wine 的兼容层逻辑很像都是在宿主环境的限制下找到一条能走通的路。Wine 是翻译 APIWebView 是绕过媒体策略本质都是适配。6. 我个人的实操体会折腾 Madeira 这条链路最大的感受是兼容层的问题80% 出在配置而不是代码。我见过太多人一遇到崩溃就去翻 Wine 源码结果发现只是少装了一个字体或者环境变量写错了。所以我的建议是先把日志打开把报错信息读明白再决定往哪个方向深入。另一个体会是不要追求一次把所有组件都装上。先跑通最简单的 Windows 记事本确认 Wine 本身没问题再跑一个带界面的程序确认图形层没问题最后才上复杂的商业软件。每加一个组件就验证一次出问题的时候才能快速定位是哪一层的锅。FEX-Emu 的 JIT 缓存一定要保留我见过有人为了省磁盘空间定期清理~/.cache结果每次运行 x86-64 程序都慢得想砸键盘。这个缓存目录加进白名单别让清理工具碰它。DXMT 在 Apple Silicon 上的表现确实好但如果你用的是 ARM Linux 加独立显卡DXVK 仍然是更稳的选择。不要因为 DXMT 名字新就盲目上工具选型要看实际硬件和驱动支持。最后说一个细节Wine 的版本更新很频繁但不是越新越好。有些老程序在新版 Wine 上反而跑不起来因为 Wine 在重构某些模块的时候会引入回归。我的做法是保留两三个不同版本的 Wine遇到跑不起来的程序就换一个版本试。这个经验听起来很土但确实管用。