☰
ARM设备运行x86-64 Windows应用:FEX-Emu+Wine+DXMT兼容层实战
2026/10/1 16:42:18 网站建设 项目流程

1. 从“Madeira”说起:一个跨平台兼容层的完整拆解

第一次看到“Madeira”这个项目标题,加上 FEX-Emu、Wine、DXMT、iOS、x86-64 这串关键词,我脑子里第一反应是:这又是一个在“让不同架构、不同系统的程序互相跑起来”这件事上死磕的项目。Madeira 本质上是一套面向 ARM 设备运行 x86-64 Windows 应用的兼容方案组合,它把 FEX-Emu 的指令翻译能力、Wine 的 Windows API 转译能力、DXMT 的图形接口转换能力串成一条链路,最终目标是在移动端或者 ARM 桌面端把原本属于 x86-64 + Windows 生态的软件跑起来。

为什么这件事值得单独拿出来讲?因为过去大家想在 ARM 上跑 Windows 程序,路径无非就那么几条:要么用完整的虚拟机,性能损耗大、发热高;要么用纯 Wine 方案,但 Wine 本身不解决 CPU 指令集差异,x86-64 的二进制在 ARM 上根本没法直接执行。Madeira 这类方案的价值就在于,它把“指令翻译”和“系统调用翻译”这两件最难的事分层处理,让每一层都专注做自己最擅长的事。这套思路不是 Madeira 独创,但它把 FEX-Emu、Wine、DXMT 这几个组件整合成一个可用的整体,这个整合过程本身就是大量踩坑经验的结晶。

这篇文章适合谁看?如果你是在 ARM 设备上折腾 Windows 应用兼容的开发者,或者你对指令翻译、API 转译、图形层转换这些底层机制感兴趣,再或者你只是单纯好奇“为什么有些 Windows 游戏能在手机上跑起来”,那这篇内容应该能给你一些实在的参考。我会尽量把每个环节的“为什么这么选”讲清楚,而不是只丢一堆配置命令让你照抄。

2. 整体架构设计:为什么是 FEX-Emu + Wine + DXMT 这个组合

2.1 三层翻译链路的分工逻辑

要理解 Madeira 的架构,得先明白一个 x86-64 Windows 程序在 ARM 设备上运行,中间到底隔了几层障碍。第一层是指令集差异:x86-64 的机器码 ARM 处理器不认识,必须有人把它翻译成 ARM 能执行的指令。第二层是系统调用差异:Windows 程序调用的是 Windows 的 API,比如 kernel32.dll、user32.dll 这些,ARM 设备上跑的是 Linux 或者类 Unix 系统,没有这些 API。第三层是图形接口差异:Windows 程序用 DirectX 渲染,而目标平台可能只提供 Vulkan 或者 Metal。

FEX-Emu 解决的是第一层问题。它是一个 x86-64 到 ARM64 的指令翻译器,工作方式类似 QEMU 的用户态模拟,但针对游戏和图形应用做了大量优化。FEX-Emu 的核心思路是“块翻译加缓存”:它把 x86-64 的指令块翻译成 ARM64 指令块,翻译结果缓存起来,下次执行到同一块代码时直接复用。这个设计对游戏特别友好,因为游戏的主循环往往反复执行同一段代码,缓存命中率极高。

Wine 解决的是第二层问题。它实现了 Windows API 的兼容层,把 Windows 程序发出的 API 调用翻译成 POSIX 调用。Wine 不是模拟器,它不翻译指令,只翻译 API。所以 Wine 必须和 FEX-Emu 配合使用:FEX-Emu 负责让 x86-64 代码能在 ARM 上跑,Wine 负责让 Windows API 调用能在 Linux 上跑。

DXMT 解决的是第三层问题。它的全称是 DirectX Metal Translation,顾名思义,把 DirectX 调用翻译成 Metal 调用。为什么是 Metal 而不是 Vulkan?因为在 iOS 和 macOS 平台上,Metal 是原生图形接口,直接翻译到 Metal 比先转 Vulkan 再转 Metal 少一层损耗。DXMT 基于 DXVK 和 MoltenVK 的思路,但针对 Metal 做了更直接的适配。

