戰(zhàn)與踩坑記錄)
1. 為什么要在 Windows 上折騰 Qt for WebAssembly第一次聽說 Qt for WebAssembly 的時(shí)候我的反應(yīng)是這東西到底圖什么桌面端 Qt 寫得好好的為什么非要把 C 代碼編譯成 wasm 丟進(jìn)瀏覽器里跑后來接了一個(gè)需求客戶希望把一套已有的 Qt 桌面工具做成打開瀏覽器就能用的版本不想重寫前端也不想維護(hù)兩套代碼。這時(shí)候 Qt for WebAssembly 就成了唯一合理的選項(xiàng)。它的核心邏輯其實(shí)不復(fù)雜Emscripten 工具鏈把 C 代碼編譯成 WebAssembly 字節(jié)碼Qt 提供了一套 wasm 平臺插件來對接瀏覽器的 canvas、事件系統(tǒng)和文件系統(tǒng)。最終產(chǎn)物是一個(gè).html.js.wasm的組合扔到任意靜態(tài)服務(wù)器上就能訪問。對于 Windows 開發(fā)者來說整個(gè)編譯過程可以在本機(jī)完成不需要切換到 Linux 環(huán)境這是它比很多跨平臺方案更友好的地方。但能在 Windows 上編譯和能順利編譯出可用的產(chǎn)物之間隔著相當(dāng)多的坑。我在前前后后折騰了大概兩周之后才算把一套穩(wěn)定的構(gòu)建流程跑通。這篇文章就把整個(gè)過程中的關(guān)鍵決策、踩坑記錄和實(shí)操細(xì)節(jié)完整梳理一遍適合已經(jīng)有 Qt 開發(fā)基礎(chǔ)、想嘗試 WebAssembly 方向的開發(fā)者參考。如果你還沒接觸過 Qt建議先把 qmake 或 CMake 的基本用法摸熟再來看這篇。需要提前說明的是Qt for WebAssembly 目前仍然屬于技術(shù)預(yù)覽級別的支持不同 Qt 版本之間的行為差異比較大。我下面所有的操作和結(jié)論都基于 Qt 6.x 系列在 Windows 上的實(shí)際驗(yàn)證Qt 5 時(shí)代的做法有部分已經(jīng)不適用了遇到不一致的地方以你本地版本為準(zhǔn)。2. 環(huán)境搭建從零到能編譯出第一個(gè) wasm 文件2.1 工具鏈選型與版本匹配的坑Windows 上做 Qt for WebAssembly第一步就是裝 Emscripten。這里最容易踩的坑是版本匹配問題。Qt 官方對 Emscripten 的版本有明確要求不是隨便裝一個(gè)最新版就能用。我一開始圖省事裝了當(dāng)時(shí)最新的 emsdk結(jié)果編譯 Qt 基礎(chǔ)庫的時(shí)候直接報(bào)了一堆關(guān)于emscripten_version的斷言錯(cuò)誤排查了半天才發(fā)現(xiàn)是版本不兼容。正確的做法是先確定你要用的 Qt 版本然后去查這個(gè) Qt 版本對應(yīng)的官方文檔找到推薦的 Emscripten 版本號。以 Qt 6.5 為例它要求的是 Emscripten 3.1.x 系列。安裝的時(shí)候用 emsdk 指定版本git clone https://github.com/emscripten-core/emsdk.git cd emsdk ./emsdk install 3.1.37 ./emsdk activate 3.1.37在 Windows 上如果你用的是 Git Bash 或者 MSYS2 環(huán)境上面的命令可以直接跑。如果用的是 PowerShell把./emsdk換成emsdk.bat或者python emsdk.py。激活之后每次開新終端都要重新執(zhí)行一次emsdk_env腳本來設(shè)置環(huán)境變量這個(gè)別忘否則后面編譯的時(shí)候會提示找不到emcc。注意emsdk 的安裝路徑里不要有空格和中文否則在某些構(gòu)建腳本里會出現(xiàn)路徑解析異常。我一開始裝在C:\Program Files\下面結(jié)果 CMake 配置階段就掛了換到C:\emsdk之后一切正常。2.2 Qt for WebAssembly 的安裝方式Qt for WebAssembly 的庫文件不是默認(rèn)安裝的需要在 Qt 安裝器里手動勾選。打開 Qt 在線安裝器在組件選擇頁面找到對應(yīng) Qt 版本下面的 WebAssembly 選項(xiàng)。這里有個(gè)細(xì)節(jié)WebAssembly 組件是分架構(gòu)的有wasm_singlethread和wasm_multithread兩種。單線程版本兼容性更好幾乎所有瀏覽器都能跑多線程版本性能更強(qiáng)但需要瀏覽器支持 SharedArrayBuffer對服務(wù)器的 COOP/COEP 響應(yīng)頭也有要求。我的建議是先用單線程版本把流程跑通確認(rèn)沒問題之后再考慮切多線程。安裝路徑同樣建議不要有空格我一般放在C:\Qt下面。安裝完成之后你會在 Qt 目錄下看到一個(gè)類似C:\Qt\6.5.0\wasm_singlethread\的文件夾里面包含了編譯好的 Qt 庫和對應(yīng)的 CMake 配置文件。這個(gè)路徑后面配置 kit 的時(shí)候要用到。2.3 在 Qt Creator 里配置 wasm 構(gòu)建套件Qt Creator 對 wasm 的支持已經(jīng)比較完善了。打開 Qt Creator進(jìn)入 Preferences - Kits在 Qt Versions 標(biāo)簽頁里添加一個(gè)新的 Qt 版本指向剛才安裝的 wasm 目錄下的bin/qmake.exe。然后在 Compilers 標(biāo)簽頁里C 編譯器選emccC 編譯器選em這兩個(gè)都在 emsdk 的upstream/emscripten目錄下。最后在 Kits 標(biāo)簽頁新建一個(gè)套件Qt 版本選剛才添加的 wasm 版本編譯器選 emcc/em設(shè)備類型選 WebAssembly。配置好之后打開一個(gè) Qt 項(xiàng)目在項(xiàng)目配置里就能看到這個(gè)新套件了。這里有個(gè)常見的坑如果你同時(shí)裝了多個(gè)版本的 EmscriptenQt Creator 可能會自動選錯(cuò)。建議在套件配置里手動指定編譯器路徑不要依賴自動檢測。另外構(gòu)建目錄也建議單獨(dú)設(shè)置不要和桌面端的構(gòu)建目錄混在一起否則 CMake 緩存會互相干擾。3. 項(xiàng)目配置CMake 與 qmake 的取舍3.1 為什么我最終選擇了 CMakeQt 6 時(shí)代CMake 已經(jīng)成了官方主推的構(gòu)建系統(tǒng)wasm 平臺也不例外。雖然 qmake 也能用但在 wasm 場景下 CMake 的優(yōu)勢更明顯對 Emscripten 工具鏈的支持更原生鏈接選項(xiàng)的傳遞更靈活而且 Qt 官方文檔里的 wasm 示例基本都是 CMake 寫的。一個(gè)最基礎(chǔ)的CMakeLists.txt大概長這樣cmake_minimum_required(VERSION 3.16) project(wasm_demo LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) qt_standard_project_setup() qt_add_executable(wasm_demo main.cpp mainwindow.cpp mainwindow.h ) target_link_libraries(wasm_demo PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets )看起來和普通桌面項(xiàng)目沒什么區(qū)別但關(guān)鍵在于qt_add_executable這個(gè)宏在 wasm 平臺下會自動幫你處理一些平臺特有的鏈接選項(xiàng)。如果你手動用add_executable就需要自己補(bǔ)一堆 Emscripten 的鏈接參數(shù)很容易漏。3.2 必須關(guān)注的幾個(gè) CMake 變量在 wasm 構(gòu)建中有幾個(gè) CMake 變量會直接影響最終產(chǎn)物的行為值得單獨(dú)拿出來說。第一個(gè)是QT_WASM_INITIAL_MEMORY用來設(shè)置初始內(nèi)存大小。默認(rèn)值對于簡單應(yīng)用夠用但如果你的程序要加載比較大的資源文件或者運(yùn)行時(shí)內(nèi)存占用較高就需要手動調(diào)大。我遇到過一次程序在瀏覽器里跑著跑著就崩了排查下來就是初始內(nèi)存不夠wasm 的內(nèi)存增長機(jī)制沒能及時(shí)擴(kuò)容導(dǎo)致的。第二個(gè)是QT_WASM_PTHREAD_POOL_SIZE這個(gè)只在多線程版本下有效用來設(shè)置線程池大小。如果你用的是單線程版本這個(gè)變量會被忽略。第三個(gè)是CMAKE_BUILD_TYPEwasm 構(gòu)建下建議用Release或者M(jìn)inSizeRel。Debug 版本的 wasm 文件體積會大得離譜一個(gè)簡單的窗口程序編譯出來可能就有幾十兆加載速度非常感人。Release 版本加上-O3優(yōu)化之后體積能縮小到十分之一左右。3.3 資源文件的處理方式Qt 的資源系統(tǒng).qrc在 wasm 平臺下依然可用但工作機(jī)制和桌面端不太一樣。桌面端資源是編譯進(jìn)可執(zhí)行文件的wasm 端則是通過 Emscripten 的虛擬文件系統(tǒng)在運(yùn)行時(shí)加載。這意味著如果你的資源文件很多很大會顯著增加 wasm 文件的體積和加載時(shí)間。我的經(jīng)驗(yàn)是小圖標(biāo)、配置文件這類東西放 qrc 里沒問題大塊的圖片、音頻、視頻資源最好放到服務(wù)器上通過 HTTP 請求加載不要打包進(jìn) wasm。Qt 提供了QNetworkAccessManager的 wasm 實(shí)現(xiàn)可以正常發(fā)起網(wǎng)絡(luò)請求。另外要注意的是wasm 端的文件系統(tǒng)是內(nèi)存文件系統(tǒng)程序運(yùn)行期間寫入的文件在頁面刷新后就會丟失。如果需要持久化存儲得用瀏覽器的 IndexedDB 或者 localStorageQt 這邊可以通過 Emscripten 的 JavaScript 接口來調(diào)用。4. 編譯過程中的典型報(bào)錯(cuò)與排查思路4.1 emcc not found 與路徑問題這是最常見的一個(gè)報(bào)錯(cuò)通常出現(xiàn)在 CMake 配置階段。原因無非兩種要么是 emsdk 的環(huán)境變量沒設(shè)置好要么是 Qt Creator 里的編譯器路徑配錯(cuò)了。排查方法很簡單打開一個(gè)終端執(zhí)行emcc --version如果能正常輸出版本號說明環(huán)境變量沒問題那問題就出在 Qt Creator 的套件配置上。如果終端里也報(bào)找不到命令那就是 emsdk 沒激活重新跑一遍emsdk_env腳本就行。還有一種比較隱蔽的情況路徑里有空格。Emscripten 的某些內(nèi)部腳本對空格路徑處理得不好會導(dǎo)致一些莫名其妙的錯(cuò)誤。如果你的 emsdk 裝在C:\Program Files\下面建議挪個(gè)位置。4.2 鏈接階段的 undefined symbol 錯(cuò)誤這個(gè)錯(cuò)誤在引入第三方庫的時(shí)候特別容易出現(xiàn)。wasm 平臺的鏈接行為和桌面端有差異某些在桌面端能自動解析的符號在 wasm 下需要顯式聲明。舉個(gè)例子如果你用了一個(gè)依賴 POSIX 線程的庫但用的是單線程版本的 Qt鏈接時(shí)就會報(bào)一堆 pthread 相關(guān)的未定義符號。解決辦法要么是換成多線程版本要么是找這個(gè)庫的單線程替代方案。還有一種情況是 C 標(biāo)準(zhǔn)庫的符號缺失。Emscripten 默認(rèn)用的是自己的 libc 實(shí)現(xiàn)如果你鏈接的第三方庫是用別的標(biāo)準(zhǔn)庫編譯的就可能出現(xiàn)符號不匹配。這種情況下需要重新用 Emscripten 工具鏈編譯那個(gè)庫不能直接拿桌面端的.lib或.a文件來用。4.3 運(yùn)行時(shí)白屏或崩潰的排查編譯通過不代表就能跑。我遇到過好幾次編譯一切正常但瀏覽器打開就是白屏的情況。這種問題的排查鏈路比較長我一般按下面的順序來首先看瀏覽器控制臺的報(bào)錯(cuò)信息。按 F12 打開開發(fā)者工具Console 標(biāo)簽頁里通常會有具體的錯(cuò)誤描述。常見的有 RuntimeError: memory access out of bounds這基本就是內(nèi)存越界可能是初始內(nèi)存設(shè)小了也可能是代碼里有 bug。如果控制臺沒有明顯報(bào)錯(cuò)就看 Network 標(biāo)簽頁確認(rèn).wasm文件是否成功加載。有時(shí)候是服務(wù)器沒有正確配置 MIME 類型導(dǎo)致 wasm 文件被當(dāng)成普通二進(jìn)制流處理瀏覽器拒絕執(zhí)行。再不行就用emcc的調(diào)試選項(xiàng)重新編譯加上-g和-s ASSERTIONS2這樣運(yùn)行時(shí)的錯(cuò)誤信息會詳細(xì)很多。雖然產(chǎn)物體積會變大但排查階段值得。提示Chrome 和 Firefox 對 wasm 的支持細(xì)節(jié)有差異同一個(gè)程序在 Chrome 里正常在 Firefox 里可能就報(bào)錯(cuò)。建議至少在這兩個(gè)瀏覽器里都測一遍。5. 性能調(diào)優(yōu)與產(chǎn)物瘦身5.1 編譯優(yōu)化選項(xiàng)的實(shí)際效果wasm 產(chǎn)物的體積直接決定了加載速度而加載速度又直接影響用戶體驗(yàn)。我做過一組對比測試同一個(gè) Qt Widgets 程序不同編譯選項(xiàng)下的產(chǎn)物大小差異非常明顯編譯選項(xiàng)wasm 文件大小首次加載時(shí)間本地服務(wù)器Debug約 45 MB8-12 秒Release -O2約 12 MB3-4 秒Release -O3 LTO約 8 MB2-3 秒MinSizeRel -Oz約 6 MB1.5-2 秒從 Debug 到 Release 的差距是最夸張的所以千萬別用 Debug 版本做演示。-Oz是 Emscripten 特有的優(yōu)化級別專門針對體積優(yōu)化代價(jià)是運(yùn)行速度會稍微慢一點(diǎn)但對于大多數(shù)工具類應(yīng)用來說完全可以接受。開啟 LTOLink Time Optimization也能顯著減小體積在 CMake 里加上set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)就行。不過 LTO 會明顯增加編譯時(shí)間開發(fā)階段可以關(guān)掉出正式版本的時(shí)候再開。5.2 按需裁剪 Qt 模塊Qt 本身是個(gè)龐大的庫如果你只用到了 Widgets 和 Core卻把整個(gè) Qt 都鏈接進(jìn)去產(chǎn)物不大才怪。CMake 的target_link_libraries里只寫你實(shí)際用到的模塊不要圖省事寫Qt6::Qt6這種全量鏈接。另外Qt 提供了一些編譯時(shí)的裁剪選項(xiàng)可以在編譯 Qt 庫的時(shí)候就去掉不需要的功能。不過對于大多數(shù)項(xiàng)目來說直接用官方預(yù)編譯的 wasm 庫就夠了自己編譯 Qt 庫的時(shí)間成本太高除非你有非常特殊的裁剪需求。5.3 資源加載的異步化處理wasm 程序的啟動流程是先加載.js膠水代碼再加載.wasm字節(jié)碼然后初始化運(yùn)行時(shí)最后才執(zhí)行你的main函數(shù)。這個(gè)過程是異步的如果你的程序在啟動時(shí)就要加載大量資源會進(jìn)一步拉長首屏?xí)r間。我的做法是把非必要的資源加載推遲到程序啟動之后用異步的方式在后臺加載。Qt 的QTimer::singleShot配合QNetworkAccessManager可以很方便地實(shí)現(xiàn)這一點(diǎn)。用戶先看到一個(gè)可交互的界面資源在后臺慢慢加載體驗(yàn)會好很多。6. 調(diào)試手段在瀏覽器里定位 C 問題6.1 用 console.log 做最樸素的調(diào)試wasm 里的 C 代碼沒法直接打斷點(diǎn)最原始的調(diào)試方式就是往控制臺打日志。Emscripten 提供了emscripten_log函數(shù)Qt 這邊則可以用qDebug()輸出會自動重定向到瀏覽器控制臺。#include QDebug void someFunction() { qDebug() Entering someFunction, param param; // ... }這種方式雖然笨但在排查邏輯問題時(shí)非常有效。我一般會在關(guān)鍵分支和循環(huán)入口加上日志確認(rèn)代碼執(zhí)行路徑是否符合預(yù)期。6.2 Source Map 與斷點(diǎn)調(diào)試如果你想要更高級的調(diào)試體驗(yàn)可以開啟 Source Map。在 CMake 里加上-g4編譯選項(xiàng)Emscripten 會生成.wasm.map文件Chrome 開發(fā)者工具就能把 wasm 字節(jié)碼映射回 C 源碼支持?jǐn)帱c(diǎn)調(diào)試。不過這個(gè)功能在實(shí)際使用中體驗(yàn)一般映射關(guān)系有時(shí)候不太準(zhǔn)確變量查看也不如原生調(diào)試方便。我的建議是簡單的邏輯問題用日志復(fù)雜的問題還是回到桌面端調(diào)試確認(rèn)邏輯沒問題之后再編譯成 wasm 驗(yàn)證。6.3 內(nèi)存問題的定位技巧wasm 的內(nèi)存問題是最難排查的因?yàn)闉g覽器給出的錯(cuò)誤信息通常很模糊。我總結(jié)了一個(gè)比較有效的排查流程先確認(rèn)是不是初始內(nèi)存不足。把QT_WASM_INITIAL_MEMORY調(diào)大試試如果問題消失那就是內(nèi)存不夠。如果調(diào)大之后還是崩那可能是代碼里有內(nèi)存越界或者野指針。這時(shí)候可以用 Emscripten 的 AddressSanitizer編譯時(shí)加上-fsanitizeaddress。它能在運(yùn)行時(shí)檢測內(nèi)存越界和釋放后使用等問題報(bào)錯(cuò)信息比默認(rèn)的詳細(xì)得多。代價(jià)是產(chǎn)物體積會增大不少而且運(yùn)行速度會變慢只適合排查階段用。7. 部署與跨域問題的實(shí)際處理7.1 靜態(tài)文件服務(wù)器的選擇wasm 產(chǎn)物的部署很簡單本質(zhì)上就是幾個(gè)靜態(tài)文件.html、.js、.wasm可能還有.data和.map。扔到任意靜態(tài)服務(wù)器上就能訪問。但有個(gè)關(guān)鍵點(diǎn)服務(wù)器必須正確設(shè)置.wasm文件的 MIME 類型為application/wasm。如果 MIME 類型不對瀏覽器會拒絕流式編譯雖然有些情況下會回退到非流式編譯但加載速度會慢很多。我用過幾種部署方式各有優(yōu)劣部署方式優(yōu)點(diǎn)缺點(diǎn)Python http.server零配置本地測試方便不支持 wasm MIME 類型需要手動改Nginx性能好配置靈活需要手動加 MIME 配置Node.js serve開箱即用MIME 類型正確需要 Node 環(huán)境對象存儲靜態(tài)托管免運(yùn)維適合生產(chǎn)需要配置 CORS 和 MIME本地開發(fā)階段我推薦用npx serve或者python -m http.server配合手動 MIME 配置。生產(chǎn)環(huán)境的話Nginx 或者對象存儲靜態(tài)托管都可以關(guān)鍵是確認(rèn) MIME 類型和緩存策略。7.2 多線程版本的跨域隔離要求如果你用的是多線程版本的 Qt for WebAssembly部署時(shí)會多一個(gè)要求服務(wù)器必須設(shè)置以下兩個(gè)響應(yīng)頭Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp這兩個(gè)頭是為了啟用瀏覽器的跨域隔離模式只有在這個(gè)模式下SharedArrayBuffer 才可用而多線程 wasm 依賴 SharedArrayBuffer 來實(shí)現(xiàn)線程間共享內(nèi)存。如果這兩個(gè)頭沒設(shè)置程序在瀏覽器里會直接報(bào)錯(cuò)提示 SharedArrayBuffer 未定義。這個(gè)問題的排查難度在于錯(cuò)誤信息不會直接告訴你缺了響應(yīng)頭而是報(bào)一個(gè)看起來毫不相關(guān)的錯(cuò)誤。我第一次遇到的時(shí)候查了好久才定位到。Nginx 里的配置大概是這樣location / { add_header Cross-Origin-Opener-Policy same-origin; add_header Cross-Origin-Embedder-Policy require-corp; # 其他配置... }7.3 緩存策略的取舍wasm 文件通常比較大合理的緩存策略能顯著提升二次加載速度。但 wasm 文件在開發(fā)階段更新頻繁如果緩存策略太激進(jìn)每次更新都要手動清緩存很煩。我的做法是.wasm和.js文件用內(nèi)容哈希命名配合長期緩存.html文件用短緩存或者不緩存。這樣每次發(fā)版文件名變了瀏覽器自然會重新加載用戶不需要手動清緩存。Qt 的 CMake 構(gòu)建默認(rèn)生成的文件名是固定的要實(shí)現(xiàn)哈希命名需要自己寫一點(diǎn)后處理腳本。如果嫌麻煩也可以在服務(wù)器端配置Cache-Control: no-cache讓瀏覽器每次都發(fā)請求驗(yàn)證雖然多一次請求但至少不會拿到舊版本。8. 幾個(gè)讓我印象深刻的實(shí)際踩坑記錄8.1 中文輸入法的兼容性問題這個(gè)坑是我在做一個(gè)需要用戶輸入中文的工具時(shí)遇到的。在 wasm 環(huán)境下Qt 的文本輸入框?qū)χ形妮斎敕ǖ闹С趾懿煌晟?。具體表現(xiàn)是輸入法候選框能彈出來但選詞之后文字沒有正確進(jìn)入輸入框或者進(jìn)入的是亂碼。排查下來發(fā)現(xiàn)wasm 平臺的輸入事件處理和桌面端差異很大。瀏覽器把輸入法事件通過 CompositionEvent 傳遞而 Qt 的 wasm 平臺插件對這個(gè)事件的處理還不夠成熟。我最后的解決方案是用一個(gè)隱藏的 HTML input 元素來接收輸入然后通過 JavaScript 橋接把文字傳給 Qt。雖然繞了一層但至少能正常工作。如果你做的工具不需要中文輸入這個(gè)問題可以忽略。但如果需要建議提前做好心理準(zhǔn)備或者考慮用 HTML 原生輸入框來替代 Qt 的輸入控件。8.2 文件對話框的行為差異Qt 的QFileDialog在 wasm 平臺下的行為和桌面端完全不同。桌面端打開的是系統(tǒng)原生文件對話框wasm 端則是通過瀏覽器的文件選擇器來實(shí)現(xiàn)。這意味著用戶選擇的文件不是通過路徑訪問的而是通過 File API 獲取的文件內(nèi)容需要讀取到內(nèi)存中才能使用無法像桌面端那樣直接操作文件系統(tǒng)Qt 對這部分做了封裝大部分情況下QFileDialog::getOpenFileName能正常工作但返回的路徑是一個(gè)虛擬路徑不能直接傳給標(biāo)準(zhǔn) C 的文件操作函數(shù)。需要用QFile來讀取Qt 的 wasm 平臺插件會把虛擬路徑映射到 Emscripten 的虛擬文件系統(tǒng)。這個(gè)差異在移植已有代碼時(shí)特別容易出問題。如果你的代碼里有大量直接使用std::ifstream或者fopen的地方都需要改成QFile。8.3 定時(shí)器精度與瀏覽器節(jié)流瀏覽器的后臺標(biāo)簽頁會對定時(shí)器進(jìn)行節(jié)流這是為了節(jié)省資源。具體表現(xiàn)是當(dāng)頁面不在前臺時(shí)setTimeout和setInterval的最小間隔會被限制到 1 秒甚至更長。Qt 的QTimer在 wasm 平臺下底層也是用瀏覽器的定時(shí)器實(shí)現(xiàn)的所以同樣會受到這個(gè)限制。如果你的程序依賴高精度的定時(shí)器比如做實(shí)時(shí)數(shù)據(jù)采集或者動畫在后臺標(biāo)簽頁里就會出現(xiàn)明顯的卡頓。這個(gè)問題的解決辦法不多因?yàn)檫@是瀏覽器的安全策略沒法繞過。我的做法是在程序里檢測頁面可見性當(dāng)頁面不可見時(shí)暫停非關(guān)鍵的定時(shí)任務(wù)等頁面恢復(fù)可見時(shí)再重新啟動。Qt 提供了QGuiApplication::applicationStateChanged信號可以用來監(jiān)聽?wèi)?yīng)用狀態(tài)變化。9. 我對這套技術(shù)棧的真實(shí)看法折騰完這一整套流程之后我對 Qt for WebAssembly 的評價(jià)是能用但別指望它和桌面端一樣順滑。它的最大價(jià)值在于讓已有的 Qt 代碼能夠以較低的成本跑到瀏覽器里省去了用 JavaScript 重寫一遍的工作量。對于那些界面邏輯復(fù)雜、但性能要求不高的內(nèi)部工具類應(yīng)用這套方案非常合適。但如果你要做的是一個(gè)面向公眾的、對加載速度和交互流暢度有高要求的 Web 應(yīng)用我建議還是老老實(shí)實(shí)用前端框架重寫。wasm 的加載開銷和 Qt 的運(yùn)行時(shí)代價(jià)擺在那里再怎么優(yōu)化也很難達(dá)到原生 Web 應(yīng)用的體驗(yàn)。另外Qt for WebAssembly 的生態(tài)還在發(fā)展中很多第三方庫的兼容性沒有保證遇到問題可參考的資料也相對有限。選擇這條路之前最好先做一個(gè)最小可行性的驗(yàn)證確認(rèn)你的核心依賴都能正常工作再投入時(shí)間做完整移植。我在實(shí)際項(xiàng)目中最后采用的是混合方案核心的計(jì)算邏輯用 Qt 寫成 wasm 模塊界面部分用 HTML/CSS 重新實(shí)現(xiàn)兩者通過 JavaScript 橋接通信。這樣既復(fù)用了已有的 C 代碼又保證了界面的靈活性和加載速度。如果你也面臨類似的場景這個(gè)思路可以參考。