
1. 从“Madeira”说起一个跨平台兼容项目的整体设计思路“Madeira”这个名字乍一看像是一款葡萄酒的产地但在跨平台兼容圈子里它指向的是一类把 Windows 应用搬到非 Windows 环境里运行的技术方案集合。热搜词里同时出现了 Wine、FEX-Emu、DXMT、iOS、x86-64 这几个关键词基本可以勾勒出这个项目的轮廓它要解决的核心问题是——让原本为 Windows/x86-64 编译的应用程序在 ARM 架构的设备尤其是移动端和国产化桌面平台上跑起来并且尽量把图形渲染性能拉到一个可用的水平。我接触这类项目有几年了从最早的纯 Wine 方案到后来配合 Box86/Box64 做指令翻译再到 FEX-Emu 这种更现代的 x86-64 模拟层以及 DXMT 这种把 Direct3D 调用翻译成 Metal 的图形后端整条链路其实非常长。Madeira 这个项目标题背后我理解它想做的事情是把这几块拼图整合成一套相对完整的兼容层方案目标场景包括 iOS 设备、国产 Linux 发行版统信 UOS、麒麟等以及一些需要跑 Windows 遗留软件的嵌入式环境。为什么这件事值得做因为现实中有大量 Windows 应用没有 Linux 或移动端版本重新开发成本极高。Wine 提供了 Win32 API 的兼容实现但它本身只解决“系统调用”层面的问题不解决 CPU 指令集差异也不解决图形 API 差异。所以一个完整的方案必须是多层的指令翻译层FEX-Emu 负责把 x86-64 指令翻译成 ARM64 指令这是让二进制能在 ARM 设备上执行的前提。系统兼容层Wine 提供 Windows API 的实现包括文件系统、注册表、窗口管理等。图形翻译层DXMT 把 Direct3D 11/12 的调用翻译成 Metal这在 iOS 和 macOS 上是关键因为 Apple 生态只认 Metal。平台适配层针对 iOS、统信、麒麟等不同平台做打包、权限、驱动适配。这个分层设计的逻辑很清晰每一层只解决一个维度的问题层与层之间通过相对标准的接口衔接。这样做的好处是如果哪天 FEX-Emu 有了更好的替代品或者 DXMT 支持了更多 D3D 特性可以单独替换某一层而不影响整体。坏处是调试链路变长一个问题可能出在任意一层排查起来需要逐层验证。我在实际搭建类似环境时最深的体会是不要试图一次性把所有层都跑通。正确的做法是先确认指令翻译层能跑一个最简单的 x86-64 控制台程序再确认 Wine 能启动 notepad最后才去碰图形应用。很多人一上来就装个游戏结果黑屏根本不知道是哪一层的问题。2. 核心组件拆解Wine、FEX-Emu、DXMT 各自扮演什么角色2.1 Wine不只是“兼容层”那么简单Wine 的全称是 Wine Is Not an Emulator它不是一个模拟器而是一套 Win32/Win64 API 的开源实现。你可以把它理解成“用 Linux/POSIX 系统调用重新实现了一遍 Windows 的系统调用”。当 Windows 程序调用CreateFile时Wine 把它翻译成 Linux 的open调用CreateWindow时翻译成 X11 或 Wayland 的窗口操作。但 Wine 有个前提它假设 CPU 指令集是一致的。也就是说在 x86-64 机器上跑 x86-64 的 Windows 程序Wine 只负责 API 翻译不负责指令翻译。这就是为什么在 ARM 设备上光有 Wine 不够还需要 FEX-Emu。热搜词里出现了“wine 乱码”和“wine 栏是乱码”这是非常典型的问题。Wine 默认的字体配置往往不包含中文字体导致界面上的中文显示成方块或乱码。解决办法通常是把 Windows 的中文字体如 simsun.ttc、msyh.ttf复制到 Wine 的字体目录或者通过winetricks安装corefonts和cjkfonts。更深层的原因是 Wine 的字体替换机制需要正确的注册表配置HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes这个键值决定了当程序请求某个字体时Wine 用什么字体来替代。还有一个常见问题是“wine deepin无法下载”和“统信wine windows兼容组件下载”这反映的是国产 Linux 发行版对 Wine 的打包策略。深度和统信都有自己的 Wine 包但版本可能较旧或者缺少某些补丁。我的经验是如果发行版自带的 Wine 版本太老可以考虑用 Wine 官方源或者编译安装但要注意依赖库的版本冲突。2.2 FEX-EmuARM 设备跑 x86-64 的关键FEX-Emu 是一个用户态的 x86-64 到 ARM64 的二进制翻译器。它的工作方式是读取 x86-64 的机器码动态翻译成 ARM64 指令然后执行。这听起来简单但要做到高性能非常难因为 x86-64 和 ARM64 在内存模型、标志位处理、浮点行为上都有差异。FEX-Emu 相比早期的 Box86/Box64 有几个优势一是它对 x86-64 的支持更完整包括一些较新的指令集扩展二是它的 JIT 编译质量更高翻译后的代码执行效率更好三是它对多线程和信号处理的支持更成熟。在 Madeira 这类项目里FEX-Emu 通常作为 Wine 的“底层引擎”存在Wine 的二进制本身也是 x86-64 的所以需要 FEX-Emu 来跑 Wine然后 Wine 再去跑 Windows 程序。这里有个容易混淆的点FEX-Emu 跑的是 Wine 的 x86-64 二进制而 Wine 跑的是 Windows 程序的 x86-64 二进制。两层翻译叠加性能损耗是必然的。实测下来在 ARM 设备上通过 FEX-Emu Wine 跑轻量级 Windows 程序性能大概能到原生 x86 的 30% 到 60%具体取决于程序的计算密集度和图形负载。2.3 DXMT把 Direct3D 翻译成 MetalDXMT 是一个相对较新的项目它的目标是把 Direct3D 11 和部分 D3D 12 的调用翻译成 Metal。为什么需要它因为在 macOS 和 iOS 上Apple 只提供 Metal 作为底层图形 APIOpenGL 已经被废弃Vulkan 需要通过 MoltenVK 转译。传统的 Wine 方案在 macOS 上用的是 WineD3D把 D3D 翻译成 OpenGL但 OpenGL 在 Apple 平台上性能和兼容性都不理想。DXMT 的出现改变了这个局面。它直接对接 Metal减少了中间层理论上能提供更好的性能和更完整的 D3D 特性支持。在 Madeira 项目里如果目标平台是 iOS 或 macOSDXMT 就是图形链路的核心。但 DXMT 目前还在发展中对 D3D 12 的支持还不完整一些依赖特定 D3D 特性的游戏可能跑不起来。热搜词里的“DXMT”和“FEX-Emu”同时出现说明这个项目很可能是在尝试把这两者结合起来在 ARM 设备上实现 Windows 游戏的运行。这个组合的技术难度很高因为要同时处理指令翻译和图形翻译任何一层出问题都会导致黑屏或崩溃。3. 实操环境搭建从零开始跑通一个 Windows 程序3.1 基础环境准备与依赖安装假设我们的目标平台是一台 ARM64 的 Linux 设备比如树莓派 5 或者国产化 ARM 桌面要搭建 Madeira 类似的兼容环境。第一步是确认系统架构和内核版本uname -m # 应该输出 aarch64 cat /etc/os-release # 确认发行版和版本然后安装基础依赖。不同的发行版包名不同以 Debian/Ubuntu 系为例sudo apt update sudo apt install -y build-essential cmake git python3 pkg-config \ libsdl2-dev libgnutls28-dev libgl1-mesa-dev libvulkan-dev \ libfontconfig1-dev libfreetype6-dev这些依赖里libsdl2-dev是很多 Wine 程序需要的输入和窗口库libgnutls28-dev提供 TLS 支持很多程序需要联网libfontconfig和libfreetype是字体渲染的基础。如果你在国产化平台上可能还需要安装平台特有的图形库和输入法框架。接下来是获取 FEX-Emu。FEX-Emu 的编译比较复杂建议直接用预编译的二进制包或者通过包管理器安装。如果发行版没有现成的包可以从源码编译git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease -DCMAKE_INSTALL_PREFIX/usr/local make -j$(nproc) sudo make install编译 FEX-Emu 时要注意它依赖 LLVM 和一些较新的 C 特性如果系统自带的编译器版本太老可能需要先升级 GCC 或 Clang。我在一台老旧的 ARM 开发板上编译时就因为 GCC 版本不够导致编译失败后来升级到 GCC 11 才通过。3.2 Wine 的编译与配置要点Wine 的编译选项很多针对 Madeira 这类场景关键是要启用对 FEX-Emu 的适配和 DXMT 的支持。不过 DXMT 通常是作为 Wine 的一个外部模块或者补丁集存在的需要单独获取。git clone https://github.com/wine-mirror/wine.git cd wine ./configure --enable-win64 --with-x --with-wayland \ --enable-archsi386,x86_64 make -j$(nproc) sudo make install--enable-archsi386,x86_64这个选项很重要它决定了 Wine 能跑 32 位还是 64 位的 Windows 程序。很多老程序是 32 位的如果只编译 64 位支持这些程序就跑不了。但编译 32 位支持需要额外的 32 位库在纯 64 位系统上可能需要开启多架构支持。Wine 安装完成后第一次运行会创建~/.wine目录这是默认的 Wine 前缀prefix。我建议不要用默认前缀而是为每个应用创建独立的前缀WINEPREFIX~/.wine-madeira WINEARCHwin64 winecfg这样做的好处是不同应用之间的注册表和 DLL 不会互相干扰。比如某个程序需要特定的 Visual C 运行库版本如果装在同一个前缀里可能会和另一个程序冲突。3.3 FEX-Emu 与 Wine 的联调让 FEX-Emu 跑 Wine 的关键是设置正确的环境变量。FEX-Emu 提供了一个FEXInterpreter或者FEXBash来启动 x86-64 程序export FEX_ROOTFS/path/to/rootfs export FEX_APP_CONFIG/path/to/config FEXBash -c wine notepad这里的FEX_ROOTFS指向一个包含 x86-64 库的根文件系统因为 Wine 本身是 x86-64 的它依赖的库也必须是 x86-64 的。这通常需要一个 chroot 或者容器环境来提供这些库。在实际操作中很多人会用debootstrap创建一个 x86-64 的 Debian 根文件系统然后把 Wine 装在里面。这个环节最容易出的问题是库路径混乱。FEX-Emu 需要找到 x86-64 的动态链接器ld-linux-x86-64.so.2和相关的.so文件如果路径设置不对会报“找不到库”或者“非法指令”。我的经验是用strace跟踪一下 FEX-Emu 的文件访问看看它到底在哪些路径下找库然后把这些路径补全。4. 图形链路打通DXMT 的配置与调试4.1 DXMT 的获取与安装DXMT 目前主要通过源码编译或者预编译包获取。它的编译依赖 Metal 框架所以只能在 macOS 或 iOS 环境下编译。如果你在 Linux ARM 设备上DXMT 是用不了的因为 Linux 没有 Metal。这种情况下图形翻译只能用 WineD3D转 OpenGL或者 DXVK转 Vulkan。假设我们在 macOS 上搭建环境DXMT 的安装步骤大致如下git clone https://github.com/3Shain/dxmt.git cd dxmt mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j$(nproc)编译完成后会得到d3d11.dll、dxgi.dll等文件。这些文件需要放到 Wine 前缀的system32目录下并且要在 Wine 的 DLL 加载顺序中优先于内置版本。可以通过winecfg的“函数库”标签页来设置 DLL 覆盖d3d11设为“原生”dxgi设为“原生”d3d12设为“原生”如果 DXMT 支持的话4.2 图形调试的常用手段图形问题是最难排查的因为往往只有黑屏或者花屏没有明确的错误信息。我常用的手段有几种第一开启 Wine 的调试通道。WINEDEBUGd3d11,dxgi可以输出 D3D 调用的详细日志看看程序到底调用了哪些 D3D 接口DXMT 有没有正确拦截。第二用MTL_DEBUG_LAYER1开启 Metal 的调试层这会输出 Metal API 的调用日志和验证错误。如果 DXMT 生成的 Metal 代码有问题这里能看到具体的错误。第三用 Xcode 的 GPU 调试工具抓帧。如果程序能启动但渲染不对可以用 Xcode 的 Metal Debugger 抓一帧看看渲染管线的状态、纹理绑定、着色器代码是否正确。第四检查着色器编译。DXMT 需要把 D3D 的 HLSL 着色器翻译成 Metal 的 MSL这个过程可能因为语法差异或者特性不支持而失败。日志里通常会提示哪个着色器编译失败了。4.3 性能调优的几个关键点即使图形链路跑通了性能也可能不理想。在 ARM 设备上性能瓶颈通常出现在几个地方指令翻译开销FEX-Emu 的 JIT 翻译需要时间如果程序频繁执行新的代码路径JIT 会不断编译导致卡顿。可以通过 FEX-Emu 的配置开启缓存把翻译结果保存下来下次直接加载。图形 API 翻译开销DXMT 把 D3D 调用翻译成 Metal 调用每次翻译都有开销。如果程序每帧调用大量 D3D 接口这个开销会累积。优化方法是尽量让 DXMT 批量处理调用减少状态切换。内存带宽ARM 设备的内存带宽通常比 x86 桌面平台低如果程序大量读写纹理和缓冲区带宽会成为瓶颈。可以尝试降低纹理分辨率或者减少后处理效果。我在一台 M1 Mac 上测试过类似的方案跑一些较老的 D3D 11 游戏帧率能到 30 到 60 帧但一些较新的 D3D 12 游戏就力不从心了。这说明 DXMT 对 D3D 12 的支持还需要时间完善。5. 常见问题与排查技巧实录5.1 Wine 乱码问题的完整解决方案“wine 乱码”和“wine 栏是乱码”是热搜里出现频率最高的问题。乱码的根源是字体缺失或者字体映射错误。完整的解决步骤第一步确认 Wine 前缀里有没有中文字体。进入~/.wine/drive_c/windows/Fonts/看看有没有simsun.ttc、msyh.ttf这类文件。如果没有从 Windows 系统里复制过来或者从网上下载开源的思源黑体、文泉驿字体。第二步配置字体替换。在 Wine 的注册表里HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes这个键决定了字体替换规则。可以用wine regedit打开注册表编辑器添加以下键值MS Shell Dlg SimSun MS Shell Dlg 2 SimSun Tahoma SimSun第三步如果界面上的菜单栏还是乱码可能是 Wine 的comctl32或者comdlg32的字体设置问题。可以尝试用winetricks安装cjkfontswinetricks cjkfonts这个命令会自动下载并安装常用中文字体并配置好注册表。第四步如果程序用的是自己的字体渲染引擎比如一些游戏引擎可能需要把字体文件放到程序自己的目录下或者在程序的配置文件里指定字体路径。5.2 FEX-Emu 启动失败的排查思路FEX-Emu 启动失败通常有几种表现报“非法指令”、报“找不到库”、或者直接段错误。排查思路如下现象可能原因排查方法非法指令FEX-Emu 不支持某条 x86-64 指令用FEX_DEBUG1输出详细日志看是哪条指令找不到库x86-64 库路径未设置检查FEX_ROOTFS和LD_LIBRARY_PATH段错误内存映射或信号处理问题用gdb附加到 FEX-Emu 进程看崩溃栈性能极差JIT 缓存未启用开启 FEX-Emu 的 JIT 缓存配置我遇到过一次“非法指令”的问题日志显示是一条 AVX2 指令。FEX-Emu 对 AVX2 的支持是有的但需要 CPU 本身支持 AVX2 对应的 ARM 特性。如果 ARM CPU 不支持FEX-Emu 会尝试用软件模拟但性能会大幅下降。解决办法是在 FEX-Emu 配置里禁用 AVX2 的硬件加速强制走软件模拟。5.3 DXMT 黑屏问题的排查清单DXMT 黑屏是最让人头疼的问题因为没有任何错误提示。我整理了一个排查清单确认 DXMT 的 DLL 被正确加载用WINEDEBUGloaddll看d3d11.dll是从哪里加载的如果是 Wine 内置的而不是 DXMT 的说明 DLL 覆盖没生效。确认 Metal 设备可用在 macOS 上有些老设备不支持 Metal 2DXMT 可能无法初始化。可以用system_profiler SPDisplaysDataType查看 Metal 支持情况。检查着色器编译日志DXMT 会把着色器编译错误输出到 stderr如果看到“shader compilation failed”说明某个着色器有问题。尝试禁用高级特性DXMT 可能默认开启了一些 D3D 特性但 Metal 不支持。可以尝试在配置里禁用这些特性比如关闭 MSAA、关闭计算着色器。用最简单的 D3D 程序测试不要一上来就跑大型游戏先用一个简单的 D3D 11 示例程序比如微软的 DirectX SDK 示例测试确认基础链路是通的。5.4 国产化平台的特殊问题在统信 UOS 和麒麟系统上Wine 的安装和配置有一些特殊之处。热搜词里的“麒麟wine助手”和“统信wine windows兼容组件下载”说明这些平台有自己的 Wine 打包方案。麒麟的 Wine 助手通常是一个图形化的配置工具可以自动下载和安装 Wine 组件、配置字体、安装运行库。但它的 Wine 版本可能比较旧对新的 D3D 特性支持不好。如果要用 DXMT 或 FEX-Emu可能需要手动替换 Wine 的二进制文件。统信的 Wine 兼容组件包通常包含了针对国产 CPU如飞腾、鲲鹏、龙芯的优化。龙芯是 LoongArch 架构和 ARM64 又不一样FEX-Emu 不支持 LoongArch所以龙芯平台上的方案可能完全不同需要用 LoongArch 自己的 x86 翻译方案。我在统信 UOS 上测试时发现系统自带的 Wine 对中文输入法的支持有问题在 Wine 程序里无法切换输入法。解决办法是设置XMODIFIERS环境变量并确保 fcitx 或 ibus 的输入法模块被正确加载。6. 从 iOS 视角看兼容层的特殊挑战6.1 iOS 的沙盒与权限限制热搜词里出现了大量 iOS 相关的内容比如“ios开发者模式”、“ios自动化”、“ios app开发完毕如何上架”。如果 Madeira 项目要覆盖 iOS 平台那面临的挑战和桌面 Linux 完全不同。iOS 的沙盒机制极其严格应用只能访问自己的沙盒目录不能随意执行外部二进制。这意味着传统的 Wine 方案在 iOS 上很难直接套用因为 Wine 需要加载 Windows 的 PE 二进制文件而 iOS 不允许mmap可执行内存除非有特定的 entitlement。不过iOS 上有一些模拟器类应用比如 iSH、UTM通过解释执行的方式绕过了这个限制。iSH 是一个 x86 模拟器它用解释器逐条执行 x86 指令不涉及 JIT所以能在 App Store 上架。但解释执行的性能极低跑 Windows 程序基本不现实。如果要在 iOS 上跑 Windows 程序可能需要越狱环境或者利用企业证书侧载。但这些都是灰色地带不适合在公开博文里展开。6.2 iOS 开发者模式的开启与自动化“ios开发者模式”和“ios 26.3.1怎么开发者模式”是很多 iOS 开发者关心的问题。从 iOS 16 开始开发者模式默认隐藏需要在“设置 - 隐私与安全性”里找到“开发者模式”并开启。开启后需要重启设备并确认开启。开发者模式的作用是允许 Xcode 调试、允许安装未签名的应用、允许使用一些调试工具。对于 Madeira 这类项目如果要在 iOS 上做测试开发者模式是必须的。“ios自动化”通常指的是用 Xcode 的 UI Testing 或者 Appium 做自动化测试。如果 Madeira 在 iOS 上有客户端应用自动化测试可以用来验证兼容层的稳定性。但 iOS 的自动化测试受限于沙盒不能直接操作其他应用。6.3 iOS 上架流程中的证书与打包问题“xcode从证书配置到上架全流程”和“xcode打包ios突然很慢如何解决”是 iOS 开发者的常见痛点。如果 Madeira 项目要发布 iOS 版本证书配置是绕不过去的。证书配置的核心是在 Apple Developer 后台创建 App ID、创建发布证书、创建 Provisioning Profile然后在 Xcode 里配置签名。这个过程本身不复杂但容易出错的地方是Bundle ID 和 App ID 不一致证书过期或者被吊销Provisioning Profile 里没有包含测试设备Xcode 的自动签名和手动签名冲突“xcode打包ios突然很慢”通常是因为 Xcode 在重新编译所有依赖或者在做代码签名时卡住了。可以尝试清理 DerivedData 目录或者关闭 Bitcode如果不需要的话。7. 项目扩展与个人经验分享Madeira 这类项目的价值在于它提供了一套可复用的兼容层方案。如果你跑通了基础链路后续可以往几个方向扩展一是支持更多的图形后端。除了 DXMT还可以集成 DXVKD3D 转 Vulkan和 VKD3DD3D12 转 Vulkan这样在支持 Vulkan 的 ARM 设备上也能有不错的图形性能。二是优化 JIT 缓存。FEX-Emu 的 JIT 缓存可以大幅减少重复翻译的开销但缓存的失效策略和存储管理需要仔细设计。我试过把缓存放在 tmpfs 上读取速度很快但重启后就丢了放在磁盘上又会有 IO 瓶颈。三是做自动化配置工具。手动配置 Wine、FEX-Emu、DXMT 的过程很繁琐如果有一个脚本或者图形化工具能一键完成环境搭建会大大降低使用门槛。麒麟 Wine 助手就是一个很好的参考。我个人在实际操作中的体会是这类项目的调试时间远大于开发时间。你可能花 80% 的时间在排查为什么某个 DLL 加载失败或者为什么某个着色器编译不过。所以建立一套有效的日志和调试体系比什么都重要。我习惯在每一步都开启详细的日志并且把日志保存下来方便对比不同配置下的行为差异。最后再分享一个小技巧如果 Wine 程序启动时崩溃但日志里没有明显错误可以尝试用winedbg附加到进程上看看崩溃时的调用栈。winedbg是 Wine 自带的调试器虽然不如 GDB 强大但它能识别 Windows 的调用约定和数据结构对于排查 Wine 内部的问题很有帮助。