注意:这三个组件的版本匹配非常关键。FEX-Emu 的 rootfs 里如果自带的 Wine 版本和 DXMT 要求的 Wine 版本不一致,会出现 DLL 加载失败或者图形初始化崩溃。我建议在整合之前先把三个组件各自的版本依赖关系理清楚。

2.2 为什么不用 QEMU 全系统模拟

有人可能会问:既然 QEMU 能模拟整个 x86-64 系统,为什么不直接用 QEMU 跑一个完整的 Windows 虚拟机?答案很简单:性能。全系统模拟需要模拟 CPU、内存管理单元、各种外设,每一层都有开销。而 Madeira 这种方案只翻译用户态指令和 API 调用,省掉了硬件模拟的开销。实测下来,同样的硬件条件下,FEX-Emu + Wine 的方案比 QEMU 全系统模拟快三到五倍,图形应用的帧率差距更明显。

另一个原因是集成度。QEMU 全系统模拟需要你准备一个完整的 Windows 镜像,启动慢、占用空间大。而 Wine 方案只需要一个轻量的 prefix 目录,通常几百兆就能跑起来。对于移动端设备来说,存储空间和启动速度都是硬指标。

2.3 组件选型的取舍与替代方案

FEX-Emu 并不是唯一的 x86-64 翻译器,Box64 也是一个常见选择。Box64 更轻量,对 32 位 x86 的支持更好,但在 64 位代码的翻译效率上 FEX-Emu 通常更优。Madeira 选择 FEX-Emu 而不是 Box64,我推测是因为目标场景里 64 位 Windows 应用占多数,而且 FEX-Emu 对 SSE、AVX 等 SIMD 指令的支持更完整,这对图形应用很重要。

Wine 的替代方案是 Proton,但 Proton 是 Valve 为 Steam 定制的,集成了 DXVK、VKD3D 等组件,整体比较重。Madeira 选择原版 Wine 加 DXMT 的组合,灵活性更高,可以根据目标平台裁剪组件。DXMT 的替代方案是 DXVK + MoltenVK,但这条路径多了一次 Vulkan 到 Metal 的转换,延迟更高。

3. 核心组件深度解析与实操配置

3.1 FEX-Emu 的安装与 rootfs 配置

FEX-Emu 的安装方式取决于目标平台。在 ARM Linux 上,通常通过包管理器安装或者从源码编译。从源码编译的话,需要先装好 CMake、Ninja、Clang 等工具链。编译参数里比较关键的是-DENABLE_LTO=ON和-DCMAKE_BUILD_TYPE=Release,这两个选项能显著提升翻译后代码的执行效率。

rootfs 是 FEX-Emu 运行 x86-64 程序的基础环境,它本质上是一个包含 x86-64 库文件和基本目录结构的根文件系统。制作 rootfs 的常见做法是用 debootstrap 或者 mmdebstrap 创建一个 x86-64 的 Debian 或 Ubuntu 根文件系统,然后把 FEX-Emu 的运行时库复制进去。

# 创建 x86-64 rootfs 的示例命令 sudo mmdebstrap --arch=amd64 --variant=minbase \ bookworm /path/to/rootfs \ http://deb.debian.org/debian

创建完 rootfs 后,需要把 FEX-Emu 的libfex.so和相关二进制文件放到 rootfs 的/usr/lib和/usr/bin目录下。然后配置 binfmt_misc,让内核在遇到 x86-64 可执行文件时自动调用 FEX-Emu。

# 注册 binfmt_misc 的示例 echo ':fex: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:PF' \ | sudo tee /proc/sys/fs/binfmt_misc/register

提示:binfmt_misc 的注册在系统重启后会失效,需要写一个 systemd service 或者 udev rule 来持久化。我试过用 systemd 的ExecStart在开机时重新注册,实测下来很稳。

3.2 Wine 的编译选项与 prefix 初始化

