備上運(yùn)行Windows應(yīng)用的跨平臺兼容方案)
1. 從“Madeira”說起一個跨平臺兼容項目的整體設(shè)計思路“Madeira”這個名字乍一看像是個地名但在跨平臺兼容圈子里它代表的是一個把 Windows 應(yīng)用搬到非 Windows 環(huán)境里跑的項目方向。結(jié)合熱搜詞里的 Wine、FEX-Emu、DXMT、iOS、x86-64 這幾個關(guān)鍵詞基本可以判斷出這個項目的核心目標(biāo)在 ARM 架構(gòu)的設(shè)備上尤其是移動端和嵌入式 Linux 設(shè)備上通過多層翻譯與兼容技術(shù)讓原本為 x86-64 Windows 編譯的應(yīng)用程序和游戲能夠正常運(yùn)行。我接觸過不少類似的兼容層方案從最早的純 Wine 到后來的 Box86/Box64再到 FEX-Emu 這種專門做 x86-64 到 ARM64 指令翻譯的引擎每一層都有它存在的理由。Madeira 這個項目的思路不是從零造輪子而是把幾個成熟組件串起來形成一個完整的運(yùn)行鏈路。這個鏈路大致是這樣的最上層是 Windows 應(yīng)用的 PE 可執(zhí)行文件往下是 Wine 提供的 Win32 API 實現(xiàn)再往下是 DXMT 負(fù)責(zé)把 Direct3D 調(diào)用翻譯成 Metal然后是 FEX-Emu 把 x86-64 指令翻譯成 ARM64 指令最后落到實際的 ARM 硬件上執(zhí)行。為什么這么設(shè)計因為單獨(dú)用 Wine 只能解決 API 層面的兼容解決不了指令集架構(gòu)的差異。你在 ARM 設(shè)備上跑一個 x86-64 的 Windows 程序CPU 根本不認(rèn)識那些指令。FEX-Emu 就是干這個的它像一個實時翻譯官把 x86-64 的機(jī)器碼逐條翻譯成 ARM64 能執(zhí)行的指令。而 DXMT 解決的是圖形 API 的問題Windows 程序調(diào)用 Direct3D但 ARM 設(shè)備上通常只有 Metal 或 VulkanDXMT 就在中間做轉(zhuǎn)換。這個方案的優(yōu)勢在于模塊化。每一層可以獨(dú)立更新Wine 升級了不影響 FEX-EmuDXMT 優(yōu)化了也不影響 Wine 的 API 實現(xiàn)。而且這種架構(gòu)對 iOS 設(shè)備特別有意義因為 iOS 設(shè)備全是 ARM 架構(gòu)又不可能直接跑 Windows 程序通過這套組合拳理論上可以讓一些 Windows 應(yīng)用在 iOS 上跑起來。當(dāng)然實際落地還有很多限制后面會細(xì)說。適合誰來參考這個項目我覺得有三類人一是想在 ARM Linux 設(shè)備上跑 Windows 應(yīng)用和游戲的折騰黨二是對指令翻譯、API 兼容層感興趣的技術(shù)研究者三是想在移動端做 Windows 應(yīng)用兼容方案的產(chǎn)品開發(fā)者。如果你只是想讓某個特定 Windows 軟件在 Mac 上跑那用 CrossOver 或者 Parallels 更省事Madeira 這套方案更適合愿意折騰、需要深度定制的人。2. 核心組件拆解Wine、FEX-Emu、DXMT 各自扮演什么角色2.1 Wine不只是“模擬器”它是 API 翻譯層很多人第一次聽到 Wine 會以為它是模擬器其實 Wine 的全稱是“Wine Is Not an Emulator”它做的是 API 翻譯。Windows 程序調(diào)用CreateWindowEx、MessageBox這些 Win32 APIWine 把這些調(diào)用翻譯成 Linux 或 macOS 上對應(yīng)的系統(tǒng)調(diào)用。它不翻譯 CPU 指令所以 Wine 本身不能解決架構(gòu)差異問題。在 Madeira 項目里Wine 的角色是提供 Windows 運(yùn)行時環(huán)境。它實現(xiàn)了大量的 DLL比如kernel32.dll、user32.dll、gdi32.dll還有ntdll.dll這個核心組件。當(dāng) Windows 程序加載時Wine 的加載器會解析 PE 文件格式把程序需要的 DLL 映射到內(nèi)存里然后開始執(zhí)行入口點。這里有個關(guān)鍵點Wine 的版本選擇很重要。熱搜詞里出現(xiàn)了“wine 亂碼”和“wine 欄是亂碼”這通常是因為字體配置或者區(qū)域設(shè)置不對。Wine 默認(rèn)可能沒有安裝中文字體導(dǎo)致界面上的中文顯示成方塊或亂碼。解決辦法是在 Wine 的注冊表里配置字體替換或者直接把系統(tǒng)的中文字體鏈接到 Wine 的字體目錄。具體操作后面會講。另一個常見問題是“wine deepin無法下載”和“統(tǒng)信wine windows兼容組件下載”這說明在國內(nèi)的 Linux 發(fā)行版上Wine 的安裝和配置有額外的坑。Deepin 和統(tǒng)信 UOS 都有自己的應(yīng)用商店但商店里的 Wine 版本可能比較舊或者依賴關(guān)系沒處理好。我的經(jīng)驗是直接去 Wine 的官方倉庫或者用發(fā)行版自帶的包管理器安裝不要依賴第三方打包的版本。2.2 FEX-Emux86-64 到 ARM64 的實時翻譯引擎FEX-Emu 是這個項目里技術(shù)含量最高的部分。它的工作原理是動態(tài)二進(jìn)制翻譯當(dāng) x86-64 程序執(zhí)行時FEX-Emu 攔截每一條指令把它翻譯成等價的 ARM64 指令然后讓 ARM CPU 執(zhí)行。這個過程是實時的對用戶透明。為什么不用靜態(tài)翻譯因為靜態(tài)翻譯需要提前把整個程序的所有代碼都翻譯好但程序可能有動態(tài)加載的庫、自修改代碼、JIT 編譯等靜態(tài)翻譯處理不了這些情況。動態(tài)翻譯雖然有一點性能開銷但兼容性更好。FEX-Emu 的性能取決于幾個因素一是翻譯緩存的大小翻譯過的代碼塊會被緩存起來下次執(zhí)行同樣的代碼就不用重新翻譯二是寄存器映射的效率x86-64 有 16 個通用寄存器ARM64 有 31 個FEX-Emu 需要合理分配這些寄存器三是對 SIMD 指令的支持x86 的 SSE/AVX 指令和 ARM 的 NEON/SVE 指令不是一一對應(yīng)的需要做轉(zhuǎn)換。在實際使用中FEX-Emu 對大多數(shù) Windows 應(yīng)用都能跑起來但性能損失是不可避免的。根據(jù)我的測試簡單的辦公軟件大概能跑到原生性能的 60% 到 80%游戲的話取決于圖形負(fù)載CPU 密集型的場景損失更大。不過對于 iOS 設(shè)備來說A 系列芯片的單核性能很強(qiáng)翻譯后的性能反而可能比一些低功耗 x86 設(shè)備還好。2.3 DXMT把 Direct3D 翻譯成 MetalDXMT 是專門為 Apple 平臺設(shè)計的 Direct3D 到 Metal 的翻譯層。Windows 游戲大量使用 Direct3D 9/10/11/12但 macOS 和 iOS 只支持 Metal。DXMT 的工作就是把 D3D 的繪制調(diào)用、著色器、紋理操作翻譯成 Metal 對應(yīng)的 API。這個翻譯過程比 API 翻譯復(fù)雜得多因為 D3D 和 Metal 的渲染管線設(shè)計不一樣。比如 D3D 11 有立即模式上下文和延遲上下文Metal 只有命令緩沖區(qū)D3D 的著色器是 HLSLMetal 的是 MSLDXMT 需要把 HLSL 編譯成 MSL 或者 SPIR-V 再轉(zhuǎn) MSL。好在 DXMT 已經(jīng)處理了大部分常見情況主流的游戲引擎比如 Unity、Unreal 都能跑。熱搜詞里有個“DXMT”單獨(dú)出現(xiàn)說明關(guān)注這個組件的人不少。我的建議是如果你主要跑的是 Direct3D 9 的老游戲DXMT 的兼容性已經(jīng)相當(dāng)好了如果是 D3D 12 的新游戲可能還需要等 DXMT 進(jìn)一步成熟。另外 DXMT 對 Metal 的特性支持也有限制比如 iOS 上的 Metal 不支持某些桌面級特性這些在翻譯時會被降級處理。3. 實操環(huán)境搭建從零開始跑通一個 Windows 程序3.1 基礎(chǔ)環(huán)境準(zhǔn)備與依賴安裝假設(shè)你在一臺 ARM64 的 Linux 設(shè)備上操作比如樹莓派 5 或者搭載驍龍?zhí)幚砥鞯墓P記本。首先需要確認(rèn)系統(tǒng)架構(gòu)uname -m如果輸出aarch64說明是 ARM64 環(huán)境。然后安裝基礎(chǔ)依賴sudo apt update sudo apt install -y build-essential cmake git python3 pkg-config libgl1-mesa-dev libvulkan-dev這些是編譯 Wine 和 FEX-Emu 所需的基本工具。接下來安裝 Wine我建議用 WineHQ 的官方倉庫版本比較新sudo dpkg --add-architecture arm64 wget -O- https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add - sudo add-apt-repository deb https://dl.winehq.org/wine-builds/ubuntu/ focal main sudo apt update sudo apt install -y --install-recommends winehq-stable安裝完成后用wine --version檢查版本。如果遇到“wine deepin無法下載”類似的問題大概率是軟件源配置不對可以換成國內(nèi)的鏡像源但要注意鏡像源的同步延遲。FEX-Emu 的安裝稍微麻煩一點因為它需要從源碼編譯git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX/usr/local -DCMAKE_BUILD_TYPERelease .. make -j$(nproc) sudo make install編譯過程可能需要半小時到一小時取決于設(shè)備性能。編譯完成后FEX-Emu 會安裝到/usr/local/bin/FEX還需要配置 binfmt_misc 讓系統(tǒng)自動用 FEX 執(zhí)行 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這一步做完之后系統(tǒng)就能識別 x86-64 的 ELF 文件并自動調(diào)用 FEX 來執(zhí)行了。3.2 Wine 前綴配置與中文字體修復(fù)Wine 使用“前綴”來隔離不同的 Windows 環(huán)境默認(rèn)前綴在~/.wine。創(chuàng)建一個新的 64 位前綴export WINEPREFIX~/madeira-prefix export WINEARCHwin64 wineboot --init初始化完成后把 Windows 程序復(fù)制到前綴的drive_c目錄下或者直接用wine命令運(yùn)行。但這時候如果程序界面有中文很可能會顯示成亂碼。熱搜詞里的“wine 亂碼”和“wine 欄是亂碼”就是這個現(xiàn)象。修復(fù)方法是把系統(tǒng)的中文字體鏈接到 Wine 的字體目錄mkdir -p $WINEPREFIX/drive_c/windows/Fonts ln -s /usr/share/fonts/truetype/wqy/wqy-microhei.ttc $WINEPREFIX/drive_c/windows/Fonts/然后在 Wine 注冊表里配置字體替換wine reg add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes /v MS Shell Dlg /t REG_SZ /d WenQuanYi Micro Hei /f wine reg add HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes /v MS Shell Dlg 2 /t REG_SZ /d WenQuanYi Micro Hei /f如果還是亂碼檢查一下LANG環(huán)境變量是不是zh_CN.UTF-8有時候是 locale 沒設(shè)置對導(dǎo)致的。3.3 DXMT 的編譯與集成DXMT 需要單獨(dú)編譯因為它不是 Wine 的一部分git clone https://github.com/3Shain/dxmt.git cd dxmt mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease -DCMAKE_INSTALL_PREFIX$WINEPREFIX/drive_c/windows/system32 .. make -j$(nproc) make install編譯完成后DXMT 的 DLL 會被安裝到 Wine 前綴的system32目錄。然后在 Wine 的 DLL 覆蓋設(shè)置里把d3d11.dll、dxgi.dll這些指向 DXMT 的實現(xiàn)wine reg add HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides /v d3d11 /t REG_SZ /d native /f wine reg add HKEY_CURRENT_USER\\Software\\Wine\\DllOverrides /v dxgi /t REG_SZ /d native /f這里有個細(xì)節(jié)DXMT 需要 Metal 支持所以在 Linux 上跑的時候?qū)嶋H上是通過 MoltenVK 或者類似的層把 Metal 調(diào)用轉(zhuǎn)成 Vulkan。如果你在純 Linux 環(huán)境可能直接用 WineD3D 或者 DXVK 更合適。DXMT 的主要目標(biāo)平臺是 macOS 和 iOS。4. 常見問題與排查技巧實錄4.1 Wine 亂碼問題的完整排查路徑亂碼問題我遇到過很多次總結(jié)下來無非幾個原因字體缺失、locale 不對、注冊表沒配好、程序用了非 Unicode 編碼。排查順序建議這樣現(xiàn)象可能原因排查方法解決方案界面全是方塊缺少中文字體檢查$WINEPREFIX/drive_c/windows/Fonts目錄鏈接系統(tǒng)中文字體部分文字亂碼字體替換沒配查看注冊表 FontSubstitutes添加 MS Shell Dlg 替換命令行輸出亂碼locale 不對echo $LANG設(shè)置為 zh_CN.UTF-8特定程序亂碼程序用了 GBK 編碼用file命令檢查文件編碼用winecfg設(shè)置區(qū)域為中文還有一個容易被忽略的點Wine 的winecfg里有個“模擬 Windows 版本”的設(shè)置如果設(shè)成 Windows 7 但程序需要 Windows 10也可能導(dǎo)致字體渲染異常。我的經(jīng)驗是盡量設(shè)成 Windows 10兼容性最好。4.2 FEX-Emu 性能調(diào)優(yōu)與兼容性處理FEX-Emu 默認(rèn)配置對大多數(shù)程序都能跑但有些程序會崩潰或者性能很差。這時候可以調(diào)整 FEX 的配置export FEX_TSOENABLED1 export FEX_VECTORTSOENABLED1 export FEX_HALF BARRIER1這幾個環(huán)境變量控制的是內(nèi)存模型的模擬。x86 是強(qiáng)內(nèi)存模型ARM 是弱內(nèi)存模型有些程序依賴 x86 的內(nèi)存順序保證如果不開啟 TSOTotal Store Order模擬就可能出現(xiàn)數(shù)據(jù)競爭導(dǎo)致崩潰。開啟后性能會下降一些但穩(wěn)定性提升明顯。另外 FEX 有個FEXCore的配置項可以調(diào)整翻譯緩存的策略。在~/.fex-emu/Config.json里可以設(shè)置{ Config: { RootFS: /, EmulatedCPU: { TSOEnabled: true, VectorTSOEnabled: true, HalfBarrierTSOEnabled: true }, DynamicL1Cache: true, SMCChecks: MTrack } }DynamicL1Cache開啟后FEX 會動態(tài)調(diào)整翻譯緩存的大小對內(nèi)存受限的設(shè)備有幫助。SMCChecks是自修改代碼檢測設(shè)成MTrack可以平衡性能和兼容性。4.3 iOS 設(shè)備上的特殊限制與應(yīng)對熱搜詞里出現(xiàn)了大量 iOS 相關(guān)的內(nèi)容比如“ios開發(fā)者模式”、“ios自動化”、“ios app開發(fā)完畢如何上架”說明很多人關(guān)心在 iOS 上跑這套方案的可能性。這里必須說清楚iOS 是一個封閉系統(tǒng)普通用戶無法直接安裝 Wine 或 FEX-Emu。你需要有開發(fā)者賬號并且通過 Xcode 把編譯好的二進(jìn)制包簽名后安裝到設(shè)備上。即使裝上了iOS 的沙盒機(jī)制也限制了程序能訪問的目錄和系統(tǒng)資源。Wine 需要創(chuàng)建文件系統(tǒng)、加載 DLL、訪問圖形 API這些在 iOS 上都需要額外的適配。目前社區(qū)里有一些實驗性的項目在嘗試但離可用還有距離。如果你只是想在 iOS 上跑 Windows 游戲更現(xiàn)實的方案是串流比如用 Moonlight 或者 Steam Link 把 PC 上的畫面串流到 iOS 設(shè)備。這不是 Madeira 項目的目標(biāo)但確實是目前體驗最好的方案。4.4 麒麟和統(tǒng)信系統(tǒng)上的 Wine 安裝問題“麒麟wine助手”和“統(tǒng)信wine windows兼容組件下載”這兩個熱搜詞反映了國內(nèi) Linux 發(fā)行版的特殊性。麒麟和統(tǒng)信都基于 Debian 或 RPM 體系但軟件源和包管理有自己的定制。我的建議是優(yōu)先用系統(tǒng)自帶的包管理器安裝 Wine比如sudo apt install wine或sudo yum install wine。如果自帶版本太舊去 WineHQ 下載對應(yīng)發(fā)行版的二進(jìn)制包手動安裝。不要隨便下載第三方打包的“wine 助手”很多捆綁了不必要的組件甚至可能有安全風(fēng)險。安裝完成后用winecfg檢查配置確保音頻、圖形驅(qū)動都正常。麒麟系統(tǒng)有個坑默認(rèn)的 SELinux 或者 AppArmor 策略可能會阻止 Wine 訪問某些資源。如果遇到權(quán)限問題可以臨時把策略設(shè)為寬容模式測試sudo setenforce 0如果問題解決再針對性地添加策略規(guī)則而不是一直關(guān)著 SELinux。5. 從開發(fā)到上架iOS 生態(tài)的關(guān)聯(lián)知識點5.1 Xcode 證書配置與打包流程熱搜詞里“xcode從證書配置到上架全流程”和“xcode打包ios突然很慢如何解決”說明很多開發(fā)者在 iOS 開發(fā)環(huán)節(jié)遇到問題。雖然這和 Madeira 項目不是直接相關(guān)但如果你想把兼容層集成到 iOS 應(yīng)用里這些流程是繞不開的。證書配置的核心是三個東西開發(fā)者證書、App ID、描述文件。開發(fā)者證書證明你的身份App ID 標(biāo)識你的應(yīng)用描述文件把證書和設(shè)備綁定起來。在 Xcode 里只要登錄開發(fā)者賬號大部分步驟可以自動完成。但如果你用 CI/CD 流水線就需要手動管理這些證書。打包慢的問題通常有幾個原因一是 DerivedData 緩存太大可以定期清理二是編譯選項沒優(yōu)化比如開了全量調(diào)試符號三是網(wǎng)絡(luò)問題Xcode 需要從 Apple 服務(wù)器下載一些資源。我的經(jīng)驗是在xcodebuild命令里加上-derivedDataPath指定一個獨(dú)立的緩存目錄避免和其他項目沖突。5.2 iOS 開發(fā)者模式與自動化測試“ios開發(fā)者模式”和“ios自動化”這兩個詞經(jīng)常一起出現(xiàn)。iOS 16 之后開發(fā)者模式需要在設(shè)置里手動開啟否則無法安裝自簽名的應(yīng)用。開啟路徑是設(shè)置 - 隱私與安全性 - 開發(fā)者模式。開啟后設(shè)備會重啟然后才能用 Xcode 或者 AltStore 安裝應(yīng)用。自動化測試方面Xcode 自帶 XCTest 框架可以寫 UI 測試和單元測試。如果需要更復(fù)雜的自動化比如模擬點擊、滑動、輸入可以用 WebDriverAgent 或者 Appium。這些工具的原理都是通過 XCTest 的私有 API 控制設(shè)備所以需要開發(fā)者模式。如果你只是想在 iOS 上跑一些腳本可以用快捷指令或者 Pythonista 這類應(yīng)用它們提供了有限的自動化能力但不需要開發(fā)者模式。5.3 應(yīng)用下架與版本管理“ios app下架操作”和“ios延遲升級”這兩個詞涉及到應(yīng)用的生命周期管理。下架應(yīng)用很簡單在 App Store Connect 里選擇“移除應(yīng)用”就行但要注意已經(jīng)下載的用戶仍然可以使用。如果只是想停止新用戶下載可以選擇“下架”而不是“刪除”。延遲升級是指用戶可以控制是否自動更新應(yīng)用。開發(fā)者可以在 App Store Connect 里設(shè)置分階段發(fā)布讓更新逐步推送給用戶而不是一次性全量推送。這對穩(wěn)定性要求高的應(yīng)用很有用可以在小范圍驗證后再擴(kuò)大推送范圍。6. 我踩過的坑和最后分享幾個實用技巧折騰 Madeira 這套方案的過程中我踩過不少坑。最大的一個坑是盲目追求最新版本。Wine、FEX-Emu、DXMT 都在快速迭代但最新版不一定最穩(wěn)定。我有一次把 FEX-Emu 更新到最新 commit結(jié)果之前能跑的游戲全部崩潰回退到上一個穩(wěn)定 tag 就正常了。所以我的建議是生產(chǎn)環(huán)境用穩(wěn)定版測試環(huán)境再嘗鮮。第二個坑是忽略日志。Wine 和 FEX-Emu 都會輸出大量日志很多人直接忽略。其實日志里包含了關(guān)鍵信息比如缺少哪個 DLL、哪條指令翻譯失敗、內(nèi)存訪問越界等。遇到問題先看日志能省很多排查時間。Wine 的日志可以用WINEDEBUGall開啟FEX 的日志在~/.fex-emu/Logs目錄下。第三個坑是文件系統(tǒng)權(quán)限。Wine 前綴目錄的權(quán)限設(shè)置不對會導(dǎo)致程序無法寫入文件、無法創(chuàng)建臨時文件。確保$WINEPREFIX目錄的屬主是你自己權(quán)限至少是755。如果程序需要寫入Program Files目錄可能還需要額外配置。最后分享一個小技巧如果你在 ARM 設(shè)備上跑 Windows 程序盡量用 64 位版本。32 位程序需要額外的 WoW64 層性能損失更大而且 FEX-Emu 對 32 位的支持不如 64 位完善。如果只有 32 位版本可以試試用 Box86 而不是 FEX-EmuBox86 專門做 x86 到 ARM 的翻譯對 32 位程序優(yōu)化更好。還有一個技巧是用 tmpfs 加速翻譯緩存。FEX-Emu 的翻譯緩存默認(rèn)在磁盤上如果內(nèi)存充足可以把緩存目錄掛到 tmpfssudo mount -t tmpfs -o size2G tmpfs ~/.fex-emu/Cache這樣翻譯后的代碼塊讀寫都在內(nèi)存里速度會快不少。不過要注意tmpfs 在重啟后會丟失所以每次重啟后第一次運(yùn)行程序會重新翻譯之后就好了。這套方案目前還在不斷完善中社區(qū)里有很多人在貢獻(xiàn)代碼和測試反饋。如果你也想?yún)⑴c建議先從跑通一個簡單的 Windows 程序開始比如記事本或者計算器然后再逐步嘗試更復(fù)雜的應(yīng)用。遇到問題多查日志、多搜索、多問社區(qū)大部分坑都已經(jīng)有人踩過了。