基于Qt4.8.6版本
Qt 的信号槽和属性系统基于在运行时进行内省的能力,所谓内省是指面向对象语言的一种在运行期间查询对象信息的能力, 好比若是语言具备运行期间检查对象型别的能力,那么是型别内省(type intropection)的,型别内省能够用来实施多态。
C++的内省比较有限,仅支持型别内省, C++的型别内省是经过运行时类型识别(RTTI)(Run-Time Type Information)中的typeid 以及 dynamic_cast关键字来实现的。
Qt拓展了C++的内省机制,但并无采用C++的RTTI,而是提供了更为强大的元对象(meta object)机制,来实现内省机制。基于内省机制,能够列出对象的方法和属性列表,而且可以获取有关对象的全部信息,如参数类型。若是没有内省机制,QtScript和 QML是难以实现的。
Qt中的元对象系统全称Meta Object System,是一个基于标准C++的扩展,为Qt提供了信号与槽机制、实时类型信息、动态属性系统。元对象系统基于QObject类、Q_OBJECT宏、元对象编译器MOC实现。
A、QObject 类
做为每个须要利用元对象系统的类的基类。
B、Q_OBJECT宏
定义在每个类的私有数据段,用来启用元对象功能,好比动态属性、信号和槽。
在一个QObject类或者其派生类中,若是没有声明Q_OBJECT宏,那么类的metaobject对象不会被生成,类实例调用metaObject()返回的就是其父类的metaobject对象,致使的后果是从类的实例得到的元数据其实都是父类的数据。所以类所定义和声明的信号和槽都不能使用,因此,任何从QObject继承出来的类,不管是否认义声明了信号、槽和属性,都应该声明Q_OBJECT 宏。
C、元对象编译器MOC (Meta Object Complier),
MOC分析C++源文件,若是发如今一个头文件(header file)中包含Q_OBJECT 宏定义,会动态的生成一个moc_xxxx命名的C++源文件,源文件包含Q_OBJECT的实现代码,会被编译、连接到类的二进制代码中,做为类的完整的一部分。程序员
元对象系统除了提供信号槽机制在对象间进行通信的功能,还提供了以下功能:
QObject::metaObject() 方法
得到与一个类相关联的 meta-object
QMetaObject::className() 方法
在运行期间返回一个对象的类名,不须要本地C++编译器的RTTI(run-time type information)支持
QObject::inherits() 方法
用来判断生成一个对象类是否是从一个特定的类继承出来,必须是在QObject类的直接或者间接派生类当中。
QObject::tr() and QObject::trUtf8()
为软件的国际化翻译字符串
QObject::setProperty() and QObject::property()
根据属性名动态的设置和获取属性值
使用qobject_cast()方法在QObject类之间提供动态转换,qobject_cast()方法的功能相似于标准C++的dynamic_cast(),但qobject_cast()不须要RTTI的支持。编程
#define Q_PROPERTY(text)
Q_PROPERTY定义在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC处理。数组
Q_PROPERTY(type name READ getFunction [WRITE setFunction] [RESET resetFunction] [NOTIFY notifySignal] [REVISION int] [DESIGNABLE bool] [SCRIPTABLE bool] [STORED bool] [USER bool] [CONSTANT] [FINAL])
Type:属性的类型
Name:属性的名称
READ getFunction:属性的访问函数
WRITE setFunction:属性的设置函数
RESET resetFunction:属性的复位函数
NOTIFY notifySignal:属性发生变化的地方发射的notifySignal信号
REVISION int:属性的版本,属性暴露到QML中
DESIGNABLE bool:属性在GUI设计器中是否可见,默认为true
SCRIPTABLE bool:属性是否能够被脚本引擎访问,默认为true
STORED bool:
USER bool:
CONSTANT:标识属性的值是常量,值为常量的属性没有WRITE、NOTIFY
FINAL:标识属性不会被派生类覆写
注意:NOTIFY notifySignal声明了属性发生变化时发射notifySignal信号,但并无实现,所以程序员须要在属性发生变化的地方发射notifySignal信号。
Object.h:数据结构
#ifndef OBJECT_H #define OBJECT_H #include <QObject> #include <QString> #include <QDebug> class Object : public QObject { Q_OBJECT Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged) Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged) Q_CLASSINFO("Author", "Scorpio") Q_CLASSINFO("Version", "1.0") Q_ENUMS(Level) protected: QString m_name; QString m_level; int m_age; int m_score; public: enum Level { Basic, Middle, Advanced }; public: explicit Object(QString name, QObject *parent = 0):QObject(parent) { m_name = name; setObjectName(m_name); connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int))); connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int))); } int age()const { return m_age; } void setAge(const int& age) { m_age = age; emit ageChanged(m_age); } int score()const { return m_score; } void setScore(const int& score) { m_score = score; emit scoreChanged(m_score); } signals: void ageChanged(int age); void scoreChanged(int score); public slots: void onAgeChanged(int age) { qDebug() << "age changed:" << age; } void onScoreChanged(int score) { qDebug() << "score changed:" << score; } }; #endif // OBJECT_H
Main.cpp:框架
#include <QCoreApplication> #include "Object.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Object ob("object"); //设置属性age ob.setProperty("age", QVariant(30)); qDebug() << "age: " << ob.age(); qDebug() << "property age: " << ob.property("age").toInt(); //设置属性score ob.setProperty("score", QVariant(90)); qDebug() << "score: " << ob.score(); qDebug() << "property score: " << ob.property("score").toInt(); //内省intropection,运行时查询对象信息 qDebug() << "object name: " << ob.objectName(); qDebug() << "class name: " << ob.metaObject()->className(); qDebug() << "isWidgetType: " << ob.isWidgetType(); qDebug() << "inherit: " << ob.inherits("QObject"); return a.exec(); }
#define Q_INVOKABLE
Q_INVOKABLE定义在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC识别。
Q_INVOKABLE宏用于定义一个成员函数能够被元对象系统调用,Q_INVOKABLE宏必须写在函数的返回类型以前。以下:
Q_INVOKABLE void invokableMethod();
invokableMethod()函数使用了Q_INVOKABLE宏声明,invokableMethod()函数会被注册到元对象系统中,可使用 QMetaObject::invokeMethod()调用。
Q_INVOKABLE与QMetaObject::invokeMethod均由元对象系统唤起,在Qt C++/QML混合编程、跨线程编程、Qt Service Framework以及 Qt/ HTML5混合编程以及里普遍使用。
A、在跨线程编程中的使用
如何调用驻足在其余线程里的QObject方法呢?Qt提供了一种很是友好并且干净的解决方案:向事件队列post一个事件,事件的处理将以调用所感兴趣的方法为主(须要线程有一个正在运行的事件循环)。而触发机制的实现是由MOC提供的内省方法实现的。所以,只有信号、槽以及被标记成Q_INVOKABLE的方法才可以被其它线程所触发调用。若是不想经过跨线程的信号、槽这一方法来实现调用驻足在其余线程里的QObject方法。另外一选择就是将方法声明为Q_INVOKABLE,而且在另外一线程中用invokeMethod唤起。
B、Qt Service Framework
Qt服务框架是Qt Mobility 1.0.2版本推出的,一个服务(service)是一个独立的组件提供给客户端(client)定义好的操做。客户端能够经过服务的名称,版本号和服务的对象提供的接口来查×××。 查找到服务后,框架启动服务并返回一个指针。
服务经过插件(plug-ins)来实现。为了不客户端依赖某个具体的库,服务必须继承自QObject,保证QMetaObject 系统能够用来提供动态发现和唤醒服务的能力。要使QmetaObject机制充分的工做,服务必须知足,其全部的方法都是经过 signal、slot、property或invokable method和Q_INVOKEBLE来实现。ide
QServiceManager manager; QObject *storage ; storage = manager.loadInterface("com.nokia.qt.examples.FileStorage"); if(storage) QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt"));
上述代码经过service的元对象提供的invokeMethod方法,调用文件存储对象的deleteFile() 方法。客户端不须要知道对象的类型,所以也没有连接到具体的service库。 固然在服务端的deleteFile方法,必定要被标记为Q_INVOKEBLE,才可以被元对象系统识别。
Qt服务框架的一个亮点是它支持跨进程通讯,服务能够接受远程进程。在服务管理器上注册后,进程经过signal、slot、invokable method和property来通讯,就像本地对象同样。服务能够设定为在客户端间共享,或针对一个客户端。 在Qt服务框架推出以前,信号、槽以及invokable method仅支持跨线程。 下图是跨进程的服务/客户段通讯示意图。invokable method和Q_INVOKEBLE 是跨进城、跨线程对象之间通讯的重要利器。函数
任何从QObject派生的类都包含本身的元数据模型,通常经过宏Q_OBJECT定义。
Q_OBJECT定义在/src/corelib/kernel/Qobjectdefs.h文件中。工具
#define Q_OBJECT \ public: \ Q_OBJECT_CHECK \ static const QMetaObject staticMetaObject; \ Q_OBJECT_GETSTATICMETAOBJECT \ virtual const QMetaObject *metaObject() const; \ virtual void *qt_metacast(const char *); \ QT_TR_FUNCTIONS \ virtual int qt_metacall(QMetaObject::Call, int, void **); \ private: \ Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData; \ Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
QMetaObject类型的静态成员变量staticMetaObject是元数据的数据结构。metaObject,qt_metacast,qt_metacall、qt_static_metacall四个虚函数由MOC在生成的moc_xxx.cpp文件中实现。metaObject的做用是获得元数据表指针;qt_metacast的做用是根据签名获得相关结构的指针,返回void*指针;qt_metacall的做用是查表而后调用调用相关的函数;qt_static_metacall的做用是调用元方法(信号和槽)。#define Q_DECL_HIDDEN __attribute__((visibility("hidden")))
post
QMetaObject类定义在/src/corelib/kernel/Qobjectdefs.h文件。ui
struct Q_CORE_EXPORT QMetaObject { ... enum Call { InvokeMetaMethod, ReadProperty, WriteProperty, ResetProperty, QueryPropertyDesignable, QueryPropertyScriptable, QueryPropertyStored, QueryPropertyEditable, QueryPropertyUser, CreateInstance }; int static_metacall(Call, int, void **) const; static int metacall(QObject *, Call, int, void **); struct { // private data const QMetaObject *superdata; const char *stringdata; const uint *data; const void *extradata; } d; };
QMetaObject中有一个嵌套结构封装了全部的数据:
const QMetaObject superdata;//元数据表明的类的基类的元数据
const char stringdata;//元数据的签名标记
const uint *data;//元数据的索引数组的指针
const QMetaObject **extradata;//扩展元数据表的指针,指向QMetaObjectExtraData数据结构。
struct QMetaObjectExtraData { #ifdef Q_NO_DATA_RELOCATION const QMetaObjectAccessor *objects; #else const QMetaObject **objects; #endif typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **); //from revision 6 //typedef int (*StaticMetaCall)(QMetaObject::Call, int, void **); //used from revison 2 until revison 5 StaticMetacallFunction static_metacall; };
static_metacall是一个指向Object::qt_static_metacall 的函数指针。
宏QT_TR_FUNCTIONS是和翻译相关的。
#define QT_TR_FUNCTIONS \ static inline QString tr(const char *s, const char *c = 0) \ { return staticMetaObject.tr(s, c); } \ #endif
Qt在/src/corelib/kernel/Qobjectdefs.h文件中定义了大量的宏。
#ifndef Q_MOC_RUN # if defined(QT_NO_KEYWORDS) # define QT_NO_EMIT # else # define slots # define signals protected # endif # define Q_SLOTS # define Q_SIGNALS protected # define Q_PRIVATE_SLOT(d, signature) # define Q_EMIT #ifndef QT_NO_EMIT # define emit #endif #define Q_CLASSINFO(name, value) #define Q_INTERFACES(x) #define Q_PROPERTY(text) #define Q_PRIVATE_PROPERTY(d, text) #define Q_REVISION(v) #define Q_OVERRIDE(text) #define Q_ENUMS(x) #define Q_FLAGS(x) #define Q_SCRIPTABLE #define Q_INVOKABLE #define Q_SIGNAL #define Q_SLOT
Qt中的大部分宏都无实际的定义,都是提供给MOC识别处理的,MOC工具经过对类中宏的解析处理生成moc_xxx.cpp文件。
在 Qt4 及以前的版本中,signals被展开成protected。Qt5则变成public,用以支持新的语法。
A、处理Q_OBJECT宏和signals/slots关键字,生成信号和槽的底层代码
B、处理Q_PROPERTY()和Q_ENUM()生成property系统代码
C、处理Q_FLAGS()和Q_CLASSINFO()生成额外的类meta信息
D、不须要MOC处理的代码能够用预约义的宏括起来,以下:
#ifndef Q_MOC_RUN … #endif
A、模板类不能使用信号/槽机制
B、MOC不扩展宏,因此信号和槽的定义不能使用宏, 包括connect的时候也不能用宏作信号和槽的名字以及参数
C、从多个类派生时,QObject派生类必须放在第一个。 QObject(或其子类)做为多重继承的父类之一时,须要把它放在第一个。 若是使用多重继承,moc在处理时假设首先继承的类是QObject的一个子类,须要确保首先继承的类是QObject或其子类。
D、函数指针不能做为信号或槽的参数, 由于其格式比较复杂,MOC不能处理。能够用typedef把它定义成简单的形式再使用。
E、用枚举类型或typedef的类型作信号和槽的参数时,必须fully qualified。这个词中文不知道怎么翻译才合适,简单的说就是, 若是是在类里定义的, 必须把类的路径或者命名空间的路径都加上, 防止出现混淆。如Qt::Alignment之类的,前面的Qt就是Alignment的qualifier, 必须加上,并且有几级加几级。
F、信号和槽不能返回引用类型
G、signals和slots关键字区域只能放置信号和槽的定义,不能放其它的如变量、构造函数的定义等,友元声明不能位于信号或者槽声明区内。
H、嵌套类不能含有信号和槽
MOC没法处理嵌套类中的信号和槽,错误的例子:
class A:public QObject
{
Q_OBJECT
public:
class B
{
public slots://错误用法
};
};
I、信号槽不能有缺省参数
Qt线程间传递自定义类型数据时,本身定义的类型若是直接使用信号槽来传递的话会产生下面这种错误:
QObject::connect: Cannot queue arguments of type 'XXXXX' (Make sure 'XXXXX' is registed using qRegisterMetaType().)
缘由:当一个signal被放到队列中(queued)时,参数(arguments)也会被一块儿一块儿放到队列中,参数在被传送到slot以前须要被拷贝、存储在队列中;为了可以在队列中存储参数(argument),Qt须要去construct、destruct、copy参数对象,而为了让Qt知道怎样去做这些事情,参数的类型须要使用qRegisterMetaType来注册。
步骤:(以自定义XXXXX类型为例)
A、自定义类型时在类的顶部包含:#include <QMetaType>
B、在类型定义完成后,加入声明:Q_DECLARE_METATYPE(XXXXX);
C、在main()函数中注册自定义类类型:qRegisterMetaType<XXXXX>("XXXXX");
若是但愿使用类型的引用,一样要注册:qRegisterMetaType<XXXXX>("XXXXX&");
查看工程的Makefile文件能够查找到MOC生成moc_xxx.cpp文件的命令:
moc_Object.cpp: ../moc/Object.h /usr/local/Trolltech/Qt-4.8.6/bin/moc $(DEFINES) $(INCPATH) ../moc/Object.h -o moc_Object.cpp 所以命令行能够简化为:
`moc Object.h -o moc_Object.cpp`