浏览器上的Qt Quick

你想不想在浏览器上运行你的Qt Quick程序呢?在Qt 5.12之前,唯一的方法是使用Qt WebGL Streaming技术把界面镜像到浏览器上。但该方法有不少缺陷,下文会说。前不久随着Qt 5.12的推出,有一个模块正式进入Qt大家庭,那就是Qt for WebAssembly。简单地讲,就是使用WebAssembly技术,将Qt库编译为wasm字节码,然后直接在浏览器上运行。听着是不是很黑科技?本文将向大家展示如何使用Qt for WebAssembly(以下简称QtWasm)来开发程序,同时也针对目前该模块的几个问题提出自己的解决方法。

The English version of this post is:

Qt Quick on the Browser​jimmychen.space

为啥要在浏览器上运行Qt程序?

在讲具体技术前,我们首先讨论下该技术的目的,或者优势。没有实用价值的黑科技不是好科技。

首先得讲一下WebAssembly技术的优势。Wasm官网提了几个好处,大家可以去详细了解下,在我看来其中毫无疑问最大的优势就是速度。wasm是一种新的字节码技术,可以在浏览器本地获得近乎二进制机器码的运行速度,比js要快多了。对于那些对性能有极高要求的Web应用,例如Web游戏、图形图像处理应用,恨不得榨干整个机器的资源,wasm这类技术方向无疑是必走的。而wasm目前已经获得了Chrome、Firefox、Edge以及Safari这四大浏览器的支持,所以wasm未来可期。

再来讲讲QtWasm。Qt是一套成熟的C++框架,从基本逻辑到界面、网络都可以帮我们快速开发应用。通过将Qt搬到WebAssembly平台,我们可以在浏览器上运行我们的Qt程序。WebAssembly的高性能使得像Qt这么复杂的C++框架也能够顺畅运行。Qt之前的口号是:Write once, compile & run everywhere。而QtWasm使得该口号可以变为:Compile once, run everywhere。是不是很熟悉?对,那正是Java的口号啊!QtWasm解决了Qt程序二进制的跨平台问题,而Web的模式解决了应用的分发、升级问题,所以QtWasm同样未来可期。

所以总结起来,我们为啥要在浏览器上跑Qt 程序?

  1. 借由Qt和WebAssembly技术,快速构建高效、丰富的Web应用,非常适合Qt程序员向Web端拓展;
  2. 解决了传统桌面Qt程序分发、安装、升级问题,而且是一次编译各平台各浏览器都能运行。

现有的浏览器跑Qt Quick程序的方法

目前我还没发现有什么方法可以在浏览器跑Qt Widgets的,所以这里只说Qt Quick:

  1. Qt WebGL Streaming。官方介绍在这里:Qt Quick WebGL release in Qt 5.12。该方法的本质是将Qt Quick的OpenGL ES命令通过WebGL Streaming技术发送到浏览器,实现界面镜像的效果。既然是镜像,也就意味着程序的所有运行其实都还在本地,浏览器只是一个远程桌面。该方法笔者测试下来觉得非常适合于局域网内嵌入式开发板上的Qt Quick程序的远程控制,但也仅限于此。非局域网的情况下会非常卡。
  2. 将QML编译成JavaScript,然后在浏览器跑。典型的是qmlweb。Qt官方是没有这类项目的。这种方法其实只是用了QML的语法,并不是真正的Qt Quick程序,所以也就不可能使用Qt框架的种种强大的功能。

和这两类方法比较,QtWasm有着本质区别,可以说是唯一的、真正在浏览器跑的Qt解决方案。

配置编译环境

讲了QtWasm的优势,那我们开始尝试开发吧。首先就是配置编译环境。