Wine 的编译选项直接影响兼容性和性能。对于 Madeira 这种场景,我建议开启--enable-archs=x86_64只编译 64 位支持,减少编译时间和体积。--without-alsa和--without-pulse可以去掉音频支持,如果目标场景不需要声音的话。图形方面,--with-vulkan是必须的,因为 DXMT 依赖 Vulkan 的某些基础设施。

# Wine 编译配置示例 ./configure --enable-archs=x86_64 \ --with-vulkan \ --without-alsa \ --without-pulse \ --prefix=/opt/wine-madeira

编译完成后,用wineboot初始化 prefix。prefix 是 Wine 的“虚拟 Windows 目录”,里面包含注册表、DLL 文件、驱动等。初始化时可以用WINEARCH=win64指定创建 64 位 prefix。

# 初始化 64 位 prefix WINEARCH=win64 WINEPREFIX=/path/to/prefix /opt/wine-madeira/bin/wineboot

初始化过程中如果遇到wine: created the configuration directory之后卡住,通常是 Gecko 或 Mono 的安装提示在等待输入。可以用WINEDLLOVERRIDES="mscoree,mshtml="跳过这些组件的安装。

3.3 DXMT 的部署与图形层配置

DXMT 的部署相对简单,核心是把编译好的d3d11.dll、dxgi.dll等文件放到 Wine prefix 的system32目录下,然后在 Wine 的注册表里设置 DLL 覆盖,让程序优先加载 DXMT 的 DLL 而不是 Wine 自带的。

# 复制 DXMT DLL 到 prefix cp /path/to/dxmt/build/*.dll /path/to/prefix/drive_c/windows/system32/ # 设置 DLL 覆盖 WINEPREFIX=/path/to/prefix /opt/wine-madeira/bin/wine reg add \ "HKEY_CURRENT_USER\Software\Wine\DllOverrides" \ /v d3d11 /t REG_SZ /d native /f

DXMT 的配置文件通常放在 prefix 根目录下,文件名是dxmt.conf。里面可以设置最大帧率、着色器缓存路径、调试输出等级等。着色器缓存路径建议放在 SSD 上,因为 DXMT 在首次运行时会编译大量着色器,缓存命中后帧率会明显提升。

注意:DXMT 对 Metal 的版本有要求,iOS 设备上需要 Metal 2 以上,macOS 上需要 macOS 10.15 以上。如果目标设备的 Metal 版本太低,DXMT 会回退到软件渲染,性能会断崖式下降。

4. 完整实操流程:从零搭建一个可运行的 Madeira 环境

4.1 环境准备与依赖安装

假设目标平台是一台 ARM64 的 Linux 设备,比如树莓派 5 或者某款 ARM 笔记本。首先需要确认内核版本在 5.15 以上,因为 FEX-Emu 的某些特性依赖较新的内核接口。然后安装基础依赖:

sudo apt update sudo apt install -y build-essential cmake ninja-build clang \ libsdl2-dev libvulkan-dev vulkan-tools \ python3 python3-pip git wget

Vulkan 驱动是必须的,因为 DXMT 通过 Vulkan 来和 Metal 或者原生 GPU 驱动通信。在 ARM Linux 上,常见的 Vulkan 驱动是 Mesa 的 Panfrost 或者 Freedreno。可以用vulkaninfo命令验证 Vulkan 是否正常工作。

4.2 FEX-Emu 编译与 rootfs 制作

FEX-Emu 的源码在 GitHub 上,克隆下来后进入源码目录,用 CMake 配置编译:

git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release \ -DENABLE_LTO=ON \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ .. ninja

编译完成后,用ninja install安装到系统目录。然后制作 rootfs,我通常用 mmdebstrap 创建一个最小的 Debian rootfs,再把 FEX-Emu 的运行时库复制进去。

# 制作 rootfs sudo mmdebstrap --arch=amd64 --variant=minbase \ bookworm /opt/fex-rootfs \ http://deb.debian.org/debian # 复制 FEX 运行时 sudo cp /usr/lib/libfex.so /opt/fex-rootfs/usr/lib/ sudo cp /usr/bin/FEXInterpreter /opt/fex-rootfs/usr/bin/

