深度解析qml引擎---(2)绑定(binding)
强烈的希望是人生中比任何欢乐更大的兴奋剂。——尼采
上一篇文章讲了QML引擎加载qml文件的过程,大体过程是,解析qml文件,然后为文件中的每个元素创建对应的c++对象。例如,qml文件中如果使用了Text类型,引擎会创建对应的QQuickText类的实例。
qml文件被引擎加载之后,在运行阶段,QML引擎并没有很多地参与, 事件处理和场景绘制是由C++类完成的。例如,qml的中的TextInput控件类型对应的c++类是QQuickTextInput,该控件的输入事件由QQuickTextInput::keyPressEvent() 处理,绘制由QQuickTextInput::updatePaintNode()处理,QML引擎并没有再参与。
在运行阶段,QML引擎仍然参与了两个事情: Bound signal handlers 和 property binding updates。例如,MouseArea控件的onClicked信号会与一个处理函数对应起来,这就是Bound signal handlers.
import QtQuick 2.0
Rectangle {
width: 300
height: 300
color: "lightsteelblue"
Text {
anchors.centerIn: parent
text: "Window Area: " + (parent.width * parent.height)
}
}
如上所示例子,它涵盖了两种类型的属性绑定:
1)简单的属性赋值(Simple value assignments)。 例如,QQuickRectangle的width属性被赋值为300. 这个赋值动作对应的VME instruction是 STORE_DOUBLE, 该指令会在创建c++对象时被执行,VME会调用QMetaObject::metacall(QMetaObject::WriteProperty, …),该函数最终调用QQuickRectangle::setWidth(). 设置完成后,QML引擎就不会在更改这个width属性值了。
2)属性绑定(Binding assignments)。 例如,上例中的,Text的text属性与其parent的width属性相关联,当其parent的width属性变化时,其text的值自动相应变化,内部时如何实现的呢,请看下面对binding的分析:
Creating the Binding
令QML_COMPILER_DUMP=1, 我们可以看到bytecode中包含的instructions如下所示(关于bytecode请参考上一篇文章),两种类型的绑定对应的instruction都是STORE_COMPILED_BINDING。
...
9 STORE_COMPILED_BINDING 43 1 0
10 FETCH 19
11 STORE_COMPILED_BINDING 17 0 1
...
Compiled binding方式是一种优化绑定方式,在这片文章中我们先看普通的绑定,普通的绑定所对应的instruction是STORE_BINDING。
查看 QQmlVME::run()代码,可以发现该函数会创建一个 QQmlBinding 对象,该对象拥有表达式:“function $text() { return “Window Area: ” + (parent.width * parent.height) }” . 对应的是一个javaScript函数,该表达是中的“function $text()”部分是由QML 编译器添加的,之所以要添加,是因为 QML使用了javaScript V8引擎,该引擎只支持完整的函数。 这个JavaScript函数紧接着会被V8编译器编译成一个V8::Function对象, 这时,V8:: Function对象并不会被执行,但是它会一直保留。
(传统的JavaScript引擎是把JavaScript代码先编译为字节码,然后再通过解释器执行字节码,V8引擎运用JIT技术,不通过解释器执行字节码,而是直接把JavaScript代码编译成运行在CPU(x86/x64/ARM)上的机器码)。
STORE_BINDING指令创建一个绑定可总结为:先创建了一个QQmlBinding对象,然后该对象借助V8引擎把传给它的JavaScript函数编译成了一个V8::Function对象。
Running the Binding
在某些时候,需要运行绑定的函数(上面讲的javaScript函数),这时V8引擎会运行绑定行数并将结果赋值给相应的属性。这些都是在creating阶段(创建c++对象的阶段)的最后阶段完成的,在最后阶段 QQmlVME::complete()会调用每个绑定对象的update()函数,在我们的例子中就是QQmlBinding:: update()函数。update()只是简单的执行v8:Function对象并将返回值赋给目标属性, 这在我们的例子中就是Rectangle的text属性。
但是V8引擎是怎么知道parent.width和parent.height的值的呢?实际上,它不知道。V8引擎没有任何线索知道到qml文件中包含了哪些QObject对象(每个qml基本类型对应的c++类都是继承自QObject类的),也不知道他们 之间的层次关系,也不知道每个对象都有哪些属性。
当V8引擎遇到一个未知类对象或未知属性时,它会询问QML引擎中的一个对象包裹器(Object Wrapper),这个对象包裹器会为它找到正确的类或属性,并把它们返回给V8引擎。下面我们通过堆栈信息来看一看QQuickItem的width属性是如何被访问的:
#0 QQuickItem::width (this=0x6d8580) at items/qquickitem.cpp:4711
#1 0x00007ffff78e592d in QQuickItem::qt_metacall (this=0x6d8580, _c=QMetaObject::ReadProperty, _id=8, _a=0x7fffffffc270) at .moc/debug-shared/moc_qquickitem.cpp:675
#2 0x00007ffff7a61689 in QQuickRectangle::qt_metacall (this=0x6d8580, _c=QMetaObject::ReadProperty, _id=9, _a=0x7fffffffc270) at .moc/debug-shared/moc_qquickrectangle_p.cpp:526
#3 0x00007ffff7406dc3 in ReadAccessor::Direct (object=0x6d8580, property=..., output=0x7fffffffc2c8, n=0x0) at qml/v8/qv8qobjectwrapper.cpp:243
#4 0x00007ffff7406330 in GenericValueGetter (info=...) at qml/v8/qv8qobjectwrapper.cpp:296
#5 0x00007ffff49bf16a in v8::internal::JSObject::GetPropertyWithCallback (this=0x363c64f4ccb1, receiver=0x363c64f4ccb1, structure=0x1311a45651a9, name=0x3c3c6811b7f9) at ../3rdparty/v8/src/objects.cc:198
#6 0x00007ffff49c11c3 in v8::internal::Object::GetProperty (this=0x363c64f4ccb1, receiver=0x363c64f4ccb1, result=0x7fffffffc570, name=0x3c3c6811b7f9, attributes=0x7fffffffc5e8)
at ../3rdparty/v8/src/objects.cc:627
#7 0x00007ffff495c0f1 in v8::internal::LoadIC::Load (this=0x7fffffffc660, state=v8::internal::UNINITIALIZED, object=..., name=...) at ../3rdparty/v8/src/ic.cc:933
#8 0x00007ffff4960ff5 in v8::internal::LoadIC_Miss (args=..., isolate=0x603070) at ../3rdparty/v8/src/ic.cc:2001
#9 0x000034b88ae0618e in ?? ()
...
[more ?? frames from the JIT'ed v8::Function code]
...
#1 0x00007ffff481c3ef in v8::Function::Call (this=0x694fe0, recv=..., argc=0, argv=0x0) at ../3rdparty/v8/src/api.cc:3709
#2 0x00007ffff7379afd in QQmlJavaScriptExpression::evaluate (this=0x6d7430, context=0x6d8440, function=..., isUndefined=0x7fffffffcd23) at qml/qqmljavascriptexpression.cpp:171
#3 0x00007ffff72b7b85 in QQmlBinding::update (this=0x6d7410, flags=...) at qml/qqmlbinding.cpp:285
#4 0x00007ffff72b8237 in QQmlBinding::setEnabled (this=0x6d7410, e=true, flags=...) at qml/qqmlbinding.cpp:389
#5 0x00007ffff72b8173 in QQmlBinding::setEnabled (This=0x6d7448, e=true, f=...) at qml/qqmlbinding.cpp:370
#6 0x00007ffff72c15fb in QQmlAbstractBinding::setEnabled (this=0x6d7448, e=true, f=...) a /../../qtbase/include/QtQml/5.0.0/QtQml/private/../../../../../../qtdeclarative/src/qml/qml/qqmlabstractbinding_p.h:98
#7 0x00007ffff72dcb14 in QQmlVME::complete (this=0x698930, interrupt=...) at qml/qqmlvme.cpp:1292
#8 0x00007ffff72c72ae in QQmlComponentPrivate::complete (enginePriv=0x650560, state=0x698930) at qml/qqmlcomponent.cpp:919
#9 0x00007ffff72c739b in QQmlComponentPrivate::completeCreate (this=0x698890) at qml/qqmlcomponent.cpp:954
#10 0x00007ffff72c734c in QQmlComponent::completeCreate (this=0x698750) at qml/qqmlcomponent.cpp:947
#11 0x00007ffff72c6b2f in QQmlComponent::create (this=0x698750, context=0x68ea30) at qml/qqmlcomponent.cpp:781
#12 0x00007ffff79d4dce in QQuickView::continueExecute (this=0x7fffffffd2f0) at items/qquickview.cpp:445
#13 0x00007ffff79d3fca in QQuickViewPrivate::execute (this=0x64dc10) at items/qquickview.cpp:106
#14 0x00007ffff79d4400 in QQuickView::setSource (this=0x7fffffffd2f0 at items/qquickview.cpp:243
#15 0x0000000000400d70 in main ()
We can see that the wrapper is in qv8qobjectwrapper.cpp and ends up calling QObject::qt_metacall(QMetaObject::ReadProperty, …) to get the property value. The wrapper was called from v8 code, which itself was called by generated machine code of our v8::Function object. The generated machine code doesn’t have stack frames, and therefore GDB is unable to show the backtrace after the ??. I cheated a bit and pieced together this backtrace from two separate backtraces, which explains the inconsistent frame numbering.
从上面的堆栈信息来看,我们发现qv8qobjectwrapper.cpp中的包裹类最终调用函数QObject::qt_metacall(QMetaObject::ReadProperty,…) 来获取属性值。
So the v8 engine involves an object wrapper to get property values. In the same vein, it involves a context wrapper to find objects themselves, for example the parent object that is accessed during binding evaluation.
To sum up: A binding is evaluated by running the compiled v8::Function code. The v8 engine access unknown objects and properties by calling out to wrappers in Qt. The result returned by the v8::Function is then written to the target property.
Updating the Binding
好了,现在我们知道text属性是如何获得它的初始值的。但是绑定更新是如何实现的?当height和width属性改变时,QML引擎是怎么知道需要重新对绑定求值的呢?
这个问题的答案就隐藏在对象包裹类中。你应该还记得,当V8引擎需要访问一个属性时,就会调用它。这个对象包裹类不止返回属性值:它还会捕获所有被访问过的属性。从根本上讲,当一个属性被访问时,对象包裹类会调用绑定对象的捕获函数,在我们的例子中就是QQmlJavaScriptExpression::GuardCapture::captureProperty() (QQmlBinding是QQmlJavaScriptExpression的子类)。在捕获函数内部实现中,只是简单地把绑定对象的一个槽函数连接到被捕获属性的NOTIFY信号。当NOTIFY信号被触发时,与之连接的槽函数就会被调用,并重新计算绑定的值。如果你还没有听说过NOTIFY信号,也不用担心,这很简单:当一个属性用Q_PROPERTY来声明时,在那里就可能声明了一个NOTIFY信号。只要属性发生改变,拥有该属性的对象就会触发NOTIFY信号。比如,QQuickItem的width属性的声明类似如下:
Q_PROPERTY(qrealwidth READ width WRITE setWidth NOTIFY widthChanged)
在我们这个例子中,首次运行绑定,访问width属性时,该属性的捕获函数将绑定对象中的一个槽函数连接到widthChanged()信号。在此之后,只要QQuickItem触发widthChanged()信号,对应的槽函数将被调用,并重新计算绑定的值。
这就是为什么当你的属性发生改变时,拥有并触发NOTIFY信号是非常的重要。假如你忘了这样做,绑定的值就不会被重新计算,基本上,属性绑定就无法正确的运作。另一方面,尽管属性并没有真正地改变,但你也触发了NOTIFY信号,那么绑定的值也会被毫无意义地重新计算。
综上所述:当访问属性时,对象包裹类会调用绑定对象的捕捉函数,它会将绑定对象的一个槽函数连接到该属性的NOTIFY信号,以便当属性改变时重新计算绑定的值。
Ref:
http://www.kdab.com/qml-engine-internals-part-2-bindings/
https://www.jianshu.com/p/9b277a3ee613
深度解析qml引擎---(2)绑定(binding)的更多相关文章
- 深度解析qml引擎---(1)Qml文件加载
"美的事物是永恒的喜悦" --- 济慈 ...
- 深入解析QML引擎, 第3部分: 绑定类型
原文 QML Engine Internals, Part 3: Binding Types 译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/Q ...
- 深入解析QML引擎, 第2部分: 绑定(Bindings)
原文 QML Engine Internals, Part 2: Bindings 译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好 ...
- 深入解析QML引擎, 第1部分:QML文件加载
译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来.翻译并不是完 ...
- 深入解析QML引擎, 第4部分: 自定义解析器
原文 QML Engine Internals, Part 4: Custom Parsers ——————————————————————————————————————————— 上一篇 绑定类型 ...
- [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析
[WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...
- QML引擎的演进,第一部分
原文链接:Lars Knoll – Evolution of the QML engine, part 1 QML作为一项技术对于Qt的成功变得越来越重要.它允许创建流畅的动画界面,与现今的市场预期相 ...
- mybatis 3.x源码深度解析与最佳实践(最完整原创)
mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...
- Flink Connector 深度解析
作者介绍:董亭亭,快手大数据架构实时计算引擎团队负责人.目前负责 Flink 引擎在快手内的研发.应用以及周边子系统建设.2013 年毕业于大连理工大学,曾就职于奇虎 360.58 集团.主要研究领域 ...
随机推荐
- docker的daemon配置
文件:/etc/docker/daemon.json,如果没有就创建 修改后重启生效:systemctl restart docker 示例内容: { "registry-mirrors&q ...
- 指数基金介绍专栏(4):上证50AH优选指数
作者:牛大 | 公众号:定投五分钟 大家好,我是牛大.每天五分钟,投资你自己:坚持基金定投,终会财富自由! 想必大家会有疑问,什么是上证50AH优选指数?今天老师给大家答疑解惑,详细介绍一下上证50A ...
- 留言板(Nodejs)
一.知识点 ①nodejs实际开发中,通常把所有的HTML文件放到views目录中 ②nodejs实际开发中,通常把所有的静态资源文件放到public目录中,方便统一处理 当浏览器收到HTML响应内容 ...
- PowerDesigner 创建表的时候 没有自增长Id的设置项
今天早上同事创建表的时候,在那个界面没有自增长Id的选项,当时我也纳闷,软件肯定都是一样的,设置的步骤都一样(有些配置好的 我就没改过 然后就忘了还改过些什么步骤了),结果还是没有那个选项 百度了一下 ...
- 【JOISC2018|2019】【20190622】minerals
题目 交互题 有\(2n\)个物品,编号为\(1-2n\),存在唯一的两两配对关系,即有\(n\)种物品 有一个盒子,初始为空,盒子上会显示里面存在的物品种类数\(C\) 你每次操作可以将一个物品从盒 ...
- 64、Spark Streaming:StreamingContext初始化与Receiver启动原理剖析与源码分析
一.StreamingContext源码分析 ###入口 org.apache.spark.streaming/StreamingContext.scala /** * 在创建和完成StreamCon ...
- 【luoguP5091】【模板】欧拉定理
题目链接 欧拉定理: 当\(a\),\(m\)互质时,\(a^{\phi(m)}\equiv 1 (mod ~ m)\) 扩展欧拉定理: 当\(B>\phi(m)\)时,\(a^B\equiv ...
- go语言new和make
1.new func new(Type) *Type 内建函数,内建函数 new 用来分配内存,它的第一个参数是一个类型,它的返回值是一个指向新分配类型默认值的指针! 2.make func make ...
- Codeforces - 1264C - Beautiful Mirrors with queries - 概率期望dp
一道挺难的概率期望dp,花了很长时间才学会div2的E怎么做,但这道题是另一种设法. https://codeforces.com/contest/1264/problem/C 要设为 \(dp_i\ ...
- MongoDB 关系型数据库表(集合)与表(集合)之间的几种关系
简述关系数据库中表与表的 3 种关系 一对一的关系:例如:一个人对应一个唯一的身份证号,即为一对一的关系. 一对多关系 :例如:一个班级对应多名学生,一个学生只能属于一个班级,即为一对多关系 多对多关 ...