你想不想在浏览器上运行你的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,大家也可以用http://unicode.scarfboy.com/ 这个网站。能够一次性将一大段中文转成codepoint。

示例

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

我打开了Github Pages功能,大家可以直接浏览器打开:https://cjmdaixi.github.io/QtWasmLoader/ 就能看到效果:

大家看到我跑的是我另一个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

浏览器上的Qt Quick的更多相关文章

  1. Qt Quick 简单介绍

    Qt Quick 是 Qt 提供的一种高级用户界面技术.使用它可轻松地为移动和嵌入式设备创建流畅的用户界面. 在 Android 设备上, Qt Quick 应用默认使用 OpenGL ES ,渲染效 ...

  2. 发布Qt Quick桌面应用程序的方法(使得planets在XP上运行)

    发布Qt Quick桌面应用程序的方法 Qt是一款优秀的跨平台开发框架,它可以在桌面.移动平台以及嵌入式平台上运行.目前Qt 5介绍程序发布的文章帖子比较少.大家又非常想要知道如何发布Qt应用程序,于 ...

  3. Qt Quick QMl学习笔记 之图片浏览器

    Qt Quick模块是编写QML应用程序的标准库.虽然Qt QML模块提供QML引擎和语言基础结构,但Qt Quick模块提供了使用QML创建用户界面所需的所有基本类型.它提供了一个可视画布,包括用于 ...

  4. Qt Quick 简单教程

    上一篇<Qt Quick 之 Hello World 图文详解>我们已经分别在电脑和 Android 手机上运行了第一个 Qt Quick 示例—— HelloQtQuickApp ,这篇 ...

  5. qt quick中qml编程语言

    Qt QML 入门 — 使用C++定义QML类型 发表于 2013 年 3 月 11 日   注册C++类 注册可实例化的类型 注册不实例化的QML类型 附带属性 注册C++类 注册可实例化的类型 如 ...

  6. Qt Quick 基本元素初体验

    Qt Quick 作为 QML 语言的标准库,提供了很多基本元素和控件来帮助我们构建 Qt Quick 应用,这节我们简要地介绍一些 Qt Quick 元素. 一. 基本可视化项 1.1 Item I ...

  7. 从头学Qt Quick(1) --体验快速构建动态效果界面

    自2005年Qt4发布以来,Qt已经为成千上万的应用程序提供了框架服务,现在Qt已经基本上支持所有的开发平台了,这里面既包含了桌面.嵌入式领域,也包括了Android.IOS.WP等移动操作平台,甚至 ...

  8. Qt Quick实现的涂鸦程序

    之前一直以为 Qt Quick 里 Canvas 才干够自绘.后来发觉不是,原来还有好几种方式都能够画图! 能够使用原始的 OpenGL(Qt Quick 使用 OpenGL 渲染).能够构造QSGN ...

  9. Qt on Android: Qt Quick 之 Hello World 图文具体解释

    在上一篇文章,<Qt on Android:QML 语言基础>中,我们介绍了 QML 语言的语法,在最后我们遗留了一些问题没有展开,这篇呢,我们就正式開始撰写 Qt Quick 程序,而那 ...

随机推荐

  1. Unity3D for iOS初级教程:Part 3/3

    转自Unity 3D for iOS 这篇文章还可以在这里找到 英语 Learn how to use Unity to make a simple 3D iOS game! 这份教程是由教程团队成员 ...

  2. Codeforces #765D

    我在这道题上花了2个小时,仍没解出.理一下当时的思路,看看症结到底在哪里. 题意 用 $[n]$ 表示集合 $\{1,2,3,\dots, n\}$ . 3个函数 $f \colon [n] \to ...

  3. BZOJ 3518 点组计数 ——莫比乌斯反演

    要求$ans=\sum_{i=1}^n \sum_{j=1}^m (n-i)(m-j)(gcd(i,j)-1)$ 可以看做枚举矩阵的大小,然后左下右上必须取的方案数. 这是斜率单增的情况 然后大力反演 ...

  4. 北京集训TEST16——图片加密(fft+kmp)

    题目: Description CJB天天要跟妹子聊天,可是他对微信的加密算法表示担心:“微信这种加密算法,早就过时了,我发明的加密算法早已风靡全球,安全性天下第一!” CJB是这样加密的:设CJB想 ...

  5. P1140 相似基因 (动态规划)

    题目背景 大家都知道,基因可以看作一个碱基对序列.它包含了4种核苷酸,简记作A,C,G,T.生物学家正致力于寻找人类基因的功能,以利用于诊断疾病和发明药物. 在一个人类基因工作组的任务中,生物学家研究 ...

  6. [JSOI2007] 祖玛 (区间DP)

    题目描述 这是一个流行在Jsoi的游戏,名称为祖玛. 精致细腻的背景,外加神秘的印加音乐衬托,彷佛置身在古老的国度里面,进行一个神秘的游戏——这就是著名的祖玛游戏.祖玛游戏的主角是一只石青蛙,石青蛙会 ...

  7. 请问 内网的 dns服务器 为什么和 外网的dns服务器 一样??

    公司内的内网使用192.169.X.X的内网地址,但是在DNS段填写的是210.34.X.X,显然这是一个公网固定IP,我不明白的是:为什么内部网客户端使用的DNS服务器是公网上的IP呢?内网客户端能 ...

  8. 基于CI框架的管理系统

    1:ci框架是有入口文件的,前端和后台入口文件(index.php,admin.php):里面修改$application_folder = 'application/home': 2:项目基本都是在 ...

  9. AIO

    IBM® 市场 (英文) 提交   我的 IBM 站点导航   学习 开发 社区 学习 Java technology 内容   概览 简单介绍 Asynchronous I/O 开始简单的异步 I/ ...

  10. spark hbase

    1 配置 1.1 开发环境: HBase:hbase-1.0.0-cdh5.4.5.tar.gz Hadoop:hadoop-2.6.0-cdh5.4.5.tar.gz ZooKeeper:zooke ...