Qt 6.4.0rc1提供了一种新思路:同一个UI代码库,既能编译成Windows原生EXE,也能直接编译成WebAssembly在浏览器里跑。对于做桌面工具类软件、工业上位机界面,或者想低成本把C++/Qt遗产项目搬上Web的团队来说,这比重写一个前端划算太多。
这篇笔记围绕win10下的环境搭建,把三个部分讲透:Qt在线安装器的组件勾选、Emscripten工具链的版本匹配、Qt Creator里WebAssembly套件的配置流程。环境搭建最怕版本互相不认,所以整篇采用“版本对齐优先”的策略,凡是需要版本协同的地方都会明确标注,跟着走一遍不会翻车。
1. 版本选型逻辑:为什么是Qt 6.4.0rc1和Emscripten 3.1.x
先花点篇幅说清楚版本选择,这是整篇的基础。如果版本没选对,后面每一步都可能踩出奇怪报错。
Qt对WebAssembly的支持不是从6.x才开始的,5.11就有了基础支持,5.15起已经比较成熟。Qt 6.0刚出的时候,WebAssembly支持曾经回退过一段,直到6.2 LTS才稳定下来,6.4在6.2的基础上把整个wasm平台模块又重新打磨了一遍,编译产出包更小,模块裁剪也更灵活。我看到6.4.0rc1上线的消息时,第一判断是:WebAssembly模块的二进制已经比较接近最终发布形态,用它来学习搭建流程不会白费功夫。
这里要注意,WebAssembly这个目标在Qt里不是开箱即用的,它的官方支持方案是配合Emscripten来编译,而不是像Windows桌面版那样用MSVC或MinGW就能直接编。Emscripten是C/C++到wasm的编译器工具链,Qt在浏览器里跑,本质上是把Qt框架的源码和目标代码一起交给Emscripten,最终产出一个Combined的wasm包。所以Qt版本和Emscripten版本之间存在严格的兼容关系,写这篇验证用的组合是:
- Windows 10 64位(20H2及以上)
- Qt 6.4.0 rc1
- Emscripten 3.1.14
- CMake 3.21+
- Python 3.7+
- Ninja构建工具
为什么强调Emscripten要用3.1.14而不是最新版本?Qt官方在发布Qt WebAssembly时,会明确给出经过测试的Emscripten版本,我当时查的是3.1.14,这属于一个“官方注释过”的版本。如果装成Emscripten 3.1.15或更新的,某些API签名有变化,编译Qt源码时会出现头文件不匹配的报错。
提示:Emscripten是一个快速迭代的编译器项目,每月可能出两三个小版本,对Qt这类被编译对象来说,“不追新,追相对稳定”比什么都重要。
浏览器方面也要提前说明:WebAssembly最终运行环境是浏览器,但我这篇只会涉及“编译产出”,不会涉及复杂调试工具。本人日常Chrome 103以上,Edge也可以,Firefox 102以上同样没问题。手机端浏览器在这一版本下对wasm的多线程支持还有缺,桌面Chrome调试wasm资源是最顺手的,这是实操结果。
2. 挑战与思路:环境搭建的整体路径
Qt WebAssembly环境搭建最大的难点不在Qt安装,而在三个层次的协同:Qt本身、Emscripten编译器、构建系统CMake与Ninja。不对齐这三层,即使编译时侥幸过了,运行时也可能出现wasm文件加载失败或Qt组件不可用。
整个搭建路径分成五步:
- 安装基础的开发工具链:Python、CMake、Ninja、Git
- 安装Emscripten SDK并激活对应版本
- 下载Qt 6.4.0rc1在线安装包并选择WebAssembly模块
- 在Qt Creator中配置Emscripten编译器套件
- 用一个小demo工程验证整个链路是否通畅
这套顺序里,Emscripten必须先装,因为Qt安装时一般不会自动检查Emscripten,但后续构建时自动检测它会依赖emcc命令的路径。如果反着装,只是步骤颠倒,问题不大,但容易在Qt Creator配置阶段找不到编译器。
最初我按照传统桌面开发的惯性思维,先把Qt安装好再装Emscripten,结果Qt Creator去em++时什么都不显示,排查了半天,最后把Emscripten的emsdk_set_env.bat执行一遍后才正常。顺序倒过来再配置就非常快,大约10分钟就能跑通HelloWorld。这也是这篇笔记刻意调整章节顺序的原因——工具链先行,Qt安装在后,顺着官方支持的依赖方向走。
3. 环境搭建全流程:一步一坑的记录
3.1 基础开发工具:CMake、Ninja、Python、Git
Qt 6.x已经全面转向CMake构建系统,qmake虽然还能用,但官方新特性优先都在CMake侧,Ninja负责加速构建。所以这四样缺一不可。
CMake:直接下载官方二进制安装包,Windows下注意勾选“Add CMake to the system PATH for all users”,这个选项默认不勾。如果漏了,后面在命令行执行cmake会提示命令不存在,Qt Creator的自动检测也很可能失败。
Ninja:如果安装了Visual Studio,可以用VS安装器里带的,但我建议直接到Ninja的GitHub Release页面下载pre-built Windows压缩包,解压后把ninja.exe所在目录添加到PATH。Ninja包很小,几百KB,不需要安装介质。
Python:Emscripten的激活脚本是用Python写的,Windows下建议用官方Python 3.10或3.11,安装时勾选“Add Python to PATH”。这一步在很多“手把手教程”里都容易被忽略,但Emscripten内嵌的脚本严重依赖Python环境变量。
Git:Emscripten SDK本身是通过Git拉取的,推荐直接安装Git for Windows,默认设置即可。
所有基础工具装好后,在命令行里依次验证一下:
cmake --version python --version ninja --version git --version四条命令都应该正常输出版本号。我是复用了之前做C++开发时早已装好的环境,你如果是第一次搭,最好花3分钟验证这一点,不然后面所有脚本都会在这里方向性地报错。
3.2 编译三件套:Emscripten SDK安装与版本锁定
Emscripten的安装方式比较特殊,它不是通过apt或者MSI安装,而是通过Git克隆一个SDK仓库,再用SDK自带的脚本去下载对应版本的二进制工具链。地址在GitHub的emscripten-core/emsdk仓库。
克隆到本地时,我一般会放在一个不带空格的路径下,比如D:/emsdk,Windows对带空格的长路径处理不够优雅,后面激活环境变量时容易出问题。路径上尽量“住”成一个没有空格、没有中文的目录,可以少踩很多坑。
接下来是实操命令:
git clone https://github.com/emscripten-core/emsdk.git cd emsdk git pull emsdk.bat install 3.1.14 emsdk.bat activate 3.1.14重点说一下emsdk.bat activate。这一步并不是直接把Emscripten永久写入系统环境变量,它所做的只是在本目录生成一个配置,要在当前命令行窗口启用它,还需要在clone下来的目录里执行:
emsdk_env.bat这个脚本会自动修改当前终端会话的环境变量。如果你关闭这个终端,新的终端是不会自动带上emcc的。要全局生效,需要手动在系统环境变量中加入Emscripten路径,或者每次构建都用这个脚本初始化。Qt Creator里配置套件时,它会直接读取这个路径,所以为了使整个编辑器环境都能稳定的引入emcc,建议手动把它写进系统环境变量:
D:/emsdk加上D:/emsdk/upstream/emscripten加上D:/emsdk/node/14.18.2_64bit/bin加上
第三点是Emscripten内嵌Node.js的路径,版本可能随你的SDK不同而变化,打开emsdk目录看看node文件夹下面版本号是什么就填什么。
激活完后验证:
emcc --version能输出类似emcc (Emscripten gcc/clang-like replacement) 3.1.14的信息,说明正确。
这里有一个非常容易被忽视的坑:如果系统里装过MinGW或者其他GCC工具链,PATH里的emcc信息可能会被另一个名字为emcc.bat或emcc.exe的同名程序干扰。如果你看到emcc --version输出的不是Emscripten版本,直接检查PATH中是否存在其他emcc,把Emscripten的路径调整到更靠前。
3.3 下载Qt 6.4.0rc1:组件选择与在线安装器问题
Qt从5.15之后,官方就不再提供离线安装了,必须用在线安装器。访问Qt官网,下载qt-unified-windows-x64-online.exe。这个安装器的好处是它本身是一个比较完整的安装向导,能帮你选择组件,问题是网络慢时特别容易让人心浮气躁。我个人经验是,如果官网直接下载在线安装器的速度很慢,可以使用国内镜像源,很多大学的开源镜像站都同步了Qt的在线仓库,安装器启动后需要在设置里添加一个临时Qt官方镜像的仓库地址。
安装器启动后,登录Qt账号。没有账号的话现场注册一个,这个无法绕过。
组件选择界面里,按照Qt 6.4.0 rc1的目录树,我的选择是:
- 展开
Qt节点,勾选Qt 6.4.0 RC1下的WebAssembly组件(这个组件包含了wasm对应的Qt库和针对Emscripten编译所需的cmake配置),同时勾选MinGW 11.2.0 64bit作为本地桌面调试工具链 - 在“Developer and Designer Tools”节点下,勾选
Qt Creator、CMake、Ninja。在线安装器会顺便把你缺的工具一并装掉
这里提醒一下:Qt的WebAssembly组件在安装器里显示的版本号可能只是“Qt WebAssembly”,并不会特别标出Emscripten版本,需要对照官方支持列表。我装的时候勾选完占用的磁盘空间约3-4GB,WebAssembly本身约800MB,剩下的是MinGW和Qt Creator等工具占用的。
对在线安装器偶尔会卡在某个组件上,进度条长时间不动,这种情况不要直接关闭安装器,可以先取消当前下载组件数秒,再点击重试,会续传而不是从头开始。我安装过程中碰到过一次网络中断,一直显示“Downloading Qt 6.4.0 RC1 WebAssembly”,进度卡在67%很久,我直接切换镜像源后重试才恢复。
3.4 Qt Creator配置:新建Emscripten套件
安装成功后,打开Qt Creator。进入Tools > Options > Kits > Compilers,点击添加编译器,类型选择GCC,编译器路径指向D:/emsdk/upstream/emscripten/em++.bat。注意这里Qt Creator对Windows批处理文件的支持并不完美,最好把编译器路径直接指向emcc.bat或em++.bat所在目录,而不是它内部实际的exe。
再进入Qt Versions页面,如果之前勾选了WebAssembly组件,这里应该会有一个标注为Qt 6.4.0 RC1 WebAssembly的版本,选择它并确认qmake路径指向的是wasm目录下的qmake,而不是桌面版路径。如果同时装了桌面版和wasm版,这里很容易选错。区分方式是路径:wasm版本目录一般为6.4.0_rc1/wasm_single(具体看安装器命名),桌面版本目录为mingw_64或msvc2019_64。
然后进入Kits页面,新增一个套件:
- 名称:Qt 6.4.0 RC1 WebAssembly
- 编译器:C和C++都选Emscripten那一个(emcc/em++)
- Qt版本:选择WebAssembly版本的Qt
- CMake工具:选择安装时自带的CMake
- 调试器:不需要配置,浏览器调试不依赖GDB
一切都配置好后,套件前会显示一个绿色的勾,至此Qt Creator侧的准备完成。
3.5 快速验证:编译第一个wasm的Hello World
环境搭没搭好,跑一个最小demo是最直接的验收方式。
我在Qt Creator里新建了一个Qt Widgets Application,Kit选择Qt 6.4.0 RC1 WebAssembly,保留默认的带一个QPushButton的界面,按钮点击弹出QMessageBox,然后直接点击左下角的构建。
第一次构建会比预想中慢,因为需要把Qt的Widgets模块也编译进wasm,中间会调用大量Emscripten编译任务,我用的CPU是i5-10400,大约花了3分钟。产出文件在构建目录下的wasm文件夹里,核心是三个:
demo.html:网页入口demo.js:Qt加载器Javascript胶水代码demo.wasm:编译出来的WebAssembly字节码文件
这三个文件不能像普通网页一样双击用,因为浏览器对wasm有跨域限制,直接从file://协议打开会失败。正确姿势是在项目目录起一个本地HTTP服务:
python -m http.server 8000然后浏览器访问http://localhost:8000/demo.html,如果看到窗口弹出来,按钮点击有响应,就说明环境搭建整体成功。
注意:如果访问时浏览器卡在加载界面、F12控制台显示
TypeError: Failed to fetch dynamically imported module,九成是路径问题,检查HTML里的js和wasm文件是否放在同级目录,或者HTTP服务是否把整个构建目录作为根目录。
4. 常见报错与排查记录
环境搭建过程中的坑非常多,我把实际踩过的整理成一张速查表,基本覆盖88%的问题。
| 报错现象 | 可能原因 | 解决方案 |
|---|---|---|
Command "em++" not found | Emscripten没有激活或在PATH中找不到 | 在emsdk目录执行emsdk_env.bat,或检查PATH |
fatal error: 'QWidget' file not found | Qt Creator选的Qt版本是桌面版而不是wasm版 | 打开Kit,重新选择WebAssembly对应的Qt版本 |
CMake报错找不到Qt6Config.cmake | CMake搜索路径里没有Qt的wasm模块 | 在CMake配置中设置Qt6_DIR指向wasm版本Qt的lib/cmake/Qt6 |
EMCC_WASM_BACKEND相关错误 | Emscripten版本过新或过旧 | 重新激活与Qt匹配的Emscripten版本,建议3.1.14 |
emcc编译时出现missing binaryen | Emscripten安装不完整,二进制工具缺失 | 删除emsdk目录重新clone安装 |
| 网页加载后全白,控制台无输出 | 没有通过HTTP服务访问,直接用本地文件打开的 | 使用python -m http.server或其他静态服务器 |
| 编译进度永远卡在一个文件上且CPU不高 | 可能是杀毒软件扫描wasm编译中间文件拖慢 | 把emsdk和Qt的目录加入杀毒软件白名单,或者临时退出文件筛选扫描 |
链接时报错wasm-ld: error: unknown argument: --reproduce | 当前Emscripten版本与CMake工具配置的链接参数不匹配 | 把CMake/Ninja更新到Qt官方要求的最低版本 |
下面挑两个最值得展开的报错说明排查逻辑。
第一个是“Kit配置好但构建时找不到编译器”的问题。Qt Creator虽然能从em++自动推断编译器,但如果你先安装了MinGW,Qt Creator很容易把默认编译器关联到MinGW的g++上去,构建时就会报QT WASM requires Emscripten compiler。解决方式是手动编辑Kit,把C编译器和C++编译器都明确指定为emcc.bat和em++.bat,而不是靠自动检测。这是最容易误导新人的地方,因为界面上“自动检测”看起来正确,但实际选了错误的工具链。
第二个是CMake侧的问题。Qt 6的CMake配置默认会尝试找本机已安装的Qt包,如果你系统里另外装了桌面版Qt,在构建时CMake缓存可能指向桌面版路径,导致接下来所有模块都是桌面版目标,链接阶段疯狂报错。清晰快捷的方式是,删除构建目录内的CMakeCache.txt,重新用Qt Creator构建,确保构建时用了对应Kit的QMake路径。受够这个问题的开发者还可以使用命令行直接指定:
cmake -DCMAKE_PREFIX_PATH=D:/Qt/6.4.0_rc1/wasm_single5. 环境验证后的进阶配置:运行器与浏览器调试选项
HelloWorld跑通后,如果打算在这个环境上认真做开发,建议把Qt Creator的运行配置也顺手调好。之前用默认配置时,Qt Creator会调用系统默认浏览器打开那个HTML,但某些情况下会报错:“无法找到可执行文件”。原因是对于wasm目标,Qt Creator没有与浏览器对应的Run配置。
解决方式是:在Qt Creator的Projects > Run页面,为当前项目创建一个自定义的可执行配置:
- 可执行文件:指向
python.exe(前面装好的Python) - 命令行参数:
-m http.server 8000 - 工作目录:设置为构建产物HTML所在目录
这样做的好处是,以后每次按运行按钮,Qt Creator会帮你构建、起服务、打开默认浏览器访问页面,不用在终端里手动敲命令,省下大量时间。
Browser Debugging方面,Qt 6.4.0rc1的wasm构建默认是用-g调试信息的,但浏览器里直接断点调试Qt源码还是比较受限。更实用的调试手段反而是在代码里加qDebug()输出,在浏览器控制台里能看到日志,因为Qt的日志系统会默认输出到浏览器console。这一步对排查业务逻辑问题很有效。
6. 环境搭建完成后的个人体会
整套环境搭完之后,再回头看每一步的“绊脚石”,多半出在“版本匹配”和“环境变量”上。Emscripten本身是一个快速变化的编译器,Qt WebAssembly又是在Qt大版本基础上派生出的特化目标,两者只要差一个小的minor版本,就可能在新API上产生范围很大的不兼容。如果把时间拉长看,最优实践就是把Emscripten版本固定到和Qt官方测试用的版本,然后不再动它,除非Qt也要跟着升级。
另外,Qt WebAssembly组件的构建产物对部署方式非常挑剔:它要求所有资源都通过HTTP服务访问,部署到CDN时需要对.wasm文件的Content-Type做配置。标准静态服务器一般都能正确处理,但如果你用自己写的后端服务托管这些文件,需要保证application/wasm这个MIME类型被正确设置,否则浏览器会拒绝执行。
还有一点是Windows平台特有的。Qt安装器的在线下载常常比Linux和macOS上更不稳定,如果你在下载Qt组件时遇到反复失败,先检查系统代理设置,再判断是不是杀毒软件对安装器的实时扫描导致网络超时。把Qt目录加入白名单不仅解决下载速度,也解决后续wasm编译时频繁IO导致的构建时间异常长。
这套环境搭好之后,后续可以开始尝试把比较复杂的QWidget成品项目编译到浏览器里跑,尤其是之前积累了几年C++业务代码的,移植时改动量一般远小于前端的重写。我下一步的计划是拿一个内部工具软件试水,看看核心逻辑部分在wasm环境下与原生环境的性能差距到底有多大,到时有实测数据再回来更新系列的下一篇。