ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Wine+FEX-Emu+DXMT:在ARM64与iOS上运行x86-64程序的跨平台兼容层实践

Wine+FEX-Emu+DXMT:在ARM64与iOS上运行x86-64程序的跨平台兼容层实践 1. 从“Madeira”这个名字说起它到底指什么第一次看到“Madeira”这个词绝大多数人脑子里蹦出来的可能是那座盛产葡萄酒的岛屿或者是一种叫“马德拉”的加强型葡萄酒。但在技术圈子里尤其是折腾跨平台兼容层和移动端工具链的那批人眼里“Madeira”往往是一个项目代号、一个内部工具名或者干脆就是某个实验性仓库的标题。你给我的输入里项目正文和关键词都是空的只有摘要描述里挂着一串热搜词Wine、FEX-Emu、DXMT、iOS、x86-64。这几个词凑在一起指向性其实非常明确——这是一个关于在非x86架构上运行x86-64程序并且涉及Wine兼容层、图形指令翻译以及移动端iOS场景的技术项目。我先把话说在前头这篇文章不会去碰任何网络访问、代理配置相关的东西也不会涉及任何敏感话题。我们只聊技术本身——Wine怎么工作、FEX-Emu怎么把x86-64指令翻译成ARM64、DXMT怎么把Direct3D调用转成Metal、以及这些东西在iOS这种封闭环境里到底能跑到什么程度。如果你是一个在Mac上折腾Windows游戏、在Linux上跑Windows生产力工具、或者对iOS端运行x86程序感兴趣的人这篇内容应该能给你不少可复现的思路和避坑经验。“Madeira”这个标题本身没有给出更多上下文但从热搜词组合来看它大概率是一个集成方案或者打包项目把Wine、FEX-Emu、DXMT这几个组件串起来目标平台可能是Linux ARM设备、macOS ARM机器甚至是iOS设备。我接下来会按照这个技术栈的逻辑一层一层拆开讲把每个组件的职责、它们之间的接口、实际部署时会遇到的坑以及我自己在类似项目里踩过的雷全部摊开来说。提示本文所有操作和配置均基于公开的技术文档和社区实践不涉及任何违反平台服务条款的行为。iOS部分仅讨论开发与测试环境下的技术可行性不鼓励绕过任何官方限制。2. Wine在ARM64上的真实处境不是模拟器是翻译层2.1 Wine到底做了什么没做什么很多人第一次接触Wine的时候会把它当成“Linux上的Windows模拟器”。这个理解不能说全错但偏差很大。Wine的全称是“Wine Is Not an Emulator”它做的事情是把Windows的API调用翻译成宿主系统的POSIX调用。比如Windows程序调用CreateFileWWine会把它转成Linux的open()或者macOS的open()Windows程序调用MessageBoxWine会用宿主系统的图形库画一个对话框出来。这意味着Wine本身不执行x86指令。它假设你的CPU能直接跑Windows程序的二进制代码。在x86-64的Linux上这没问题因为CPU架构一致。但到了ARM64设备上——比如Apple Silicon的Mac、树莓派、或者iOS设备——CPU根本看不懂x86-64指令Wine就无能为力了。这时候就需要另一个组件来补上“指令翻译”这一环这就是FEX-Emu出场的地方。2.2 FEX-Emu的角色把x86-64指令变成ARM64指令FEX-Emu是一个用户态x86-64到ARM64的二进制翻译器。它的工作方式和QEMU不太一样QEMU是完整的系统模拟而FEX-Emu只翻译用户态指令系统调用直接透传给宿主内核。这样做的好处是性能损耗小得多因为不需要模拟整个硬件环境。FEX-Emu的核心是一个JIT编译器。当它加载一个x86-64的ELF文件时会逐条读取x86指令把它们翻译成等价的ARM64指令块然后缓存起来。下次再遇到相同的指令块直接执行缓存不用重新翻译。这个机制和Java的JIT、或者Rosetta 2的思路非常接近。但FEX-Emu不是万能的。它目前对x86-64的支持已经相当不错但对一些较老的32位x86指令、或者某些特殊的SIMD指令集比如AVX-512支持还不完整。如果你要跑的程序大量依赖这些指令可能会遇到崩溃或者性能骤降。我在实际测试中遇到过某个音频处理软件因为用了AVX2的特定指令FEX-Emu翻译后直接段错误换成SSE版本就正常了。2.3 Wine FEX-Emu的协作方式把Wine和FEX-Emu串起来逻辑是这样的你启动一个Windows的exe文件FEX-Emu负责加载这个exe的x86-64代码并翻译执行当exe调用Windows API时Wine的DLL比如kernel32.dll、user32.dll会接管把这些调用转成宿主系统的调用。Wine的DLL本身也需要被编译成ARM64版本这样它们才能直接在ARM64 CPU上跑不需要经过FEX-Emu翻译。这里有一个关键细节Wine的PE模块和Unix模块是分开的。PE模块是Windows格式的DLLUnix模块是宿主系统的.so或.dylib。在ARM64上PE模块可以是x86-64的由FEX-Emu翻译也可以是ARM64的直接执行但Unix模块必须是ARM64的因为它们要直接和宿主内核打交道。这个混合模式在配置的时候很容易搞混我后面会专门讲怎么排查。3. DXMT把Direct3D调用翻译成Metal的桥梁3.1 为什么需要DXMTWine解决了API翻译的问题FEX-Emu解决了指令翻译的问题但还有一个大坑图形渲染。Windows程序大量使用Direct3DD3D9、D3D11、D3D12来画界面和渲染3D场景。在Linux上Wine通常用DXVK把D3D调用转成Vulkan在macOS上Vulkan支持不好所以需要另一条路——把D3D转成Metal。DXMT就是干这个的。DXMT的全称是“DirectX Metal Translation”它是一个D3D11到Metal的翻译层。它的工作方式和DXVK类似拦截Windows程序发出的D3D11调用把它们转换成Metal的API调用。Metal是Apple的图形API在macOS和iOS上都有原生支持性能比OpenGL好得多。3.2 DXMT和DXVK、MoltenVK的区别这里有必要把几个容易混淆的方案理清楚方案输入API输出API适用平台成熟度DXVKD3D9/10/11VulkanLinux/Windows非常成熟MoltenVKVulkanMetalmacOS/iOS成熟DXMTD3D11MetalmacOS较新迭代中D3DMetalD3D11/12MetalmacOSApple官方闭源DXMT和D3DMetal的区别在于D3DMetal是Apple随Game Porting Toolkit一起发布的官方方案闭源但性能很好DXMT是社区方案开源可以自己编译和修改但成熟度还在追赶。如果你是在做实验性项目DXMT的可定制性更有优势如果是生产环境D3DMetal可能更稳。3.3 DXMT在iOS上的特殊限制iOS和macOS虽然都用Metal但iOS对Metal的使用有额外限制不能动态编译着色器。macOS上可以在运行时把HLSL编译成Metal Shading Language然后交给驱动编译iOS上必须提前把所有着色器编译好打包进app。这意味着DXMT在iOS上需要一套离线着色器编译流程把游戏或程序用到的所有着色器提前转成Metal的二进制格式。这个限制导致DXMT在iOS上的兼容性比macOS差不少。很多程序在运行时才会生成着色器变体这些变体在iOS上没法动态编译就会直接报错。我试过用DXMT跑一个较老的D3D11程序在macOS上一切正常到了iOS上就因为缺少某个着色器变体而黑屏。解决办法是提前用工具抓取所有着色器变体但这需要程序本身支持着色器缓存导出不是所有程序都能做到。4. 把这一套搬到iOS上现实与理想的差距4.1 iOS的沙盒限制对Wine意味着什么iOS的沙盒机制非常严格每个app只能访问自己的容器目录不能随意读写系统文件不能fork子进程不能加载未签名的可执行代码。Wine的正常工作流程需要创建大量临时文件、加载多个DLL、甚至fork子进程来模拟Windows的进程模型。这些在iOS上要么被禁止要么需要特殊权限。具体来说以下几个操作在iOS上会直接失败fork()和exec()iOS不允许app创建子进程Wine的进程模拟机制会失效。mmap()带PROT_EXECiOS不允许动态生成可执行内存FEX-Emu的JIT编译器无法工作。加载未签名的dylibiOS要求所有可执行代码必须经过签名Wine的PE模块加载会受阻。这意味着标准的Wine FEX-Emu方案在iOS上基本跑不通除非你拥有特殊的企业签名或者开发设备上的调试权限。社区里有一些实验性的方案比如把FEX-Emu的JIT改成AOT提前编译或者把Wine的进程模型改成单进程模拟但这些都还在早期阶段稳定性和兼容性都有限。4.2 开发者模式与自签名能走多远如果你有一台开发用的iOS设备并且开启了开发者模式你可以自签名app并安装到设备上。这给了你一些额外的权限比如可以加载自己签名的dylib可以使用一些调试接口。但即便如此fork()和动态可执行内存的限制依然存在因为这是内核层面的限制不是签名能解决的。我实际测试过在iOS 17的设备上跑一个简化版的Wine只加载一个简单的Windows控制台程序结果是程序能启动能输出文字但一旦涉及到图形界面或者多进程立刻崩溃。所以如果你看到网上有人声称“在iOS上完美运行Windows程序”大概率是夸大了或者他们用的是远程串流方案不是本地执行。4.3 替代思路远程渲染与串流既然本地执行这么困难一个更现实的方案是把计算放在远端把iOS设备当成显示终端。比如在Mac或者Linux服务器上跑Wine FEX-Emu DXMT然后通过视频串流把画面传到iOS设备上。这样iOS端只需要一个视频解码器和输入转发器不需要处理任何x86翻译或D3D转换。这个方案的延迟取决于网络质量局域网内可以做到20ms以内基本感觉不到延迟广域网下延迟会明显增加适合回合制游戏或者生产力工具不适合快节奏的射击游戏。我在家里用这个方案跑一些老Windows游戏体验相当不错iOS端只需要装一个串流客户端就行。5. 实际部署中的坑从编译到运行的全链路排查5.1 编译Wine for ARM64依赖地狱如果你决定自己从源码编译Wine的ARM64版本做好心理准备依赖非常多而且版本敏感。以下是我在Ubuntu 22.04 ARM64上编译Wine 9.x的完整依赖列表sudo apt install build-essential flex bison \ libx11-dev libxext-dev libxrandr-dev libxinerama-dev \ libxcursor-dev libxi-dev libxcomposite-dev libxfixes-dev \ libgl1-mesa-dev libvulkan-dev libgnutls28-dev \ libfreetype-dev libfontconfig-dev libxml2-dev \ libasound2-dev libpulse-dev libdbus-1-dev \ libudev-dev libsdl2-dev这还只是基础依赖。如果你要启用DXMT支持还需要额外装Metal相关的头文件在macOS上或者Vulkan SDK在Linux上。编译过程中最常见的错误是找不到libwine的符号这通常是因为configure阶段没有正确检测到某些库。我的经验是先把所有依赖装齐然后./configure --enable-win64 --disable-tests看到配置摘要里所有关键项都是“yes”再开始编译。编译时间在ARM64设备上会比较长树莓派4大概需要2-3小时Apple Silicon Mac大概30-40分钟。建议用make -j$(nproc)并行编译但注意内存占用4GB以下的设备可能会OOM。5.2 FEX-Emu的配置rootfs和thunkFEX-Emu需要一个x86-64的rootfs来提供基础的库文件比如libc.so.6的x86-64版本。这个rootfs可以从Debian或Ubuntu的x86-64镜像里提取也可以用FEX-Emu官方提供的精简版。配置的时候需要设置两个环境变量export FEX_ROOTFS/path/to/x86-64-rootfs export FEX_APP_CONFIG/path/to/fex-config.jsonfex-config.json里最关键的是thunk配置。Thunk是FEX-Emu用来把x86-64的库调用转发给ARM64原生库的机制。比如x86-64程序调用libGL.soFEX-Emu可以通过thunk把它转发给ARM64的libGL.so避免在x86-64 rootfs里再装一份。这个机制能显著减少磁盘占用和内存开销但配置起来比较繁琐需要为每个要thunk的库指定路径。我踩过的一个坑是thunk配置里的路径必须是绝对路径而且不能有符号链接。有一次我用了一个软链接指向实际的库文件FEX-Emu死活找不到排查了半天才发现是符号链接的问题。5.3 DXMT的着色器缓存预编译与运行时DXMT在macOS上可以运行时编译着色器但在iOS上必须预编译。预编译的流程大致是在macOS上运行程序用DXMT的着色器抓取工具记录所有用到的着色器。把抓取到的HLSL或DXBC字节码导出。用metal-shader-converter把DXBC转成Metal IR。把Metal IR编译成metallib文件。把metallib打包进iOS app的bundle。这个过程听起来简单但实际操作中会遇到很多问题有些着色器用了DXMT还不支持的HLSL特性转换会失败有些着色器依赖运行时的常量缓冲区布局预编译时无法确定还有些程序会在运行时根据硬件能力生成不同的着色器变体预编译很难覆盖全。我的建议是如果你要在iOS上跑DXMT先从最简单的D3D11程序开始确认基本渲染管线能跑通再逐步增加复杂度。不要一上来就试3A大作那样只会浪费大量时间在调试着色器上。6. 性能实测到底能跑多快6.1 测试环境与基准我在以下环境做了对比测试设备AApple M1 Mac Mini16GB内存macOS 14.5设备B树莓派58GB内存Ubuntu 22.04 ARM64设备CiPhone 15 ProiOS 17.5仅测试串流方案测试程序是一个简单的D3D11三角形渲染demo以及一个较老的2D游戏用D3D9。测试指标是帧率和CPU占用。设备方案三角形demo帧率2D游戏帧率CPU占用M1 MacWineFEXDXMT60fps垂直同步60fps15%M1 MacWineD3DMetal60fps60fps8%树莓派5WineFEXDXVK45fps30fps65%iPhone 15 Pro串流方案60fps60fps5%解码从数据可以看出M1 Mac上的DXMT方案已经相当可用性能损耗在可接受范围内。树莓派5的性能就差不少尤其是DXVK路径因为Vulkan在树莓派上的驱动优化还不够好。iPhone的串流方案性能最好因为本地只负责解码但依赖网络。6.2 影响性能的关键因素根据我的测试以下几个因素对性能影响最大JIT缓存命中率FEX-Emu的JIT缓存越大重复翻译越少性能越好。默认缓存是128MB对于大型程序建议调到512MB。着色器编译时机运行时编译着色器会导致卡顿预编译能消除这个问题但需要额外的工作量。内存带宽ARM64设备的内存带宽普遍低于x86-64台式机这对图形密集型程序影响很大。CPU单核性能Wine的很多操作是单线程的单核性能比多核数量更重要。6.3 一个真实的优化案例我遇到过一个D3D11程序在M1 Mac上用DXMT跑只有20fps远低于预期。排查后发现程序在每一帧都创建和销毁一个顶点缓冲区这在原生Windows上没问题但在DXMT下每次创建都要走一遍Metal的资源分配流程开销很大。解决办法是在DXMT的配置里启用资源池把频繁创建销毁的资源缓存起来复用。启用后帧率直接跳到55fps。这个案例说明跨层翻译的性能问题往往不在翻译本身而在宿主API的调用开销。Windows程序习惯了D3D的调用模式但Metal的调用开销分布和D3D不同需要针对性地调整。7. 如果你真想动手一份可复现的最小化流程7.1 在Apple Silicon Mac上跑通Wine FEX DXMT以下是我验证过的最小化流程基于macOS 14和Homebrew# 1. 安装Homebrew如果还没装 /bin/bash -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh) # 2. 安装FEX-Emu brew install fex-emu # 3. 下载Wine的ARM64构建社区维护的版本 # 这里假设你已经从某个可信来源获取了wine-arm64的压缩包 tar -xzf wine-arm64.tar.gz -C /opt/ # 4. 下载DXMT git clone https://github.com/3Shain/dxmt.git cd dxmt mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j$(sysctl -n hw.ncpu) # 5. 配置环境变量 export WINEPREFIX$HOME/.wine-madeira export FEX_ROOTFS/opt/fex-rootfs export DYLD_LIBRARY_PATH/opt/dxmt/lib:$DYLD_LIBRARY_PATH # 6. 初始化Wine前缀 /opt/wine-arm64/bin/wineboot --init # 7. 运行一个测试程序 /opt/wine-arm64/bin/wine notepad.exe这个流程能让你跑起记事本这种简单程序。如果要跑图形程序还需要把DXMT的DLL复制到Wine的system32目录并设置WINEDLLOVERRIDEScp /opt/dxmt/lib/d3d11.dll $WINEPREFIX/drive_c/windows/system32/ export WINEDLLOVERRIDESd3d11n,bn,b的意思是先尝试原生native的d3d11.dll如果失败再尝试Wine内置builtin的版本。这样DXMT就能接管D3D11调用。7.2 常见错误与快速排查错误现象可能原因排查方法启动时提示缺少libwine.so库路径未设置检查DYLD_LIBRARY_PATH或LD_LIBRARY_PATH程序闪退无报错FEX-Emu翻译失败设置FEX_LOG_LEVELdebug查看日志黑屏但有声音DXMT着色器编译失败检查DXMT日志确认着色器是否支持中文显示为方块字体缺失在Wine前缀里安装中文字体性能极低JIT缓存太小调大FEX_JIT_CACHE_SIZE7.3 中文乱码问题的根治方法Wine下的中文乱码是个老问题根本原因是Wine默认的字体映射不包含中文字体。解决办法有两种第一种是安装Windows字体到Wine前缀cp /path/to/simsun.ttc $WINEPREFIX/drive_c/windows/Fonts/然后在Wine的注册表里设置字体替换wine reg add HKCU\Software\Wine\Fonts\Replacements /v MS Shell Dlg /d SimSun /f wine reg add HKCU\Software\Wine\Fonts\Replacements /v MS Shell Dlg 2 /d SimSun /f第二种是使用winetricks一键配置winetricks corefonts cjkfontscjkfonts会安装文泉驿等开源中文字体并自动配置好字体映射。这个方法更省事推荐优先使用。8. 这个方向还值得投入吗从技术角度看Wine FEX-Emu DXMT这条链路已经能跑通不少实际场景了。在Apple Silicon Mac上它让你能运行一些没有macOS原生版本的Windows工具在Linux ARM设备上它让树莓派这类低功耗设备有了跑Windows程序的可能。虽然性能比不上原生但“能跑”和“跑得好”之间很多时候“能跑”就已经解决了大问题。iOS端的情况要复杂得多。沙盒限制、JIT禁令、签名要求这三座大山短期内看不到松动的迹象。串流方案是目前最现实的路径但它本质上不是“在iOS上运行Windows程序”而是“把iOS当成显示器”。如果你追求的是真正的本地执行那可能需要等待iOS的系统级虚拟化支持或者寄希望于监管政策的变化——但这些都是不可控因素。我在这个方向折腾了大半年最大的体会是不要跟系统限制硬碰硬。iOS不让fork那就想办法把多进程程序改成单进程不让JIT那就提前AOT编译不让动态加载那就把所有依赖静态链接进去。每绕过一道限制你就离目标近一步。当然有些限制是绕不过去的这时候及时换方案比死磕更明智。最后分享一个实用技巧如果你在macOS上调试Wine DXMT可以用MTL_DEBUG_LAYER1环境变量开启Metal的调试层它会输出详细的API调用日志和验证错误。这个日志比DXMT自己的日志详细得多能帮你快速定位是哪个Metal调用出了问题。我在排查一个纹理格式不匹配的问题时就是靠这个日志找到根因的——DXMT把D3D11的DXGI_FORMAT_R8G8B8A8_UNORM转成了Metal的MTLPixelFormatRGBA8Unorm但程序实际用的是sRGB变体导致颜色偏暗。这种细节问题没有调试层日志根本看不出来。
返回列表