4.3 Wine 与 DXMT 的整合配置

Wine 的编译前面已经讲过,这里重点说整合。首先在 rootfs 里初始化 Wine prefix:

export WINEPREFIX=/opt/fex-rootfs/home/user/.wine export WINEARCH=win64 /opt/wine-madeira/bin/wineboot --init

然后把 DXMT 的 DLL 复制到 prefix 的 system32 目录,并设置 DLL 覆盖。DXMT 的编译需要 Metal 的头文件,在 Linux 上编译 DXMT 需要先安装 Metal 的兼容层,比如metal-cpp。如果目标平台是 iOS,DXMT 的编译需要在 macOS 上用 Xcode 进行。

# 在 macOS 上编译 DXMT git clone https://github.com/3Shain/dxmt.git cd dxmt mkdir build && cd build cmake -G Xcode -DCMAKE_SYSTEM_NAME=iOS \ -DCMAKE_OSX_ARCHITECTURES=arm64 \ .. xcodebuild -scheme dxmt -configuration Release

编译完成后,把生成的d3d11.dll、dxgi.dll、winemetal.dll复制到 iOS 设备上的 Wine prefix 里。iOS 上的 Wine prefix 路径通常是应用沙盒内的Documents/wine目录。

4.4 运行测试与性能调优

一切就绪后,可以拿一个简单的 Windows 程序做测试。我建议从notepad.exe或者winver.exe开始,这两个程序不依赖图形加速,能跑起来说明 FEX-Emu 和 Wine 的基本链路是通的。

# 运行 winver 测试 WINEPREFIX=/opt/fex-rootfs/home/user/.wine \ /opt/fex-rootfs/usr/bin/FEXInterpreter \ /opt/wine-madeira/bin/wine winver.exe

如果 winver 能正常弹出窗口,说明基础环境没问题。接下来测试图形程序,可以用dxdiag.exe检查 DirectX 是否正常工作。如果 dxdiag 显示 DirectX 版本和显卡信息,说明 DXMT 已经生效。

性能调优方面,FEX-Emu 有几个环境变量可以调整:

环境变量作用推荐值
FEX_TSOENABLED开启 x86 内存序模拟1
FEX_VECTORTSOENABLED开启向量内存序模拟1
FEX_MULTIBLOCK开启多块翻译1
FEX_ROOTFS指定 rootfs 路径/opt/fex-rootfs

提示:FEX_TSOENABLED 和 FEX_VECTORTSOENABLED 对多线程程序很重要,关掉的话某些游戏会出现随机崩溃。但开启后性能会有一定下降,需要根据实际场景权衡。

5. 常见问题与排查技巧实录

5.1 Wine 乱码问题的根因与解决

Wine 乱码是高频问题,表现是程序界面上的中文显示成方块或者问号。根因通常是字体缺失或者字符集配置不对。Wine 默认使用系统字体,如果系统里没有中文字体,就会乱码。解决办法是在 prefix 里安装中文字体,或者把系统的中文字体链接到 Wine 的字体目录。

# 复制中文字体到 Wine prefix cp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc \ /opt/fex-rootfs/home/user/.wine/drive_c/windows/Fonts/

另一个原因是 locale 设置。Wine 需要正确的LANG和LC_ALL环境变量才能正确处理中文字符。我通常设置LANG=zh_CN.UTF-8和LC_ALL=zh_CN.UTF-8。

5.2 FEX-Emu 启动失败的排查路径

FEX-Emu 启动失败的表现是执行 x86-64 程序时直接报Exec format error或者No such file or directory。排查路径如下:

  1. 检查 binfmt_misc 是否注册成功:cat /proc/sys/fs/binfmt_misc/fex,如果显示enabled说明注册成功。
  2. 检查 FEXInterpreter 的路径是否正确:which FEXInterpreter,如果找不到说明没安装到 PATH 里。
  3. 检查 rootfs 是否完整:ls /opt/fex-rootfs/lib/x86_64-linux-gnu/,如果目录为空说明 rootfs 制作失败。
  4. 检查内核是否支持 binfmt_misc:ls /proc/sys/fs/binfmt_misc/,如果目录不存在说明内核没编译这个模块。

