一:信号槽是什么?
Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的说法,简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用
二:Qt支持三种类型的信号-槽连接:
1,直接连接,当signal发射时,slot立即调用。此slot在发射signal的那个线程中被执行(不一定是接收对象生存的那个线程)
2,队列连接,当控制权回到对象属于的那个线程的事件循环时,slot被调用。此slot在接收对象生存的那个线程中被执行
3,自动连接(缺省),假如信号发射与接收者在同一个线程中,其行为如直接连接,否则,其行为如队列连接。
连接类型可能通过以向connect()传递参数来指定。注意的是,当发送者与接收者生存在不同的线程中,而事件循环正运行于接收者的线程中,使用直接连接是不安全的。同样的道理,调用生存在不同的线程中的对象的函数也是不是安全的。QObject::connect()本身是线程安全的。
用Qt写Gui程序的时候,在main函数里面最后依据都是app.exec();很多书上对这句的解释是,使Qt程序进入消息循环。
当前connectionType为Qt::AutoConnection并且signal和slot不在一个线程或者是signal和不再当前线程中;或者是c->connectionType为 Qt::QueuedConnection这时候调用函数queued_activate:对参数转换一下,然后调用QCoreApplication::postEvent
注意: postEvent第二个参数是QMetaCallEvent。
这样这个signal-slot的connection就发送到receiver的消息队列中去了。
三:connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
这里用到了两个宏:SIGNAL() 和SLOT();通过connect声明可以知道这两个宏最后倒是得到一个const char*类型。
在qobjectdefs.h中可以看到SIGNAL() 和SLOT()的宏定义:
#ifndef QT_NO_DEBUG
# define QLOCATION " "__FILE__":"QTOSTRING(__LINE__)
# define METHOD(a) qFlagLocation("0"#a QLOCATION)
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
#else
# define METHOD(a) "0"#a
# define SLOT(a) "1"#a
# define SIGNAL(a) "2"#a
#endif
所以这两个宏的作用就是把函数名转换为字符串并且在前面加上标识符。
比如:SIGNAL(read())展开后就是"2read()";同理SLOT(read())展开后就是"1read()"。
connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
实际上就是connect(sender,“2signal()”,receiver,“1slot())”;
四:
# 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
signals宏有点不同,它限定Qt信号为protected方法,而slots宏可以是任意类型。
头文件定义的public slots和signals对C++编译器而言没有意义,会被替换成public和protected。他们的真实意图其实是给moc工具使用的,moc根据这些字符串关键字匹配,用来生成文件moc_Counter.cpp。Qt源码的构建过程是先moc转换然后再执行C++编译器。moc的目的是展开信号和槽,生成一个能让编译器读懂的源文件。
五:MOC元数据
Q_OBJECT创建QMetaObejct元对象数据成员:用来做信号槽的二维表。对于某一个信号,在二维表中存放着这个信号对应的槽的函数指针。依据这个信号的名字去查询二维表,找到对应的槽的函数指针并进行调用
Qt的信号槽机制其实就是按照名称查表,因此这里的首要问题是如何构造这个表?和C++虚函数表机制类似的,在Qt中,这个表就是元数据表
不过Qt似乎还没有完全发挥元数据的能力,动态属性,反射之类的机制
一般是通过宏Q_OBJECT定义的(内省函数)
#define Q_OBJECT /
public: /
static const QMetaObject staticMetaObject; /
virtual const QMetaObject *metaObject() const; /
virtual void *qt_metacast(const char *); /
QT_TR_FUNCTIONS /
virtual int qt_metacall(QMetaObject::Call, int, void **); /
private:
这里的三个虚函数metaObject,qt_metacast,qt_metacall是在moc文件中定义的
staticMetaObject是 QMetaObject对象,因为需要给属于同一类的全部实例共享,所以它是静态的。得到元数据表指针
metaObject方法仅仅返回staticMetaObject。
QT_TR_FUNCTIONS是一个用于所有tr函数的宏,用来实现多语言支持。
qt_metacast用于按照类名或它的某个基类名 进行动态转换(dynamic cast)【Qt显然不依赖运行时类型检查(RTTI)】。
qt_metacall通过索引查表调用内部信号和槽。参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。
用户设计的类可以从多个类派生,但只能拥有一个QObject(或从它派生)基类,这同时也是超类
class ConvDialog : public QDialog, private Ui::ConvDialog
{
Q_OBJECT
Moc将产生以下代码:
const QMetaObject ConvDialog::staticMetaObject = {
{ &QDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
qt_meta_data_ConvDialog, 0 }
};
如果在QDialog前先继承Ui::ConvDialog,moc将会生成:
const QMetaObject ConvDialog::staticMetaObject = {
{ &Ui::ConvDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
qt_meta_data_ConvDialog, 0 }
};
这是错误的,因为Ui::ConvDialog不是QObject的一个派生类,由此不拥有staticMetaObject成员,这样做只会导致一个编译错误。
QMetaObject中的结构体d
struct{
const QMetaObject *superdata;//这是元数据代表的类的基类的元数据
const char *stringdata;//这是元数据的签名标记
const uint *data;//这是元数据的索引数组的指针
const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的
}d;
第一个参数:QMetaObject类指针,指向父Qt元数据类。
第三个参数:无符号整型数组,这个数组是一个表,包含所有元数据的偏移、特征等等。所以,如果你想枚举一个类的信号和槽,那就应该遍历这个表,通过偏移量从stringdata数组中获得方法名。
参考文章:http://blog.csdn.net/tingsking18/article/details/4991563
http://www.cnblogs.com/findumars/p/4851262.html
http://www.pediy.com/kssd/pediy12/133181.html
http://www.devbean.net/2012/12/how-qt-signals-and-slots-work/