Skip to content

QT配置WebAssembly

效果

https://survivor.beyondxin.top/

安装 emsdk

Qt 的每个次版本都以特定的 Emscripten 版本为目标,该版本在补丁发布时保持不变。Qt 的二进制包使用目标 Emscripten 版本构建。应用程序应使用相同的版本,因为 Emscripten 并不保证不同版本之间的ABI 兼容性,版本对应查看 Qt for WebAssembly 文档。

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk

emsdk install 3.1.56
emsdk activate 3.1.56

QTC 配置 WebAssembly

使用 qt-online-installer 安装时候勾选 WebAssembly。(多线程)

CMake 配置

链接QT

moc qt_standard_project_setup() 会处理。
rcc 如果项目中有多个自己的 静态/动态库 ,需手动 set(CMAKE_AUTORCC ON) ,只有一个 qt_standard_project_setup() 会处理。(原因未知)

set(CMAKE_AUTORCC ON)
set(BINDIR bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${BINDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${BINDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Svg Xml Multimedia OpenGL OpenGLWidgets MultimediaWidgets)
qt_standard_project_setup()

如果使用 wasm 必须指定 install

function(apply_app_optimizations target_name)
    apply_compiler_optimizations(${target_name})

    if(MSVC)
        target_link_options(${target_name} PRIVATE
            $<$<CONFIG:Release>:/LTCG>          # 链接时代码生成
            $<$<CONFIG:Release>:/OPT:REF>       # 消除未引用函数和数据
            $<$<CONFIG:Release>:/OPT:ICF>       # 启用相同COMDAT折叠
            $<$<CONFIG:Release>:/INCREMENTAL:NO> # 禁用增量链接
            $<$<CONFIG:Release>:/SUBSYSTEM:WINDOWS> # Windows子系统
        )

        target_compile_options(${target_name} PRIVATE 
            $<$<CONFIG:Release>:/favor:INTEL64> # 针对Intel64优化
            $<$<CONFIG:Release>:/Qvec-report:2> # 向量化报告
        )

        set_target_properties(${target_name} PROPERTIES
            WIN32_EXECUTABLE TRUE
            LINK_FLAGS_RELEASE "/ENTRY:mainCRTStartup"
        )
    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
        target_link_options(${target_name} PRIVATE 
            $<$<CONFIG:Release>:-flto>              # 链接时优化
            $<$<CONFIG:Release>:-Wl,--gc-sections>  # 垃圾回收未使用段
        )
    endif()

    if(EMSCRIPTEN)
        set_target_properties(${target_name} PROPERTIES
            LINK_FLAGS "--preload-file ${CMAKE_SOURCE_DIR}/data_wasm/assets_wasm.pak@assets.pak --preload-file ${CMAKE_SOURCE_DIR}/data_wasm/tables_wasm.pak@tables.pak"
        )
    include(GNUInstallDirs)

    install(TARGETS ${target_name}
        BUNDLE  DESTINATION .
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    )

    qt_generate_deploy_app_script(
        TARGET SurvivorApp
        OUTPUT_SCRIPT deploy_script
        NO_UNSUPPORTED_PLATFORM_ERROR
    )
    install(SCRIPT ${deploy_script})
    endif()

endfunction()

本地文件访问

文件系统访问在网络上是沙箱式的,相关资源(包括字体)都需要打包进 qrc 。如果资源太大用 rcc 打包成单独文件通过 --preload-file 挂载

    if(EMSCRIPTEN)
        set_target_properties(${target_name} PROPERTIES
            LINK_FLAGS "--preload-file ${CMAKE_SOURCE_DIR}/data_wasm/assets_wasm.pak@assets.pak --preload-file ${CMAKE_SOURCE_DIR}/data_wasm/tables_wasm.pak@tables.pak"
        )

判断是否为 wasm

# 平台检测和配置
function(configure_platform_headers)
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "riscv|riscv64" OR CMAKE_CXX_COMPILER_ARCHITECTURE_ID MATCHES "riscv|riscv64")
        set(IS_RISCV 1)
    else()
        set(IS_RISCV 0)
    endif()

    if(WIN32)
        set(IS_WINDOWS 1)
    else()
        set(IS_WINDOWS 0)
    endif()

    if(UNIX AND NOT APPLE)
        set(IS_LINUX 1)
    else()
        set(IS_LINUX 0)
    endif()

    if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|x64|AMD64" OR CMAKE_CXX_COMPILER_ARCHITECTURE_ID MATCHES "x86_64|amd64|x64|AMD64")
        set(IS_X64 1)
    else()
        set(IS_X64 0)
    endif()

    if(EMSCRIPTEN)
        set(IS_WASM 1)
    else()
        set(IS_WASM 0)
    endif()

    configure_file(
        ${CMAKE_SOURCE_DIR}/cmake/platform.h.in
        ${CMAKE_BINARY_DIR}/include/platform.h
    )

    configure_file(
        ${CMAKE_SOURCE_DIR}/cmake/version.h.in
        ${CMAKE_BINARY_DIR}/include/version.h
    )
endfunction()

部署

编译后文件如下,额外准备: index.html 、favicon.ico。部署时候需要设置允许跨域头(wasm 需要),app.data 和 app.wasm 文件比较大,如果想体验效果好需要用cdn。

python 本地部署

import http.server
import socketserver

PORT = 8000


class CORSRequestHandler(http.server.SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
        self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
        super().end_headers()


Handler = CORSRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print(f"Serving at http://localhost:{PORT}")
    httpd.serve_forever()

nginx 配置

    add_header Cross-Origin-Opener-Policy same-origin always;
    add_header Cross-Origin-Embedder-Policy require-corp always;

cdn 阿里云 边缘脚本

add_rsp_header('Cross-Origin-Opener-Policy', 'same-origin')
add_rsp_header('Cross-Origin-Embedder-Policy', 'require-corp')
add_rsp_header('Access-Control-Allow-Origin', 'https://survivor.beyondxin.top')

cdn 七牛云

默认配置只支持有限的响应头,需要提工单。