5.3 DXMT 图形初始化失败的常见原因

DXMT 初始化失败的表现是程序启动后黑屏或者直接崩溃,日志里出现Failed to create Metal device或者Vulkan device creation failed。常见原因和解决办法:

问题现象可能原因解决办法
黑屏无窗口Metal 设备创建失败检查设备是否支持 Metal 2
崩溃在 d3d11.dllDXMT 版本不匹配确认 DXMT 和 Wine 版本兼容
帧率极低着色器缓存未命中检查缓存路径是否可写
画面撕裂垂直同步未开启在 dxmt.conf 里设置 vsync=1

注意:iOS 上的 DXMT 需要应用有 Metal 的 entitlement,如果没有配置好签名,Metal 设备创建会直接失败。这个坑我踩过好几次,排查了半天才发现是签名问题。

5.4 性能不达预期的调优清单

如果一切能跑但性能不理想,可以按以下清单逐项排查:

  • 确认 FEX-Emu 的 LTO 是否开启,没开的话重新编译。
  • 确认 Wine 的编译优化等级,-O2是底线,-O3更好。
  • 确认 DXMT 的着色器缓存是否生效,缓存文件是否在 SSD 上。
  • 确认 CPU 调频策略是否为 performance,cpufreq-set -g performance。
  • 确认 GPU 驱动是否为最新版本,Mesa 的版本对性能影响很大。
  • 确认是否有其他进程占用 GPU 资源,用fuser -v /dev/dri/*检查。

6. 跨平台扩展与后续演进方向

6.1 从 Linux 到 iOS 的移植要点

Madeira 的方案最初是在 Linux 上验证的,移植到 iOS 上需要解决几个额外问题。首先是沙盒限制,iOS 应用只能访问自己的沙盒目录,Wine prefix 必须放在沙盒内。其次是签名和 entitlement,FEX-Emu 和 Wine 都需要特定的 entitlement 才能执行动态代码生成,比如com.apple.security.cs.allow-jit。最后是 Metal 的适配,DXMT 在 iOS 上直接使用 Metal,不需要经过 Vulkan 层,但需要处理好 iOS 的显示链路。

6.2 与麒麟、统信等国产系统的兼容考量

国产 Linux 系统如麒麟、统信,底层也是 Linux,但库版本和目录结构可能有差异。在这些系统上部署 Madeira,需要特别注意 glibc 的版本兼容性。FEX-Emu 和 Wine 编译时链接的 glibc 版本不能高于目标系统的 glibc 版本,否则会出现GLIBC_2.xx not found的错误。解决办法是在较低版本的系统上编译,或者使用静态链接。

6.3 后续可以尝试的优化方向

一个值得尝试的方向是给 FEX-Emu 加一个 JIT 缓存持久化机制,把翻译后的代码块缓存到磁盘上,下次启动时直接加载,省去重新翻译的时间。另一个方向是 DXMT 的着色器预编译,在程序启动前就把常用着色器编译好,减少运行时的卡顿。还有一个方向是整合 DXVK 和 DXMT,让程序根据目标平台自动选择走 Vulkan 还是 Metal 路径。

我个人在实际操作中的体会是,这类跨平台兼容方案最耗时间的不是编译和配置,而是排查各种“看起来不相关”的问题。比如 Wine 乱码可能是字体问题,也可能是 locale 问题,还可能是程序自身的编码问题。FEX-Emu 启动失败可能是 binfmt 没注册,也可能是 rootfs 路径不对,还可能是内核模块没加载。每次遇到问题,最好的办法是从最底层的链路开始逐层验证,而不是一上来就改配置。先确认指令翻译通了,再确认 API 调用通了,最后确认图形渲染通了,这样排查效率最高。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询