Qt for WebAssembly
Qt for Webassembly 允许您在网络上运行 Qt 应用程序。
WebAssembly(简称Wasm)是一种二进制指令格式,旨在在虚拟机中执行,例如在网页浏览器中。
使用 Qt for WebAssembly,您可以将应用程序作为在浏览器沙盒中运行的网页应用程序分发。这种方法适用于不需要完全访问主机设备功能的首选网络分布式应用程序。
注意:Qt for WebAssembly是一个受支持的平台,但某些模块尚不受支持或处于技术预览阶段。请参阅支持的Qt模块。
Qt for WebAssembly入门
为WebAssembly构建Qt应用程序类似于为其他平台构建Qt。您需要安装SDK(Emscripten),安装Qt(或从源代码构建Qt),最后,构建应用程序。存在一些差异,例如,Qt for WebAssembly支持的模块和功能比其他Qt构建要少。
安装Emscripten
Emscripten是一个将代码编译成WebAssembly的工具链。它允许您在不需要浏览器插件的网页上运行.Qt,以接近原生的速度。
有关安装Emscripten SDK的更多信息,请参阅Emscripten文档。
安装后,您应该已经在您的路径中具有Emscripten编译器。使用以下命令进行验证
em++ --version
Qt的每个小版本都针对特定的Emscripten版本,该版本在修补版中保持不变。Qt的二进制软件包使用目标Emscripten版本构建。应用程序应使用同一版本,因为Emscripten不保证ABI兼容性在不同版本之间。
Emscripten版本包括
- Qt 6.2: 2.0.14
- Qt 6.3: 3.0.0
- Qt 6.4: 3.1.14
- Qt 6.5: 3.1.25
- Qt 6.6: 3.1.37
- Qt 6.7: 3.1.50
使用emsdk
安装特定Emscripten
版本。例如,为Qt 6.7安装,请输入
- ./emsdk install 3.1.50
- ./emsdk activate 3.1.50
在Windows上,Emscripten在安装后已经添加到您的路径中。在macOS或Linux上,您需要将其添加到您的路径中,如下所示
source /path/to/emsdk/emsdk_env.sh
使用以下命令进行验证
em++ --version
如果您在选择Emscripten版本时需要更多灵活性,则可以从中构建Qt。在这种情况下,上面的版本是最低版本。后续版本可能可以工作,但可能会引入需要修改Qt的行为变化。
安装Qt
从您的Qt帐户的下载部分下载Qt。我们为Linux、macOS和Windows提供了作为开发平台的建设。
二进制构建旨在在尽可能多的浏览器上运行,并包括单线程和多线程版本。不支持的特性,如Wasm SIMD
和Wasm 异常
,在二进制构建中不受支持。
从源代码构建 Qt
从源代码构建可以设置 Qt 配置选项,例如线程支持、OpenGL ES 版本或 SIMD 支持。从您的 Qt 账户下载 Qt 源代码。
将 QtConfigure 为交叉编译构建,用于wasm-emscripten
平台。这将设置-static
、-no-feature-thread
和-no-make examples
配置选项。您可以使用-feature-thread
配置选项启用线程支持。不支持的共享库构建。
您需要与同一版本 Qt 的主机构建,并在CMake变量QT_HOST_PATH中指定该路径,或使用-qt-host-path
配置参数。
虽然是可选的,但您可以设置CMake变量CMAKE_TOOLCHAIN_FILE为与 Emscripten SDK 一起提供的 Emscripten.cmake 工具链文件。这可以通过设置环境变量CMAKE_TOOLCHAIN_FILE或向配置传递CMAKE_TOOLCHAIN_FILE=/path/to/Emscripten.cmake
来完成。
./configure -qt-host-path /path/to/Qt -platform wasm-emscripten -prefix $PWD/qtbase
注意:若ninja
可执行文件可用,则配置总是使用Ninja生成器和构建工具。Ninja是跨平台的、功能丰富的、性能良好的,并推荐在所有平台上使用。使用其他生成器可能可行,但不受官方支持。
在 Windows 上,请确保您的系统路径中包含 MinGW,并使用以下方法进行配置
configure -qt-host-path C:\Path\to\Qt -no-warnings-are-errors -platform wasm-emscripten -prefix %CD%\qtbase
然后构建所需模块
cmake --build . -t qtbase -t qtdeclarative [-t another_module]
在命令行中构建应用程序
Qt for WebAssembly 支持使用 qmake 和 make 或使用 ninja 或 make 的 CMake 构建应用程序。
$ /path/to/qt-wasm/qtbase/bin/qt-cmake . $ cmake --build .
构建应用程序会生成多个输出文件,包括包含应用程序和 Qt 代码(静态链接)的.wasm 文件,以及一个可在浏览器中打开的 .html 文件来运行应用程序。
注意:Emscripten 在 "-g" 调试级别生成相对较大的 .wasm 文件。考虑对于调试构建使用 "-g2"。
运行应用程序
运行应用程序需要一个网页服务器。构建输出文件都是静态内容,所以任何一个网页服务器都行。有些用例可能需要特殊的服务器配置,例如提供 https 证书或设置http头,以启用多线程支持。
Emrun
Emscripten 提供了用于测试运行应用程序的 emrun 工具。Emrun 启动一个网页服务器,启动一个浏览器,并将捕获并转发 stdout/stderr(通常输出到 JavaScript 控制台)。
/path/to/emscripten/emrun --browser=firefox appname.html
Python http.server
另一个选项是启动开发网页服务器,然后单独启动网页浏览器。一个简单的方法是使用 Python 的 http.server
python -m http.server
请注意,这仅仅是一个简单的网页服务器,不支持线程所需的SharedArrayBuffer,因为以下提到的所需 COOP 和 COED 标头未发送。
qtwasmserver
Qt 提供了一个开发者网页服务器,它使用mkcert生成 HTTPS 证书。这允许测试需要安全上下文的网络功能。请注意,通过 http://localhost 交付也被认为是安全的,无需证书。
Web服务器也设置了COOP和COEP头信息,使其值为启用SharedArrayBuffer和多线程支持。
qtwasmserver脚本启动一个服务器,默认绑定到localhost。您可以使用-a命令行参数添加其他地址,或使用--all绑定到所有可用地址。
python /path/to/qtbase/util/wasm/qtwasmserver/qtwasmserver.py --all
使用Qt Creator构建应用程序
将应用程序部署到网上
构建应用程序会生成几个文件(在以下表格中用应用程序名称替换“app”)。
生成的文件 | 简短描述 |
---|---|
app.html | HTML容器 |
qtloader.js | 用于加载Qt应用程序的JavaScript API |
app.js | Emscripten生成的JavaScript运行时 |
app.wasm | app二进制文件 |
您可以将app.html原样部署,或者将其丢弃以便使用自定义HTML文件。还可以进行较小的调整,例如将启动画面中的Qt标志更改为应用程序标志。在这两种情况下,qtloader.js都提供了一个用于加载应用程序的JavaScript API。
部署前使用gzip
或brotli
压缩Wasm文件,因为它们提供的压缩率优于其他工具。有关更多信息,请参阅最小化二进制文件大小。
启用某些功能(如多线程和SIMD)会产生与不支持所启用功能的浏览器不兼容的.wasm二进制文件。可以通过构建多个.wasm文件并通过JavaScript功能检测选择正确的一个来绕过此限制,但请注意,Qt不提供执行此操作的任何功能。
使用qtloader
Qt提供JavaScript API用于下载、编译和实例化Qt WebAssembly应用程序。此加载API封装了由Emscripten提供的加载功能,并为基于Qt的应用程序提供了其他有用的功能。它在qtloader.js文件中实现。在构建时,此文件的副本将写入构建目录。
典型用法如下
const app_container_element = ...; const instance = await qtLoad({ qt: { containerElements: [ app_container_element ], onLoaded: () => { /* handle application load completed */ }, onExit: () => { /* handle application exit */ }, } });
代码使用配置对象调用qtLoad()加载函数。该配置对象可以包含任何emscripten配置选项,以及一个特殊的"qt"配置对象。qt配置对象支持以下属性
属性 | 简短描述 |
---|---|
containerElements | HTML容器元素数组。应用程序将这些视为QScreens。 |
onLoaded | 当应用程序完成加载时的回调。 |
onExit | 当应用程序退出时的回调。 |
containerElements数组是Qt和网页之间的主要接口,其中此数组中的html元素(通常是<div>元素)指定了应用程序内容在网页上的位置。
应用程序将每个容器元素视为QScreen实例,可以像往常一样在这些屏幕实例上放置应用程序窗口。设置有Qt::WindowFullScreen状态的窗口使用整个屏幕区域,而未设置为"全屏"的窗口则获得窗口装饰。
qtLoad()函数返回一个promise,它在等待时可提供Emscripten实例。该实例提供访问Embind导出函数的能力。Qt导出几个此类函数,这些函数构成了实例API。
使用 Qt 实例 API
Qt 提供了多个实例函数。目前,这些函数支持在运行时添加和删除容器元素。
属性 | 简短描述 |
---|---|
qtAddContainerElement | 添加一个容器元素。添加元素将为 QScreen 创建一个新的元素。 |
qtRemoveContainerElement | 移除一个容器元素及其对应的屏幕。 |
qtSetContainerElements | 设置所有容器元素 |
qtResizeContainerElement | 使 Qt 能够拾取容器元素大小变更。 |
迁移到 Qt 6.6 的 qtloader
Qt 6.6 包含了一个经过简化实现且范围较小的新 qtloader。这包括可能需要迁移应用 JavaScript 代码的 API 变更。Qt 提供了一个兼容性 API 以简化过渡。根据使用情况,有几种前进方式
- 如果直接使用生成的
app.html
文件,则此文件将在构建时更新。不需要采取任何操作。 - 如果使用基本 qtloader 功能集,则可以使用 Qt 6.6 中包含的兼容性 API 作为临时措施。这个 API 将在未来版本中移除;您应该计划将应用更新为新 qtloader。需要以下迁移步骤 1。
- 如果您使用的是高级功能(例如在运行时添加容器元素),则需要迁移到新加载器或实例 API。需要以下迁移步骤 1 和 2。
迁移步骤
- 包含加载 HTML 文件中的
app.js
(由 Emscripten 生成的 JavaScript 运行时)。<script src="app.js"></script>
在 Qt 6.6 之前,qtloader 会加载并评估此 JavaScript 文件。现在不再这样做了,必须使用 <script> 标签包含此文件。
- 迁移到使用新的 JavaScript 和实例 API。
请参阅上面的文档部分。
支持的浏览器
桌面
Qt for WebAssembly 在以下浏览器上开发和测试
- Chrome
- Firefox
- Safari
- Edge
如果浏览器支持 WebAssembly,则 Qt 应该可以运行。Qt 有一个固定的 WebGL 要求,即使应用本身不使用硬件加速的图形。支持 WebAssembly 的浏览器通常也支持 WebGL,尽管一些浏览器会阻止旧版本或不受支持的 GPU。s/qtloader.js 提供了检查 WebGL 是否可用的 API。
Qt 不直接使用操作系统特性,例如,Firefox 在 Windows 或 macOS 上运行时并不会影响其运行。Qt 使用一些操作系统适配,例如,用于 macOS 上的 ctrl/cmd 键处理。
移动
Qt for WebAssembly 应用在移动浏览器上运行,如移动 Safari 和 Android Chrome。
支持的 Qt 模块
Qt for WebAssembly 支持Qt模块和功能的子集。以下列出了已测试的模块,其他模块可能无法正常工作。
- Qt Core
- Qt GUI
- Qt Network
- Qt Widgets
- Qt Qml
- Qt Quick
- Qt Quick Controls
- Qt Quick Layouts
- Qt 5 Core Compatibility APIs
- Qt 图片格式
- Qt OpenGL
- Qt SVG
- Qt WebSockets
在任何情况下,模块支持可能并不完整,可能存在其他限制,这些限制可能是由浏览器沙盒或 Qt 平台迁移的不完善引起的。有关更多信息,请参阅 使用 Qt for WebAssembly 进行开发。
Qt for WebAssembly 技术预览模块和特性。这些特性可能需要重新配置和构建 Qt。它们可能包含浏览器或 Emscripten 中仍在实验中的特性。
使用 WebAssembly 开发 Qt
使用 CMake 构建项目
如果 CMake 中需要特定的 Emscripten 配置,可以使用以下代码:
if(EMSCRIPTEN) # WebAssembly specific code else() # other platforms endif()
此代码允许为 Emscripten 生成特定配置并确保与其他平台兼容。
OpenGL 和 WebGL
Qt 需要 WebGL,即使应用程序没有直接使用 OpenGL。所有相关的浏览器都支持 WebGL,但请注意,某些浏览器会将某些旧版 GPU 加入黑名单。Qt 加载器将检测这一点并显示错误消息。
Qt 将 WebGL 视为 OpenGL ES,以下为版本对应关系:
OpenGL | WebGL |
---|---|
OpenGL ES 2 | WebGL 1 |
OpenGL ES 3 | WebGL 2 |
OpenGL ES 2 和 OpenGL ES 3 默认启用,可通过QSurfaceFormat::setMajorVersion() 函数进行选择。
Web 和桌面 OpenGL 的差异在WebGL 和 OpenGL 差异中有文档说明。WebGL 1.0 和 WebGL 2.0 之间存在其他差异,可在WebGL 2.0 规范中找到。
默认使用 ES2(以及 ES3)的 WebGL 友好子集。如果需要在不绑定缓冲区的情况下使用glDrawArrays
和 glDrawElements
,可以通过在项目的 CMakeLists.txt
添加以下内容来启用完整的 ES2 支持:
target_link_options(<your target> PRIVATE -s FULL_ES2=1)
以及通过添加以下内容来启用完整的 ES3 模拟:
target_link_options(<your target> PRIVATE -s FULL_ES3=1)
到项目中。
多线程
Qt for WebAssembly 支持使用 Emscripten 的 Pthreads 支持 进行多线程,每个线程都有一个 web worker 支持。通过从 Qt 维护工具中安装 "WebAssembly (多线程)" 组件,或通过从源代码构建 Qt 并向配置传递 "-feature-thread" 标志来启用多线程。
现有的多线程代码通常可以重用,但可能需要修改才能解决 pthread 实现中的特殊问题。一些 Emscripten 和 Qt 特性不支持,这包括 线程代理 特性和(Qt Quick 的)线程渲染循环。
注意,由于主线程可能需要处理来自次要线程的请求,因此在 Qt for WebAssembly 上,特别重要的是不要阻塞主线程。例如,Qt 中所有计时器都在主线程上调度,如果主线程被阻塞,计时器将不会触发。另一个例子是,只能从主线程中创建新的 web worker(线程)。
Emscripten 提供了一些缓解措施。短时等待,如获取互斥锁,可以通过忙等和等待锁时处理事件来支持。应避免在主线程上进行长时间的等待。特别是,调用 QThread::wait() 或 pthread_join() 以等待次要线程的常见做法将不起作用,除非应用程序可以保证线程(和 web worker)已经启动,且在调用 wait() 或 join() 时,将能够独立完成任务而无需主线程的辅助。
多线程功能需要浏览器支持 SharedArrayBuffer API。(通常情况下,Emscripten 将堆存储在 ArrayBuffer 对象中。对于多线程,堆必须与 Web Workers 共享,需要一个 SharedArrayBuffer) 该 API 在所有现代浏览器中通常都是可用的,但如果不符合某些安全要求可能被禁用。启用了线程支持的 WebAssembly 二进制文件将无法运行,即使该二进制文件实际上没有启动线程。
启用 SharedArrayBuffer 需要有安全浏览上下文(页面通过 https:// 或 http://localhost 提供),并且页面需要在跨源隔离模式下。这可以通过在 Web 服务器上设置所谓的 COOP 和 COEP 头部来完成。
- 跨源打开策略:same-origin
- 跨源嵌入策略:require-corp
SIMD
Emscripten 支持 WebAssembly SIMD,它为 WebAssembly 提供了 128 位 SIMD 类型和操作。
从源代码构建 Qt 并使用 -feature-wasm-simd128 标志进行配置以启用;这将在编译和链接时传递 -msimd128 标志。请注意,Qt 在这一点上不包含 wasm-simd 优化的代码路径,但是启用 wasm-simd 将在编译器自动向量化中启用,此时编译器可以使用 SIMD 指令。
您可以直接使用 GCC/Clang SIMD 向量扩展或 WASM SIMD128 内置函数来针对 WebAssembly SIMD 进行定位。有关更多信息,请参阅 Emscripten SIMD 文档 。
此外,Emscripten 还支持将 x86 SSE 指令模拟/翻译为 Wasm SIMD 指令。Qt 不使用此仿真,因为使用没有本地 Wasm SIMD 等效的 SSE SIMD 指令可能会导致性能降低。
请注意,启用了 SIMD 的二进制文件与不支持 WebAssembly SIMD 的浏览器不兼容,即使 SIMD 代码路径在运行时没有调用。SIMD 支持可能在浏览器的高级配置中启用,例如 'about:config' 或 'chrome:flags'。
网络
Qt 提供了对网络有限的支持。一般来说,已经在网络上使用的网络协议也可以从 Qt 使用,而其他由于 Web 沙箱而无法直接使用。
以下协议得到支持
- QNetworkAccessManager 对网页源服务器或支持 CORS 的服务器的 http 请求。这包括来自 QML 的 XMLHttpRequest。
- QWebSocket 到任何主机的连接。请注意,通过安全的 https 协议提供的网页仅允许通过安全的 wss 协议进行 WebSocket 连接。
- 使用 Emscripten 提供的功能通过 WebSockets 模拟 POSIX TCP 套接字。注意,这需要运行一个 转发服务器,该服务器处理套接字转换。
所有其他网络协议均不受支持。
本地文件访问
在网络上,文件系统访问受到沙箱限制,这对应用程序如何处理文件有影响。Web 平台提供 API 用于在用户控制的方式下访问本地文件系统,以及访问持久性存储的 API。Emscripten 和 Qt 订包了这些功能并提供易于从 C++ 和基于 Qt 的应用程序使用的 API。
Web 平台提供了用于访问本地文件和持久性存储的功能
- <input type="file"> 用于显示一个原生的 open-file 对话框,用户可以从中选择文件。
- IndexedDB 提供了持久性本地存储(不可在浏览器外访问)
Emscripten 提供了几个具有 POSIX 类似 API 的文件系统。这些包括
- MEMFS临时文件系统,它将文件存储在内存中
- IDBFS持久化文件系统,它使用IndexedDB存储文件
Emscripten在应用启动时将在"/"路径挂载一个临时MEMFS文件系统。这意味着可以使用QFile,并且默认情况下会读取和写入内存文件。Qt还提供了其他API
- QSettings有一个基于IndexedDB的后端;请注意,在WebAssembly上QSettings是异步的。
- QFileDialog::getOpenFileContent() 打开一个本地文件对话框,用户可以从中选择文件
- QFileDialog::saveFileContent()通过文件下载将文件保存到本地文件系统}
剪贴板访问
Qt支持复制和粘贴文本到系统剪贴板,但由于网络沙箱的某些差异,可能需要用户权限。可以通过处理输入事件(例如 CTRL+c)或使用Clipboard API来获得该权限。
支持Clipboard API的浏览器首选。请注意,使用此API的要求是网页通过安全连接(例如https)提供服务,并且某些浏览器可能需要更改配置标志。
- Chrome版本66和Safari版本13.1支持Clipboard API
- Firefox版本90支持Clipboard API,如果您在"about:config"中启用以下标志
dom.events.asyncClipboard.read dom.events.asyncClipboard.clipboardItem
字体
Qt WASM模块包含3种嵌入式字体:"Bitstream Vera Sans"(备用字体),"DejaVu Sans", "DejaVu Sans Mono"。
这些字体提供了有限的字符集。Qt提供了几种添加额外字体的选项
其中一个是在QML中使用FontLoader,可以通过URL获取字体或使用Qt资源系统(与常规桌面应用相同的方式)。
使用字体的另一种方法是使用QFontDatabase::addApplicationFontFromData添加。
应用启动和事件循环
Qt for WebAssembly支持标准Qt启动方法,其中应用创建一个QApplication对象并调用exec函数
int main(int argc, char **argv) { QApplication app(argc, argv); QWindow appWindow; return app.exec(); }
上面的exec()调用通常阻塞并处理事件,直到应用关闭。遗憾的是,在Web平台上不允许阻塞主线程,因此需要在处理每个事件后返回控制权给浏览器的事件循环。
Qt通过使exec()返回主线程控制权给浏览器(同时保留堆栈)来解决此问题。从应用代码的角度来看,exec()函数调用被进入,事件处理按常规进行。然而,exec()调用永远不会返回,即使在应用退出时也不例外。
这种通常是可以接受的,因为浏览器会在应用关闭时间释放应用内存。这意味着关闭代码不会运行,因为应用对象泄漏并且其析构函数不会运行。
您可以通过将main()重写为异步的来避免这种情况,这是可能的,因为Emscripten在main()返回时不退出运行时。应用代码随后省略exec()调用,并且可以通过删除顶层窗口和应用对象干净地关闭Qt。
QApplication *g_app = nullptr; AppWindow *g_appWindow = nullptr; int main(int argc, char **argv) { g_app = new QApplication(argc, argv); g_appWindow = new AppWindow(); return 0; }
Asyncify
由于Web平台的限制,Qt for WebAssembly的默认构建不支持重新进入事件循环,例如通过调用QEventLoop::exec()或QDialog::exec()。
Emscripten的异步化特性通过允许同步调用(如QEventLoop::exec() 和 QDialog::exec())移交给事件循环来解除这些限制。嵌套调用不支持,因此异步化不用于顶级QApplication::exec() 调用。
需要异步化的特性包括:
- QDialogs,带有返回值的QMessageBox。
- 拖放(特别是拖动)。
- 嵌套/二级事件循环的exec()。
截至Qt 6.4,异步化支持已在二进制包中启用,但需要在应用程序中通过添加-sASYNCIFY -Os到链接器选项来启用
CMake
target_link_options(<your target> PUBLIC -sASYNCIFY -Os)
qmake
QMAKE_LFLAGS += -sASYNCIFY -Os
启用异步化会增加开销,如二进制文件大小和CPU使用量增加。启用优化进行构建以最大限度地减少开销。
调试和性能分析
Wasm调试在浏览器JavaScript控制台中完成,直接在Qt Creator中调试Wasm是不可行的。
- Qt调试和日志输出发送到JavaScript控制台,可以通过浏览器“开发者工具”访问。
- 可以通过重新配置Qt以–device-option QT_WASM_SOURCE_MAP=1,并构建一个调试版本来创建用于逐步通过代码的源映射。
- 如果程序与-g标志链接,将启用DWARF调试符号(已用Chrome测试)
- 这将需要此扩展:https://goo.gle/wasm-debugging-extension
- 另请参阅 https://developer.chrome.com/blog/wasm-debugging-2020/
- 移动浏览器可以使用远程调试
- 要程序化地在特定行上停止执行并弹出浏览器调试器,可以将emscripten_debugger();函数添加到应用程序源代码中。
- 可以通过使用调试版和JavaScript控制台性能分析功能完成性能分析。Qt将–profiling-funcs添加到调试版的链接器参数中,以在性能分析中保留函数名称
您可以使用Emscripten链接器参数添加更多冗余来帮助调试
- -s LIBRARY_DEBUG=1(打印库调用)
- -s SYSCALL_DEBUG=1(打印系统调用)
- -s FS_LOG=1(打印文件系统操作)
- -s SOCKET_DEBUG(打印套接字,网络数据传输)
CMake
target_link_options(<your target> PRIVATE -s LIBRARY_DEBUG=1)
qmake
QMAKE_LFLAGS_DEBUG += -s LIBRARY_DEBUG=1
优化
Qt for WebAssembly使用Emscripten工具链生成二进制文件,并且有许多可能影响性能和二进制文件大小的标志。有关更多信息,请参阅Emscripten:优化代码。
您可以将链接器和编译器标志传递得就像正常C++应用程序一样。
target_compile_options(<your target> PRIVATE -oz -flto) target_link_options(<your target> PRIVATE -flto)
QMAKE_CXXFLAGS += -oz -flto QMAKE_LFLAGS += -flto
最小化二进制文件的大小
为了提供无缝的用户体验,减少WebAssembly应用程序的下载和加载时间是重要的。较小的应用程序二进制文件是使下载更快的重要方面之一。使用以下替代方案来减少二进制文件大小
- 确保分发发布构建。调试构建包含调试符号,大小更大。
- 在服务器上启用压缩。最常用的算法,如
gzip
和Brotli,对Wasm二进制文件很有效,并且可以显著减少其大小。 - 尝试使用编译器和链接器标志,可能生成更小的程序(例如 '-os', '-oz')。结果将根据特定应用程序而变化。
- 当从源代码中编译 Qt for WebAssembly 时,禁用未使用的功能(见下方)。
禁用功能
WebAssembly应用程序默认将Qt库静态链接,使编译器能够消除死代码。然而,由于Qt的动态特性,编译器不一定总是能够执行此类优化。
如果您从源代码中构建Qt for WebAssembly,您可以通过禁用功能来减小Qt二进制文件的大小,进而减小.wasm二进制文件的大小。Qt默认为WebAssembly平台禁用了一些功能,但您也可能禁用应用程序未使用的功能。有关更多信息,请参阅禁用功能。
您可以通过禁用以下功能来减小二进制文件大小(通常可减少10-15%)
配置参数 | 简短描述 |
---|---|
-no-feature-cssparser | CSS样式表的解析器。 |
-no-feature-datetimeedit | 编辑日期和时间(依赖于datetimeparser)。 |
-no-feature-datetimeparser | 解析日期时间文本。 |
-no-feature-dockwidget | 将小部件停靠到QMainWindow内部或将其作为窗口桌面的顶层窗口。 |
-no-feature-gestures | 手势框架。 |
-no-feature-mimetype | 处理MIME类型。 |
-no-feature-qml-network | 网络透明度。 |
-no-feature-qml-list-model | ListModel QML类型。 |
-no-feature-qml-table-model | TableModel QML类型。 |
-no-feature-quick-canvas | 画布项目。 |
-no-feature-quick-path | 路径元素。 |
-no-feature-quick-pathview | PathView项目。 |
-no-feature-quick-treeview | TreeView项目。 |
-no-feature-style-stylesheet | 通过CSS可配置的小部件样式。 |
-no-feature-tableview | 表格视图的默认模型/视图实现。 |
-no-feature-texthtmlparser | HTML解析器。 |
-no-feature-textmarkdownreader | Markdown(CommonMark和GitHub)阅读器。 |
-no-feature-textodfwriter | ODF写入器。 |
Wasm异常
Qt默认不构建异常支持,其中抛出异常将终止程序。《WebAssembly异常》可以通过从源代码构建并传递给Qt配置的-feature-wasm-exceptions
标志来启用。这将在编译和链接时间向编译器传递-fwasm-exceptions
标志。Qt不支持启用基于早期JavaScript的异常实现的Emscripten支持。
请注意,在启用异常的情况下,不支持调用QApplication::exec(),这是由于内部实现细节。相反,按照应用程序启动和事件循环中描述的方式编写main(),以便它提前返回并调用exec()。
共享库和动态链接开发者预览
Qt for WebAssembly默认使用静态链接,其中应用程序作为包含Qt库和应用程序代码的单个WebAssembly文件进行部署。动态链接是一种替代的构建模式,其中每个库和插件都单独分发。
例如,使用Qt Quick的应用程序可能会使用以下库和插件
- <qtpath>/lib/libQt6Core.so
- <qtpath>/lib/libQt6Gui.so
- <qtpath>/lib/libQt6Qml.so
- <qtpath>/lib/libQt6Quick.so
- <qtpath>/plugins/imageformats/libqjpeg.so
- <qtpath>/plugins/imageformats/libqjgif.so
- <qtpath>/qml/QtQuick/Window/libquickwindowplugin.so
动态链接支持目前处于开发者预览版。该实现适用于原型设计和评估,但不适用于生产使用。当前的限制和限制包括
- Emscripten SDK 需要打补丁。使用 emsdk 3.1.37,并应用以下补丁。
- 不支持 Chrome 浏览器,因为它不支持加载大于 4K 的 wasm 文件。
- 不支持多线程。
- 不支持异步。
快速开始
构建和部署程序与静态 wasm 和共享桌面构建略有不同。在构建完整的应用程序之前,可以考虑从小示例开始。
- 从源代码构建 Qt,将“-shared”选项传递到 Qt 配置脚本。
- 使用步骤 1 中的 Qt 构建您的应用程序。
- 通过将“qt”目录复制到应用程序目录或链接到该目录来部署 Qt 安装程序
- ln -s <qtpath> qt
- cp -r <qtpath> qt
- 通过运行部署脚本来创建插件预加载列表。
- <qtpath>/qtbase/util/wasm/preload/deploy_qt_plugins.py <qtpath>
- <qtpath>/qtbase/util/wasm/preload/deploy_qml_imports.py <qthostpath> <qtpath>
深入共享库部署
Qt 的共享库构建分为两个阶段,第一个阶段使 Qt 和应用程序构建可供从 Web 服务器下载,第二个阶段在应用程序启动时下载所需的 Qt 插件和 Qt Quick 导入。
在第一步中,使 Qt 安装程序可供从 Web 服务器下载。根据 Web 服务器设置的特定情况,可能存在不同的方法来实现此目的。共通的是,Qt 加载器预计在加载应用程序的 html 文件所在的“qt”目录中找到 Qt 库和插件。
如果您已经将应用程序作为部署的一部分复制到 Web 服务器,则同时复制 Qt 也是一种可能的方案。如果您直接从其构建目录提供应用程序 - 在开发阶段经常是这样 - 创建对 Qt 的符号链接会很有用。
通过创建 Qt 组件(如插件和 Qt Quick 导入)的预加载列表来为第二步做准备。预加载确保应用程序启动时所有必需的 Qt 组件都可用。按需下载的延迟加载也是可能的,但在此不做介绍。
预加载由 Qt JavaScript 加载器实现,它从 Web 服务器下载文件到 Emscripten 提供的内存文件系统。要下载哪些文件,请使用 json 格式的下载列表指定。Qt 提供了两个用于生成预加载列表的脚本,如上文的快速开始部分所述。
已知问题
- 不支持嵌套事件循环。应用程序不应调用像 QDialog::exec() 和 QEventLoop::exec() 这样的 API。可以使用实验性功能 Asyncify。
- 不支持打印。
- QDnsLookup 查询、QTcpSocket、QSsl 由于 Web 沙盒而不起作用且不受支持
- 字体:Wasm 沙盒不允许访问系统字体。字体文件必须与应用程序一起分发,例如在 Qt 资源或下载中。WebAssembly 的 Qt 本身嵌入了一个这样的字体。
- 在 Qt Quick Controls 2 的某些组件上可能出现未初始化的图形内存的遗迹,例如复选框。在某些高 DPI 显示器上可能会看到这种情况。
- 不支持 Windows 和 macOS 的本地样式,因为作为平台提供的 Wasm 不具备该功能
- 链接时间错误,例如“wasm-ld: 错误:初始内存太小”,需要调整初始内存大小。使用QT_WASM_INITIAL_MEMORY设置初始大小(单位:kb),必须是64KB(65536)的倍数。默认为50MB。在CMakeLists.txt中:set_target_properties(<target> PROPERTIES QT_WASM_INITIAL_MEMORY "150MB")
- CMakeLists.txt中的add_executable不会生成<target>.html或复制qtloader.js。请使用qt_add_executable代替。
- QWebSocket连接仅在主线程上由Emscripten支持。
- WebAssembly的QWebSockets不支持发送ping或pong帧,因为提供给网页和浏览器的API没有公开此功能。
- 例如“RangeError: Out of memory”的运行时错误可以通过将MAXIMUM_MEMORY设置为设备支持的值来解决,例如
target_link_options(<your target> PRIVATE -s MAXIMUM_MEMORY=1GB)
- 要使用QtWebsockets,子协议可能需要设置为'mqtt'以使用QtMqtt。在打开QWebSocket时使用QWebSocketHandshakeOptions。
其他主题
Qt配置选项参考
以下配置选项在从源构建Qt for WebAssembly时相关。
配置参数 | 简短描述 |
---|---|
-特征线程 | 多线程Wasm。 |
-特征-wasm-simd128 | 启用WebAssembly SIMD支持。 |
-特征-wasm-exceptions | 启用WebAssembly异常支持。 |
-特征-opengles3 | 除默认的opengles2外,使用opengles3。 |
-设备选项QT_EMSCRIPTEN_ASYNCIFY=1 | 使用asyncify。 |
Qt默认禁用了WebAssembly平台的一些功能,以减少二进制文件大小。您可以在为WebAssembly配置Qt时明确启用功能
配置参数 | 简短描述 |
---|---|
-特征-topleveldomain | 提供检查域名是否为顶级域的支持。 |
典型下载大小
预期占用空间(下载大小):编译器生成的Wasm模块可能很大,但压缩效果好
示例 | gzip | brotli |
---|---|---|
helloglwindow (QtCore + QtGui) | 2.8M | 2.1M |
wiggly widget (QtCore + QtGui + QtWidgets) | 4.3M | 3.2M |
SensorTag (QtCore + QtGui + QtWidgets + QtQuick + QtCharts) | 8.6M | 6.3M |
压缩通常由web服务器端处理,使用标准的压缩功能:服务器会自动压缩或选择预压缩文件版本。通常不需要特别处理Wasm文件。
有关更多信息,请参阅最小化二进制文件大小。
示例
外部资源
许可
Qt for WebAssembly可以商业许可方式从Qt公司获得。此外,它还可在GNU通用公共许可证,版本3下获得。有关更多信息,请参阅Qt许可。
© 2024 Qt公司有限公司。本文件中包含的文档贡献的版权属于各自所有人。本提供的文档是根据由自由软件基金会发布的GNU自由文档许可协议版本1.3许可的。Qt及其相关标志是Qt公司在芬兰以及全世界的商标。所有其他商标属于其各自所有人。