强烈的希望是人生中比任何欢乐更大的兴奋剂。——尼采


上一篇文章讲了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)的更多相关文章

  1. 深度解析qml引擎---(1)Qml文件加载

                                                                        "美的事物是永恒的喜悦" --- 济慈    ...

  2. 深入解析QML引擎, 第3部分: 绑定类型

    原文 QML Engine Internals, Part 3: Binding Types 译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/Q ...

  3. 深入解析QML引擎, 第2部分: 绑定(Bindings)

    原文  QML Engine Internals, Part 2: Bindings 译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好 ...

  4. 深入解析QML引擎, 第1部分:QML文件加载

    译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来.翻译并不是完 ...

  5. 深入解析QML引擎, 第4部分: 自定义解析器

    原文 QML Engine Internals, Part 4: Custom Parsers ——————————————————————————————————————————— 上一篇 绑定类型 ...

  6. [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析

    [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...

  7. QML引擎的演进,第一部分

    原文链接:Lars Knoll – Evolution of the QML engine, part 1 QML作为一项技术对于Qt的成功变得越来越重要.它允许创建流畅的动画界面,与现今的市场预期相 ...

  8. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  9. Flink Connector 深度解析

    作者介绍:董亭亭,快手大数据架构实时计算引擎团队负责人.目前负责 Flink 引擎在快手内的研发.应用以及周边子系统建设.2013 年毕业于大连理工大学,曾就职于奇虎 360.58 集团.主要研究领域 ...

随机推荐

  1. EntityFramework6 学习笔记(二)

    使用EF对数据库进行操作,整个过程就像操作数组一样,我们只管修改或向集合中添加值,最后通知EF保存修改后的结果就可以了. 准备工作 为了演示,我在数据库中建了两张表.class表用于表示班级,clas ...

  2. js判断是否是在微信浏览器中打开

    // js判断是否是在微信浏览器中打开 function is_weixn(){ var ua = navigator.userAgent.toLowerCase(); if(ua.match(/Mi ...

  3. WinDbg常用命令系列---线程栈中局部上下文切换.frame

    .frame (Set Local Context) .frame命令指定使用哪个本地上下文(作用域)解释本地变量或显示当前本地上下文. .frame [/c] [/r] [FrameNumber] ...

  4. Windbg命令的语法规则系列(三)

    五.源文件行语法 可以将源文件行号指定为MASM表达式的全部或部分.这些数字计算出与该源代码行对应的可执行代码的偏移量.不能使用源代码行作为C++表达式的一部分.必须用重音符(`)将源文件和行号表达式 ...

  5. jaeger 使用scylladb作为后端存储

    scylladb 是一个不错的apache Cassandra 替代,而且兼容很不错,今天在尝试过yugabyte 之后放弃了,因为在进行jaeger 创建 Cassandra schema 的时候碰 ...

  6. lettcode 上的几道哈希表与链表组合的数据结构题

    目录 LRU缓存 LFU缓存 全O(1)的数据结构 lettcode 上的几道哈希表与链表组合的数据结构题 下面这几道题都要求在O(1)时间内完成每种操作. LRU缓存 LRU是Least Recen ...

  7. 咕泡学院java架构vip课程

    1.wps文档地址 https://docs.qq.com/doc/DRVNLUndvTmFSdEhO 2.百度网盘地址 https://pan.baidu.com/s/1uxaTzJZHKrsw_H ...

  8. PHP 之Mysql优化

    一.建立索引 普通索引 index: 对关键字没有要求. 唯一索引 unique index: 要求关键字不能重复.同时增加唯一约束. 主键索引 primary key: 要求关键字不能重复,也不能为 ...

  9. OpenFOAM——三角腔驱流

    本算例来自<ANSYS Fluid Dynamics Verification Manual>中的VMFL011: Laminar Flow in a Triangular Cavity ...

  10. 浅析package.json中的devdependencies 和 dependencies

    2.devDependencies (1)内容:是一个对象,配置模块依赖的模块列表,key是模块名称,value是版本范围(2)作用:该模块中所列举的插件属于开发环境的依赖(比如:测试或者文档框架等) ...