</qt4GUI教程>
2.2深入介绍信号和槽
信号和槽机制是Qt编程的基础。它可以让应用程序编程人员把这些互不了解的对象绑定在一起。
Qt的元对象系统
Qt的主要成就之一就是使用了一种机制对C++进行了扩展,并且使用这种机制创建了独立的软件组件。这些组件可以绑定在一起,但任何一个组件对于它所要连接的组件的情况事先都一无所知。
这种机制称为元对象系统(meta-object system),它提供了关键的两项技术:信号---槽以及内省(introspection)内省功能对于实现信号和槽是必需的,并且允许应用程序的开发人员在运行时获得有关QObject子类的"元信息",包括一个含有对象的类名以及它所支持的信号和槽的列表。
标准C++没有对Qt的元对象系统所需要的动态元信息提供支持。Qt通过提供一个独立的moc工具解决了这个问题。Moc解析Q_OBJECT类的定义并且通过C++函数来提供可使用的信息。
这一机制是这样工作的:
Q_OBJECT宏声明了在每一个QObject子类中必须实现的一些内省函数:metaObject(),tr(),qt_metacall(),以及其他一些函数。
Qt的moc工具生成了角于由Q_OBJECT声明的所有函数和所有信号的实现。
像connnect() 和disconnect()这样的QObject的成员函数使用这些内省函数来完成它们的工作。
</qt4GUI教程>
//<网上代码>
#include "qobject.h"
class Informer:public QObject
{
Q_OBJECT;
public:
void notify()
{
for(int i=0;i<=10;i++)
{
emit send(i);
}
};
signals:
void send(int status);
};
class Receiver:public QObject
{
Q_OBJECT;
private:
int total;
public:
Receiver(){this->total=0;};
public slots:
void count(int i)
{
printf("Counting: %d+%d=%d\n",this->total,i,this->total+i);
this->total=this->total+i;
};
};
#include "main.moc"
#include <iostream>
int main(int argc, char* argv[])
{
Informer *s=new Informer();
Receiver *r1=new Receiver();
QObject::connect(s,SIGNAL(send(int)),r1,SLOT(count(int)));
s->notify();
delete s,r1;
return 0;
}
//</网上代码>
核心问题1:Informer如何在不知道Receiver的情况下,调用Receiver的方法
这个例子中,要调用的函数为Receiver中的void count(int i)方法。调用过程如下
void Informer::send(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
void **argv)
{
metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
}
int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{
return object->qt_metacall(cl, idx, argv);
}
int Receiver::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: count((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
_id -= 1;
}
return _id;
}
问题的核心变成了怎么样得到receiver这个对象。Receiver来源于QObject * const receiver = c->receiver; c来源于QObjectPrivate::Connection *c = connectionLists->at(signal_index).first;
connectionLists来源于
QObjectConnectionListVector *connectionLists = sender->d_func()->connectionLists;
也就是说,receiver对象被保存在sender对象中。
现在要解决的问题是,receiver对象是如何被保存到sender对象中的。查看源码,发现在QObject::connect中向connectionLists添加元素。connect函数已包含sender对象和receiver对象,只要将receiver对象添到sender的connectionLists中即可。在日后的使用中,便能找到recevier对象了。
核心问题2:触发信号函数时系统如何找到与信号函数匹配的接收函数
这个问题可分为两个小问题:
(1)Reciver对象可能有多个方法,qt如何知道要调用哪个函数.
(2)如何将参数传给reciver对应的函数
突破口在这个段代码中
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
void **argv)
{
//....................
metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
//....................
}
metacall函数声明如下
static int metacall(QObject *, Call, int, void **);
这个函数又调用了
return object->qt_metacall(cl, idx, argv);
Object是receiver对象,moc为receiver生成了qt_metacall函数。在这个函数内部,通过idx找出要调用的函数,参数地址在argv数组中。通过argv,加上强转,就可以得到参数了。
示例代码如下
int Receiver::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: count((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
_id -= 1;
}
return _id;
}
通过上面的代码,完成了调用过程。