目前QtWasm并没有以二进制的方式包含在Qt安装包里面,需要我们自己编译。笔者在MacOS和Ubuntu for Windows Sub-system Linux(简称WSL)都编译成功过。下面以WSL为例:

  1. 首先是安装WSL。这个网上教程很多,给大家参考一个:How to install Windows 10’s Linux Subsystem on your PC 。大家一定要装Ubuntu 18.04这个版本。旧版本可能会出问题(因为软件源里的各种库都过时了)。
  2. 然后是安装emscripten。安装过程参考emscripten官方教程:Emscripten download and install。这个过程特别需要一个好网,你懂的。确保安装成功的标志是能通过下面命令打印出em++的版本号:em++ --version。如果报错了,那说明安装没成功(很可能某个组件下载出错了)。总之这个环节是最麻烦的。
  3. 下载Qt 5.12.0源代码。注意要下载Linux newline ending的源代码,可以用下面的命令下载:wget https://download.qt.io/official_releases/qt/5.12/5.12.0/single/qt-everywhere-src-5.12.0.tar.xz
  4. 接下去就可以参照Qt官方教程来编译QtWasm了:Getting Started With Qt for WebAssembly。编译过程视机器性能不同,可能会花费30分钟到2小时不等。但只要你做好了上面三步,基本上能够编译成功。

编译完成后,我们就可以用来开发QtWasm了。一般开发过程是:

  1. 使用Qt for Desktop正常新建工程(注意,要用qmake组织,而不是cmake或者qbs),开发、编译和测试代码;
  2. 转到WSL,使用QtWasm的qmake以及make编译程序。编译会生成wasm、js和html文件。
  3. 使用Python http模块搭建简单Web服务器。如果是Python2,使用命令:python -m SimpleHTTPServer 8080;如果是Python3,则使用命令:python -m http.server 8080。你也可以用其他Web服务器。
  4. 使用支持Wasm的浏览器打开html文件,如果一切顺利的话,你应该能看到你的Qt程序跑在浏览器中了。

解决编译慢的问题

能运行起来第一个QtWasm应该有些激动吧?但别高兴太早。当你试着修改源码运行其他功能时,你马上就会发现一个问题:编译实在太慢了!巨慢无比,简直不能忍。笔者试过i5的MacBook Pro和i7的Windows Linux sub-system,都很慢。

冷静下来想想,这倒不是emscripten的锅,实在是Qt这个库太大了,而WebAssembly又要求静态链接,所以……

是可忍孰不可忍,必须解决它。查遍官方文档,无果,纳闷难道这帮人没遇到过这个问题吗?自己动手丰衣足食。忽然想到Qt QML其实是支持网络加载的,比如Loader就可以将source设置为网络上的一个qml文件。

所以解决思路就是:将QML界面独立出来,而将C++在内的做成一个加载器,通过Loader动态加载我们的QML界面。由于加载器部分逻辑明确,无需频繁修改,所以只要编译一次就好了(慢就慢点吧……);而我们的QML界面可以随意修改,只要刷新下页面,就能够加载我们的新界面。说起来这比C++编译还快啊!

所以老实讲,笔者并没有解决Qt库编译慢的问题,而是将QML界面独立出来通过Qt自带的Loader动态加载。本质上我们是将Qt的QML引擎编译成了WebAssembly跑在了浏览器上。

Image无法加载图片怎么办?

真正要动手写点严肃的程序的时候,小伙伴们肯定还会发现,我的Image怎么没法显示图片了?

由于我们的QML界面是动态加载的,而Loader是没法加载qrc这些资源文件的。那读者可能会想,我可以把图片放在服务器上,Image是支持加载网络图片的呀?是的,你之前写的Image都是可以的,但是QtWasm不行。原因是网页图片加载情况下Image只能以异步的方式,也就是说Image会试图创建新线程来执行后台加载;但目前QtWasm不支持多线程,所以就加载失败了。

那怎么办?解决办法是:在我们的项目开发时就尽可能准备好图片素材,然后将他们都放进加载器的qrc里一起编译。然后在我们动态加载的QML界面里,就正常使用"qrc:/"开头的路径加载图片就好了。

