Qt Quick 编译器简介


类型:C++ & Qt4,创建时间:April 13, 2022, 12:03 a.m.

标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/113/。

Qt 6.3 重镑发布了新的 Qt Quick 编译器,号称性能至少有 30% 的提升,能够以接近 native 的速度运行 Qt Quick 程序。我觉得这是一个在工程上相当漂亮的技术,所以在此做了一个介绍。

实际上,Qt 的这个技术已经是第三次迭代了。可能很多人对 Qt Quick 并不了解,这里也顺便介绍一下。这个技术的主要目标是使用 JavaScript 开发 GUI,与 vue, reactjs, electron 这些技术不一样,它不依赖于浏览器环境,而是只使用了 JavaScript 语言,操纵 Qt 的控件。除了在响应用户操作时,日常的渲染都由 C++, OpenGL 负责。 因此,相对于 vue/reactjs,Qt Quick 快很多。Qt 基于 JavaScript 发明了一个类似于 JSON 的文档格式,并称为 QML 语言。注意 Qt Quick 是指一整套 GUI 编程的方案,而 QML 是个基于 JavaScript 的语言。QML 语言是 Qt Quick 的一部分,可以单独拿出来使用。

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    Rectangle {
        color: "red"
        anchors.centerIn: parent
        width: parent.width / 4
        height: parent.height / 4
    }
    Text {
        color: "white"
        text: "fish is here."
        anchors.centerIn: parent
    }
}

发明于 Qt 被 Nokia 收购的那段时间,Nokia 原本打算将 Qt Quick 作为赛班以及新 Meego 操作系统的官方 GUI 开发工具,用于对抗 iOS 的 objc 和 Android 的 Java. 拍脑袋想想也知道,在那个年代,用 JavaScript 这个当时慢得不行的语言开发 GUI 会是什么下场。很快 Nokia Meego 中了微软的木马计,不战而败于 iOS 和 Android,与 Windows Phone 双双被扫入历史的垃圾堆,正好让 Qt Quick 幸免于在世人面前丢脸出洋相的难堪。

不过可能因为路径依赖,又以及用 JavaScript 写 GUI 总比用 C++ 方便多了。所以 Qt 一直在这条路狂奔不止。从原本的 Qt Quick 1.0 进化到 2.0; 从原本的用户层 C++ 渲染进化到使用 OpenGL 渲染,再到现在使用 vulkan, directx 渲染; 从原本简单的少量 2D 控件,进化到现在几乎包含了一个游戏引擎。Qt 这十年,做的事情真不少。

一个严重的阻碍促使 Qt 重写 Qt Quick 底层的 QML 语言——因为 iOS 禁止使用 JIT 的应用上传到应用市场。并且在每次执行 QML 代码时还要加载并重新解析语法树拖累了 Qt 程序的启动速度。

为了解决这个问题,Qt 在 5.3 时代,推出了第一版的 Qt Quick 编译器 qmlcompiler. 它的原理类似于 Python 的 Cython,能够把 QML 和 JavaScript 编译成对 QML 虚拟机(Google v8)的调用,以此解决了 iOS 上 JIT 的问题,并且还隐藏了用户的 JavaScript 代码。

接着在 Qt 5.8 的时候,Qt 大概是已经放弃移动端路径了,不管他有没有 JIT 了,使用了类似于 Java 的 .class 或者 Python 的 .pyc 技术,直接把编译好的字节码存储起来,交给 QML 虚拟机执行。这个新的命令叫做 qmlcachegen. 没想到这么做反而更快。Qt 的解释是因为对热门代码进行 JIT,能达到更快的速度。顺便,在这个阶段,Qt 重新实现了自己的 JavaScript 引擎,从最早的从 Google 那边拿来的 v8 引擎,重写成自己的 v4 引擎。

正在这个新的可控的 v4 引擎,使得 Qt 能够更自由地实现第三版的 Qt Quick Compiler. 其基本原理是改造 QML 语言,现在已经不是一个纯粹的 JavaScript 了,而是类似于 TypeScript,能够在变量声明、函数参数、函数返回值添加类型标志:

例1, QML 文档声明类型

Item {
    id: root
    property int dynamicWidth: 10
    Rectangle {
         height: root.dynamicWidth + (5 * 3) - 8
    }
}

例2, JavaScript 声明函数

function doThings(a: int, b: string) : QtObject { ... }

例3, JavaScript 声明变量类型:

console.debug("hello, " + (name as String) + "!");

有了类型以后,Qt 提供了两个部件也就顺理成章了:

  1. QML 脚本编译器qmlsc,负责把 JavaScript 脚本编译成 C++ 代码。
  2. QML 类型编译器qmltc,负责把 QML 文档编译成 C++ 代码。

脚本编译器仍然不能完整翻译动态的 JavaScript 语法,但是能够准确地识别其中 QML 类型的属性访问,算术指令和 ifelse 判断,循环等等结构并翻译成 C++. 其它的仍然被翻译成字节码供 QML 虚拟机解释执行或者 JIT. 目前脚本编译器暂时只对 Qt Device Creation 商业用户开放。普通的开源用户,仍然只能使用自 Qt 5.8 以来的qmlgencache.

而类型编译器,则能够比较准确地把 QML 文档翻译成 C++ 代码,目前这个 QML 类型编译器已经向所有 Qt 6.3 用户开放了。

参考文档:

  • https://www.qt.io/blog/the-new-qtquick-compiler-technology
  • https://doc.qt.io/qt-6/qtqml-javascript-hostenvironment.html#type-annotations-and-assertions
  • https://www.qt.io/blog/qml-type-registration-in-qt-5.15

标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/113/。


暂时还没有任何评论。


何不来发表一下您对本文的看法(使用Markdown语法,分段空两行):