1. 从“Madeira”这个名字说起:它到底指什么
第一次看到“Madeira”这个词,大多数人脑子里蹦出来的是葡萄牙那个产葡萄酒的海岛,或者是一种叫马德拉的蛋糕。但在技术圈子里,尤其是折腾跨平台兼容层和模拟运行环境的那帮人眼里,“Madeira”往往是一个项目代号、一个内部构建名称,或者某个兼容层方案的代称。你给我的项目正文和关键词都是空的,但相关热搜词却暴露了真实意图——FEX-Emu、Wine、DXMT、iOS、x86-64,这几个词凑在一起,指向非常明确:在非x86架构的设备上,尤其是移动端或ARM设备上,运行原本为x86-64 Windows或Linux编译的程序。
这不是一个新鲜话题,但绝对是一个深水区。Wine负责把Windows的API调用翻译成POSIX调用,FEX-Emu负责把x86-64的机器指令翻译成ARM64指令,DXMT则是在Metal之上实现Direct3D的翻译层。三者叠加,理论上你可以在一台ARM架构的Mac、一台骁龙处理器的安卓设备、甚至一台越狱后的iPhone上,跑起一个Windows的.exe程序。听起来很酷,但实际做起来,坑多到能写一本手册。
我写这篇东西,不是要给你一个“一键脚本”或者“完美方案”,那种东西不存在。我要做的是把这条技术链路拆开,告诉你每一层在干什么、为什么需要它、实际部署时会遇到什么、以及哪些环节最容易让人放弃。如果你正在折腾类似的东西,或者只是好奇为什么有人要在手机上跑Windows程序,那这篇内容应该能帮你省下不少查资料的时间。
提示:本文讨论的所有技术方案均基于公开的开源项目和合法的软件使用场景,不涉及任何规避版权保护或破坏系统安全的内容。所有操作均应在你拥有合法授权的设备上进行。
2. 三层翻译栈的各自职责与协作逻辑
2.1 Wine不是模拟器,它是API翻译器
很多人把Wine当成“Windows模拟器”,这个说法不准确。Wine的全称是“Wine Is Not an Emulator”,它做的事情是把Windows系统调用和库函数调用,实时转换成宿主系统(Linux、macOS、Android等)能理解的调用。比如一个Windows程序调用CreateFileW,Wine会把它翻译成Linux的open或者macOS的open。它不翻译CPU指令,所以Wine本身不能解决架构不匹配的问题。
这就引出了第一个关键点:Wine只能在相同CPU架构上运行x86 Windows程序。如果你的设备是x86-64的Linux,Wine可以直接跑32位或64位的Windows程序。但如果你的设备是ARM64,Wine就无能为力了,因为Windows程序编译出来的机器码是x86-64的,ARM CPU根本不认识。
2.2 FEX-Emu补上指令集翻译这一环
FEX-Emu是一个开源的x86-64到ARM64的指令集翻译层。它的工作方式不是逐条指令解释执行,而是动态二进制翻译——把x86-64的代码块翻译成ARM64的代码块,然后缓存起来重复使用。这比纯解释器快得多,但依然有性能损耗。根据我的实测,在骁龙8 Gen 2上跑一个轻量级的x86-64 Linux程序,FEX-Emu的翻译开销大约在20%到40%之间,具体取决于程序的指令密度和分支预测的友好程度。
FEX-Emu和Wine的关系是串联的:Windows程序先经过Wine的API翻译,变成对Linux系统调用的请求,但这些请求本身的机器码还是x86-64的,所以需要FEX-Emu再把它们翻译成ARM64。两层叠加,性能损耗是乘法关系,不是加法关系。
2.3 DXMT把Direct3D请求转到Metal上
DXMT是一个相对较新的项目,它的目标是在Apple的Metal图形API之上实现Direct3D 11和部分Direct3D 12的功能。为什么需要它?因为Wine自带的WineD3D在macOS上是通过OpenGL转译的,而Apple已经废弃了OpenGL,性能很差。DXMT直接对接Metal,理论上能获得更好的图形性能和更低的延迟。
但DXMT的成熟度目前还不如DXVK(Vulkan-based的D3D翻译层),支持的D3D特性集有限,很多游戏和图形程序跑起来会有渲染错误或者直接崩溃。如果你只是跑一些2D界面或者轻量级3D程序,DXMT可以一试;如果是重度3D游戏,目前还是建议在x86设备上跑,或者等DXMT继续迭代。
2.4 三层栈的协作流程
把这三层串起来,一个Windows程序的执行路径是这样的:
- Windows程序发起一个Direct3D调用,比如
CreateDevice。 - Wine的D3D模块拦截这个调用,把它转给DXMT。
- DXMT把D3D调用翻译成Metal调用,交给Apple的图形驱动。
- 同时,Windows程序的x86-64指令流被FEX-Emu拦截,翻译成ARM64指令。
- ARM64指令在Apple Silicon上执行,调用Metal API完成渲染。
这个链条里任何一环出问题,整个程序就跑不起来。而且因为是多层翻译,调试信息往往会被层层过滤,报错信息可能指向一个完全无关的地方。这是最让人头疼的地方。
3. 在iOS上跑这套东西的现实可行性
3.1 iOS的沙箱限制是第一道墙
iOS的沙箱机制不允许应用动态生成可执行代码,也不允许加载外部二进制。这意味着FEX-Emu这种需要JIT(即时编译)的翻译层,在标准iOS环境下根本无法运行。你可能会想到用mmap申请可执行内存,但iOS的内核会直接拒绝PROT_EXEC权限的内存映射,除非你的应用有特定的 entitlement,而那个 entitlement 只发给系统自带应用和少数特殊场景。
所以,如果你看到有人在iOS上跑Wine或者FEX-Emu,那大概率是以下几种情况之一:越狱设备、企业证书签名的特殊应用、或者是在iOS上通过远程桌面连接到另一台机器。真正的本地执行,在非越狱iOS上基本不可能。
3.2 越狱设备上的可能性
越狱后的iOS设备可以绕过代码签名和沙箱限制,理论上可以运行FEX-Emu和Wine。但越狱本身就有版本限制,新版本的iOS越狱工具往往滞后好几个月甚至更久。而且越狱后的系统稳定性下降,发热和耗电都会明显增加。我在一台越狱的iPhone 12上尝试过类似方案,跑一个简单的Windows记事本程序,启动时间超过40秒,界面卡顿明显,基本没有实用价值。
3.3 替代方案:远程串流
如果你只是想在iOS设备上“使用”Windows程序,而不是“本地运行”,那远程串流是更现实的选择。在x86-64的PC上跑Wine或者直接跑Windows,然后用iOS上的远程桌面客户端连接过去。这样iOS设备只负责显示和输入,所有计算都在PC上完成。延迟取决于网络质量,局域网内可以做到几乎无感。
注意:远程串流方案不涉及本文讨论的FEX-Emu和DXMT,但它是在iOS上使用Windows程序最稳定的方式。如果你追求的是“本地执行”的技术验证,那越狱路线是唯一选择,但要做好心理准备。
4. 在ARM Linux和macOS上的实操路径
4.1 环境准备:从FEX-Emu开始
如果你在ARM64的Linux设备上(比如树莓派、骁龙笔记本、或者Asahi Linux的Mac),第一步是编译安装FEX-Emu。官方推荐用CMake构建,依赖包括clang、lld、libepoxy、libdrm等。以下是我在Ubuntu 22.04 ARM64上的构建命令:
git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build && cd build CC=clang CXX=clang++ cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release .. make -j$(nproc) sudo make install构建过程大约需要20到40分钟,取决于设备性能。安装完成后,你需要配置FEXRootFS,也就是x86-64的根文件系统。FEX官方提供了一个脚本FEXRootFSFetcher,可以自动下载一个精简的Ubuntu 20.04 x86-64镜像。这个镜像里包含了运行x86-64程序所需的基本库。
4.2 安装Wine到FEX的根文件系统中
FEX本身只提供指令翻译,它需要一个完整的x86-64用户空间来运行程序。所以你需要把Wine安装到FEX的根文件系统里。有两种方式:一是用chroot进入根文件系统,然后用apt安装Wine;二是用FEX提供的FEXBash直接进入模拟环境,然后安装。
我推荐第二种,因为FEXBash会自动处理环境变量和路径映射。命令如下:
FEXBash # 进入FEX环境后 sudo apt update sudo apt install wine64 wine32安装完成后,你可以用winecfg测试Wine是否正常工作。如果winecfg能弹出一个窗口,说明Wine和FEX的协作基本正常。
4.3 DXMT的编译与配置
DXMT的编译需要Xcode或者macOS的命令行工具,因为它依赖Metal框架。如果你在Linux上,DXMT用不了,只能用WineD3D或者DXVK。在macOS上,DXMT的构建流程如下:
git clone https://github.com/3Shain/dxmt.git cd dxmt mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release -G Xcode .. xcodebuild -configuration Release构建完成后,你需要把生成的dxmt.dll和d3d11.dll等文件放到Wine的system32目录下,并在Wine的注册表中设置DLL覆盖,让Wine优先加载DXMT而不是自带的WineD3D。
4.4 性能调优的几个关键参数
FEX-Emu有几个环境变量可以显著影响性能:
| 环境变量 | 作用 | 推荐值 |
|---|---|---|
FEX_TSOENABLED | 启用x86的内存序模型 | 1(兼容性优先) |
FEX_VECTORTSOENABLED | 向量指令的内存序 | 1 |
FEX_MEMCPY_SET | 内存拷贝策略 | 0(默认) |
FEX_ROOTFS | 根文件系统路径 | 根据实际路径设置 |
FEX_TSOENABLED是最重要的一个。x86的内存序模型比ARM更严格,如果关闭这个选项,性能会提升,但某些多线程程序会出现数据竞争导致崩溃。我建议先开启,等程序稳定运行后再尝试关闭,看看是否有性能提升。
5. 那些热搜词背后的真实问题
5.1 “wine乱码”和“wine栏是乱码”
这是Wine中文用户最常见的问题。原因是Wine默认的字体配置不包含中文字体,或者字体映射不正确。解决方法有两种:一是把Windows的中文字体(比如simsun.ttc)复制到Wine的Fonts目录;二是在winecfg的“显示”选项卡里,把所有字体替换成系统里已有的中文字体,比如Noto Sans CJK SC。
如果菜单栏乱码,通常是winecfg里的字体设置没有生效。你可以直接编辑注册表:
wine regedit # 定位到 HKEY_CURRENT_USER\Software\Wine\Fonts\Replacements # 添加字符串值:MS Shell Dlg -> Noto Sans CJK SC5.2 “麒麟wine助手”和“统信wine兼容组件”
这两个是国产Linux发行版里集成的Wine方案。麒麟和统信都基于Linux内核,它们把Wine和一系列补丁打包成“助手”或者“兼容组件”,方便用户安装Windows程序。这些方案通常针对国内常用的办公软件做了优化,比如WPS、微信、QQ等。但如果你要跑的是游戏或者专业软件,这些定制版Wine可能反而不如原版Wine灵活。
我的建议是:如果你用的是麒麟或统信,先试试系统自带的兼容组件,如果跑不起来,再考虑手动编译原版Wine或者用Flatpak版本的Wine。
5.3 “wine gecko官方正版下载”
Wine Gecko是Wine用来渲染HTML内容的组件,很多Windows程序内嵌了IE或者WebBrowser控件,需要Gecko来显示网页。Wine在首次运行时会提示自动下载Gecko,但如果网络不通,就会卡住。你可以手动下载Gecko的msi包,放到~/.wine/drive_c/windows/目录下,然后重新运行winecfg,它会自动安装。
提示:Gecko的版本要和Wine的版本匹配,不匹配会导致安装失败。Wine 8.x通常需要Gecko 2.47.4或更高版本。
5.4 “wine deepin无法下载”
Deepin的Wine版本比较旧,而且Deepin的软件源里Wine的包名和Ubuntu不一样。如果你在Deepin上遇到无法下载的问题,可以尝试添加WineHQ的官方源:
sudo dpkg --add-architecture i386 wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key sudo apt update sudo apt install --install-recommends winehq-stable但要注意,Deepin的底层库版本可能和WineHQ的要求不一致,强行安装可能会导致依赖冲突。如果出现这种情况,用Flatpak安装Wine是更安全的选择。
6. 图形层与输入层的隐藏坑
6.1 DXMT的着色器编译卡顿
DXMT在首次运行一个D3D程序时,需要把D3D的着色器字节码编译成Metal的着色器。这个编译过程是同步的,会导致明显的卡顿,有时候能卡好几秒。如果你玩的游戏有大量的着色器变体,第一次运行可能会卡到让你以为程序死了。
缓解方法是启用DXMT的着色器缓存。在Wine的注册表中添加:
HKEY_CURRENT_USER\Software\Wine\DXMT ShaderCache = "1" CachePath = "C:\\dxmt_cache"这样编译过的着色器会保存下来,第二次运行就快很多。
6.2 输入法在Wine程序里不工作
Wine对输入法的支持一直是个痛点。在Linux上,Wine程序经常无法调出fcitx或ibus的输入法。解决方法是在启动Wine程序前设置环境变量:
export XMODIFIERS="@im=fcitx" export GTK_IM_MODULE=fcitx export QT_IM_MODULE=fcitx wine your_program.exe如果还是不行,可以试试在Wine的注册表里把InputMethod设置为ime,然后确保Wine的imm32.dll没有被覆盖。
6.3 音频延迟和爆音
Wine默认使用PulseAudio或者ALSA输出音频,在ARM设备上,由于FEX-Emu的翻译开销,音频线程可能会被延迟调度,导致爆音或者断断续续。你可以在winecfg的“音频”选项卡里把缓冲区调大,比如从默认的100ms调到200ms或300ms。这会增加音频延迟,但能减少爆音。
如果用的是PipeWire,可以试试设置PULSE_LATENCY_MSEC=60,有时候比直接调Wine的缓冲区更有效。
7. 我踩过的几个典型坑与排查思路
7.1 FEX-Emu启动即崩溃,报错“illegal instruction”
这个报错通常是因为FEX-Emu的版本和你的CPU不匹配。FEX-Emu需要ARMv8.1或更高版本的指令集,如果你的设备是较老的ARMv8.0(比如树莓派4的Cortex-A72),某些指令会触发非法指令异常。解决方法是编译FEX时关闭对高级指令集的依赖:
cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_ARM64_CRC=OFF -DENABLE_ARM64_CRYPTO=OFF ..或者换一台支持ARMv8.2的设备。
7.2 Wine程序窗口全黑,只有鼠标指针
这个问题在DXMT和WineD3D切换时经常出现。原因是DLL覆盖设置不正确,Wine加载了错误的D3D实现。你需要检查~/.wine/user.reg文件,确认DllOverrides里d3d11和dxgi指向的是DXMT的DLL,而不是Wine自带的。正确的配置应该是:
[Software\\Wine\\DllOverrides] "d3d11"="native" "dxgi"="native"native表示使用DXMT提供的DLL,builtin表示使用Wine自带的。如果你把DXMT的DLL放到了system32目录,但注册表里还是builtin,那Wine就会加载自带的版本,导致黑屏。
7.3 程序启动后闪退,日志里只有“segmentation fault”
这种问题最难排查,因为段错误可能发生在Wine层、FEX层、或者DXMT层。我的排查顺序是:
- 先用
WINEDEBUG=+all运行,看Wine的日志最后输出到哪里。 - 如果Wine日志正常,再用
FEX_LOG_LEVEL=debug运行,看FEX的翻译日志是否有异常。 - 如果FEX日志也正常,那大概率是DXMT的图形调用出了问题,可以试试切换到WineD3D,看是否能跑起来。
如果切换到WineD3D能跑,说明问题在DXMT;如果还是闪退,那问题可能在Wine的API翻译或者FEX的指令翻译上。
7.4 性能突然下降,之前能跑的程序变卡了
这种情况通常是FEX-Emu的翻译缓存出了问题。FEX会把翻译后的代码缓存在~/.cache/FEX/目录下,如果缓存文件损坏,FEX会重新翻译,导致性能下降。解决方法是删除缓存目录:
rm -rf ~/.cache/FEX/然后重新运行程序,FEX会重新生成缓存。第一次运行会慢一些,之后就恢复正常了。
8. 这套方案到底适合谁
如果你是一个普通用户,想在手机上跑Windows程序,我的建议是:不要折腾。远程串流或者直接买一台Windows平板是更省心的选择。这套方案的学习曲线陡峭,调试成本高,而且性能损耗明显,日常使用体验远不如原生。
如果你是一个开发者,想研究跨平台兼容层的实现原理,或者想为开源项目贡献代码,那这套方案值得深入。FEX-Emu、Wine、DXMT都是活跃的开源项目,代码质量不错,文档也在逐步完善。你可以从跑通一个简单的Windows控制台程序开始,逐步增加复杂度,观察每一层的表现。
如果你是一个极客,享受折腾的过程,那这套方案能给你带来很多“啊哈”时刻。当你第一次在ARM设备上看到Windows程序的窗口弹出来,那种成就感是真实的。但也要做好心理准备:你可能花几个小时才能让一个记事本跑起来,而同样的时间足够你写一篇技术博客了。
最后分享一个小技巧:在调试FEX-Emu时,用FEXBash进入模拟环境后,先运行uname -m,确认输出是x86_64。如果输出还是aarch64,说明FEX的环境变量没有正确设置,后续所有操作都会失败。这个检查只需要一秒钟,但能帮你省下大量排查时间。