读者可能会追问:一开始就准备好所有图片怎么可能呢?那也没办法,一次准备不行,那就分阶段准备多次,总之得将图片编译进加载器里,这是笔者目前想到的最好办法了。虽不完美,但能凑合着用。

我的中文怎么成方块了?

这又是个恼人的问题,困扰笔者很久。后来在QtWasm的官方wiki上找到了原因:

Applications do not have access to system fonts. Font files must be distributed with the application, for example in Qt resources. Qt for WebAssembly itself embeds one such font.

翻译过来就是说,QtWasm是没法访问系统字体的,字体文件必须一起编译进程序中。

英文字体还好,中文字体动则三四十兆,也要编译进wasm中?好吧,大小我忍了,但是……编译通不过啊!编译了半天最后出错什么的最伤人了。

中文字体之所以大是因为汉字实在是多。但我们一个程序一般用到的汉字只是其中很小的一个子集。所以我们解决该问题的主要思路是:只抽取我们要用的汉字集合生成新的字体,然后使用该字体再编译我们的程序。

如何抽取字体?我使用了一个小工具叫FontZip。是一个jar包,所以要先安装java环境。我试验了一下,目前好像只支持从一个ttf格式的字体文件中抽取,可保存为otf或者ttf格式,这两个格式都是Qt支持的。

例如我使用一个免费的中文字体:WenQuanYi Micro Hei Mono。能用FontZip抽取成功,而且目前用下来都没问题。

将抽取出来的字体加入我们的Qt Quick工程qrc中,然后在main.qml中加入下面代码:

FontLoader{
    id: chineseFont source: "qrc:/fonts/fontzipmin.otf" } 

然后在用到的地方使用font.family: chineseFont.name即可:

Label{
    id: label font{family: chineseFont.name} text: "你好" } 

这里面特别需要注意的是,直接写中文字符串只支持像main.qml这样参与编译的QML文件!如果是通过Loader动态加载的QML文件,必须使用Unicode Codepoint才行,这也是笔者遇到的一个大坑。例如显示"你好我们"需要像下面这么写:

 Label{
     id: label anchors.centerIn: parent text: "\u4f60\u597d\u6211\u4eec" } 

推荐大家安装VS Code上的两个插件:

  1. Unicode code point of current character。这个插件可以把中文转成Unicode codepoint。但目前只能一个个转,略麻烦,但好处是可以直接在编代码时就地转换好。
  2. unicodeToChinese。如果哪天你忘了转出来的Unicode codepoint原来是什么汉字了,还能用这个插件转回去。

当然,将汉字转成Unicode codepoint,大家也可以用 这个网站。能够一次性将一大段中文转成codepoint。

示例

延续本专栏以往教程的优良传统,文末必须配上笔者实际验证过的示例程序。这次示例程序也上传到了Github:QtWasmLoader。源代码我上传到了src分支,而编译出来的加载器和用于动态加载的QML文件我放在了master分支上。

我打开了Github Pages功能,大家可以直接浏览器打开: 就能看到效果:

大家看到我跑的是我另一个Qt Quick工程DarkSwitch

大家参考、克隆我的工程的时候要注意,我在Loader里写入的是我这个工程在Github Pages上的URL。如果你要跑自己的程序,那需要将下面的URL设置成你的工程的Github Pages URL:

Loader{
    id: mainView anchors.fill: parent source: "你的Github Pages URL/UI/MainView.qml" onLoaded: { loadingItem.visible = false; } } 

为了方便大家开发,我已经把3500个常用汉字抽出来了,就是font目录下的Simplified-Chinese3500.otf文件。所以一般开发测试情况下,大家不需要自己抽取汉字了。

有问题欢迎大家去Github上留issue。

https://zhuanlan.zhihu.com/p/53077277

猜你喜欢

转载自www.cnblogs.com/findumars/p/10201323.html
今日推荐