标签:
Qt的信号和槽机制是Qt的一大特点,实际上这是和MFC中的消息映射机制相似的东西,要完成的事情也差不多,就是发送一个消息然后让其它窗口响应,当然,这里的消息是广义的
说法,简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用
多了,也复杂多了
MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数开销太大.在Qt中也没有采用C++中的虚函数机制,原因与此相同.其实这里还有更深层次上的原因,大体说来,
多态的底层实现机制只有两种,一种是按照名称查表,一种是按照位置查表,两种方式各有利弊,而C++的虚函数机制无条件的采用了后者,导致的问题就是在子类很少重载基类实现
的时候开销太大,再加上象界面编程这样子类众多的情况,基本上C++的虚函数机制就废掉了,于是各家库的编写者就只好自谋生路了,说到底,这确实是C++语言本身的缺陷
示例代码:
- #include <QApplication>
- #include <QPushButton>
- #include <QPointer>
-
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
-
- QPushButton quit("Quit");
- quit.resize(100, 30);
- quit.show();
- QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit()));
-
- return app.exec();
- }
这里主要是看QPushButton的clicked()信号和app的quit()槽如何连接?又是如何响应?
前面已经说过了,Qt的信号槽机制其实就是按照名称查表,因此这里的首要问题是如何构造这个表?
和C++虚函数表机制类似的,在Qt中,这个表就是元数据表,Qt中的元数据表最大的作用就是支持信号槽机制,当然,也可以在此基础上扩展出更多的功能,因为
元数据是我们可以直接访问到的,不再是象虚函数表那样被编译器遮遮掩掩的藏了起来,不过Qt似乎还没有完全发挥元数据的能力,动态属性,反射之类的机制好像还没有
任何从QObject派生的类都包含了自己的元数据模型,一般是通过宏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:
首先声明了一个QMetaObject类型的静态成员变量,这就是元数据的数据结构
struct Q_CORE_EXPORT QMetaObject
{
...
struct { // private data
const QMetaObject *superdata;
const char *stringdata;
const uint *data;
const QMetaObject **extradata;
} d;
}
QMetaObject中有一个嵌套类封装了所有的数据
const QMetaObject *superdata;//这是元数据代表的类的基类的元数据
const char *stringdata;//这是元数据的签名标记
const uint *data;//这是元数据的索引数组的指针
const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的
这里的三个虚函数metaObject,qt_metacast,qt_metacall是在moc文件中定义的
metaObject的作用是得到元数据表指针
qt_metacast的作用是根据签名得到相关结构的指针,注意它返回的可是void*指针
qt_metacall的作用是查表然后调用调用相关的函数
宏QT_TR_FUNCTIONS是和翻译相关的
# define QT_TR_FUNCTIONS /
static inline QString tr(const char *s, const char *c = 0) /
{ return staticMetaObject.tr(s, c); }
好了,看看实际的例子吧:
QPushButton的元数据表如下:
- static const uint qt_meta_data_QPushButton[] = {
-
-
- 1,
- 0,
- 0, 0,
- 2, 10,
- 3, 20,
- 0, 0,
-
-
- 13, 12, 12, 12, 0x0a,
- 24, 12, 12, 12, 0x08,
-
-
- 44, 39, 0x01095103,
- 56, 39, 0x01095103,
- 64, 39, 0x01095103,
-
- 0
- };
-
- static const char qt_meta_stringdata_QPushButton[] = {
- "QPushButton/0/0showMenu()/0popupPressed()/0bool/0autoDefault/0default/0"
- "flat/0"
- };
-
- const QMetaObject QPushButton::staticMetaObject = {
- { &QAbstractButton::staticMetaObject, qt_meta_stringdata_QPushButton,
- qt_meta_data_QPushButton, 0 }
- };
在这里我们看到了静态成员staticMetaObject被填充了
const QMetaObject *superdata;//这是元数据代表的类的基类的元数据,被填充为基类的元数据指针&QAbstractButton::staticMetaObject
const char *stringdata;//这是元数据的签名标记,被填充为qt_meta_stringdata_QPushButton
const uint *data;//这是元数据的索引数组的指针,被填充为qt_meta_data_QPushButton
const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的,被填充为0
首先应该看qt_meta_data_QPushButton,因为这里是元数据的主要数据,它被填充为一个整数数组,正因为这里只有整数,不能有任何字符串存在,因此才有
qt_meta_stringdata_QPushButton发挥作用的机会,可以说真正的元数据应该是qt_meta_data_QPushButton加上qt_meta_stringdata_QPushButton,那么为什么
不把这两个东西合在一起呢?估计是两者都不是定长的结构,合在一起反而麻烦吧
qt_meta_data_QPushButton实际上是以以下结构开头的
- struct QMetaObjectPrivate
- {
- int revision;
- int className;
- int classInfoCount, classInfoData;
- int methodCount, methodData;
- int propertyCount, propertyData;
- int enumeratorCount, enumeratorData;
- };
-
- static inline const QMetaObjectPrivate *priv(const uint* data)
- { return reinterpret_cast<const QMetaObjectPrivate*>(data); }
这种转换怎么看都有些黑客的味道,这确实是十足的C风格
再结合实际的数据看一看
- static const uint qt_meta_data_QPushButton[] = {
-
-
- 1,
- 0,
- 0, 0,
- 2, 10,
- 3, 20,
- 0, 0,
-
-
- 13, 12, 12, 12, 0x0a,
-
- 24, 12, 12, 12, 0x08,
-
-
-
- 44, 39, 0x01095103,
-
-
- 56, 39, 0x01095103,
-
-
- 64, 39, 0x01095103,
-
-
-
- 0
- };
-
- static const char qt_meta_stringdata_QPushButton[] = {
- "QPushButton/0/0showMenu()/0popupPressed()/0bool/0autoDefault/0default/0"
- "flat/0"
- };
-
- QPushButton
-
- static const uint qt_meta_data_QAbstractButton[] = {
-
-
- 1,
- 0,
- 0, 0,
- 12, 10,
- 9, 70,
- 0, 0,
-
-
- 17, 16, 16, 16, 0x05,
- 27, 16, 16, 16, 0x05,
- 46, 38, 16, 16, 0x05,
- 60, 16, 16, 16, 0x25,
- 70, 38, 16, 16, 0x05,
-
-
- 89, 84, 16, 16, 0x0a,
- 113, 108, 16, 16, 0x0a,
- 131, 16, 16, 16, 0x2a,
- 146, 16, 16, 16, 0x0a,
- 154, 16, 16, 16, 0x0a,
- 163, 16, 16, 16, 0x0a,
- 182, 180, 16, 16, 0x1a,
-
-
- 202, 194, 0x0a095103,
- 213, 207, 0x45095103,
- 224, 218, 0x15095103,
- 246, 233, 0x4c095103,
- 260, 255, 0x01095103,
- 38, 255, 0x01195103,
- 270, 255, 0x01095103,
- 281, 255, 0x01095103,
- 295, 255, 0x01094103,
-
- 0
- };
-
- static const char qt_meta_stringdata_QAbstractButton[] = {
- "QAbstractButton/0/0pressed()/0released()/0checked/0clicked(bool)/0"
- "clicked()/0toggled(bool)/0size/0setIconSize(QSize)/0msec/0"
- "animateClick(int)/0animateClick()/0click()/0toggle()/0setChecked(bool)/0"
- "b/0setOn(bool)/0QString/0text/0QIcon/0icon/0QSize/0iconSize/0"
- "QKeySequence/0shortcut/0bool/0checkable/0autoRepeat/0autoExclusive/0"
- "down/0"
- };
-
- QAbstractButton00pressed()0released()0checked0clicked(bool)0clicked()0toggled(bool)0size0setIconSize(QSize)0ms
基本上都是大同小异的
QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit()));
下面开始看信号和槽连接的源码了
// connect的源码
connect函数是连接信号和槽的桥梁,非常关键
- bool QObject::connect(const QObject *sender, const char *signal,
- const QObject *receiver, const char *method,
- Qt::ConnectionType type)
- {
- #ifndef QT_NO_DEBUG
- bool warnCompat = true;
- #endif
- if (type == Qt::AutoCompatConnection) {
- type = Qt::AutoConnection;
- #ifndef QT_NO_DEBUG
- warnCompat = false;
- #endif
- }
-
-
- if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
- #ifndef QT_NO_DEBUG
- qWarning("Object::connect: Cannot connect %s::%s to %s::%s",
- sender ? sender->metaObject()->className() : "(null)",
- signal ? signal+1 : "(null)",
- receiver ? receiver->metaObject()->className() : "(null)",
- method ? method+1 : "(null)");
- #endif
- return false;
- }
- QByteArray tmp_signal_name;
-
- #ifndef QT_NO_DEBUG
-
- if (!check_signal_macro(sender, signal, "connect", "bind"))
- return false;
- #endif
-
- const QMetaObject *smeta = sender->metaObject();
- ++signal;
-
- int signal_index = smeta->indexOfSignal(signal);
- if (signal_index < 0) {
-
- tmp_signal_name = QMetaObject::normalizedSignature(signal).prepend(*(signal - 1));
- signal = tmp_signal_name.constData() + 1;
- signal_index = smeta->indexOfSignal(signal);
- if (signal_index < 0) {
- #ifndef QT_NO_DEBUG
- err_method_notfound(QSIGNAL_CODE, sender, signal, "connect");
- err_info_about_objects("connect", sender, receiver);
- #endif
- return false;
- }
- }
-
- QByteArray tmp_method_name;
- int membcode = method[0] - ‘0‘;
-
- #ifndef QT_NO_DEBUG
-
- if (!check_method_code(membcode, receiver, method, "connect"))
- return false;
- #endif
- ++method;
-
-
- const QMetaObject *rmeta = receiver->metaObject();
- int method_index = -1;
-
- switch (membcode) {
- case QSLOT_CODE:
-
- method_index = rmeta->indexOfSlot(method);
- break;
- case QSIGNAL_CODE:
-
- method_index = rmeta->indexOfSignal(method);
- break;
- }
- if (method_index < 0) {
-
- tmp_method_name = QMetaObject::normalizedSignature(method);
- method = tmp_method_name.constData();
- switch (membcode) {
- case QSLOT_CODE:
- method_index = rmeta->indexOfSlot(method);
- break;
- case QSIGNAL_CODE:
- method_index = rmeta->indexOfSignal(method);
- break;
- }
- }
-
- if (method_index < 0) {
- #ifndef QT_NO_DEBUG
- err_method_notfound(membcode, receiver, method, "connect");
- err_info_about_objects("connect", sender, receiver);
- #endif
- return false;
- }
- #ifndef QT_NO_DEBUG
-
- if (!QMetaObject::checkConnectArgs(signal, method)) {
- qWarning("Object::connect: Incompatible sender/receiver arguments"
- "/n/t%s::%s --> %s::%s",
- sender->metaObject()->className(), signal,
- receiver->metaObject()->className(), method);
- return false;
- }
- #endif
-
- int *types = 0;
- if (type == Qt::QueuedConnection
- && !(types = ::queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
- return false;
-
- #ifndef QT_NO_DEBUG
- {
-
- QMetaMethod smethod = smeta->method(signal_index);
- QMetaMethod rmethod = rmeta->method(method_index);
- if (warnCompat) {
- if(smethod.attributes() & QMetaMethod::Compatibility) {
- if (!(rmethod.attributes() & QMetaMethod::Compatibility))
- qWarning("Object::connect: Connecting from COMPAT signal (%s::%s).", smeta->className(), signal);
- } else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) {
- qWarning("Object::connect: Connecting from %s::%s to COMPAT slot (%s::%s).",
- smeta->className(), signal, rmeta->className(), method);
- }
- }
- }
- #endif
-
- QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
-
- const_cast<QObject*>(sender)->connectNotify(signal - 1);
- return true;
- }
-
- static bool check_signal_macro(const QObject *sender, const char *signal,
- const char *func, const char *op)
- {
- int sigcode = (int)(*signal) - ‘0‘;
- if (sigcode != QSIGNAL_CODE) {
- if (sigcode == QSLOT_CODE)
- qWarning("Object::%s: Attempt to %s non-signal %s::%s",
- func, op, sender->metaObject()->className(), signal+1);
- else
- qWarning("Object::%s: Use the SIGNAL macro to %s %s::%s",
- func, op, sender->metaObject()->className(), signal);
- return false;
- }
- return true;
- }
-
- int QMetaObject::indexOfSignal(const char *signal) const
- {
- int i = -1;
- const QMetaObject *m = this;
- while (m && i < 0) {
-
- for (i = priv(m->d.data)->methodCount-1; i >= 0; --i)
-
- if ((m->d.data[priv(m->d.data)->methodData + 5*i + 4] & MethodTypeMask) == MethodSignal
- && strcmp(signal, m->d.stringdata
-
- + m->d.data[priv(m->d.data)->methodData + 5*i]) == 0) {
-
- i += m->methodOffset();
- break;
- }
-
- m = m->d.superdata;
- }
- #ifndef QT_NO_DEBUG
-
- if (i >= 0 && m && m->d.superdata) {
- int conflict = m->d.superdata->indexOfMethod(signal);
- if (conflict >= 0)
- qWarning("QMetaObject::indexOfSignal:%s: Conflict with %s::%s",
- m->d.stringdata, m->d.superdata->d.stringdata, signal);
- }
- #endif
- return i;
- }
-
- int QMetaObject::methodOffset() const
- {
- int offset = 0;
- const QMetaObject *m = d.superdata;
- while (m) {
- offset += priv(m->d.data)->methodCount;
- m = m->d.superdata;
- }
- return offset;
- }
-
- QMetaMethod QMetaObject::method(int index) const
- {
- int i = index;
-
- i -= methodOffset();
-
- if (i < 0 && d.superdata)
- return d.superdata->method(index);
-
-
- QMetaMethod result;
- if (i >= 0 && i < priv(d.data)->methodCount) {
-
- result.mobj = this;
-
- result.handle = priv(d.data)->methodData + 5*i;
- }
- return result;
- }
-
- bool QMetaObject::connect(const QObject *sender, int signal_index,
- const QObject *receiver, int method_index, int type, int *types)
- {
-
- QConnectionList *list = ::connectionList();
- if (!list)
- return false;
- QWriteLocker locker(&list->lock);
-
- list->addConnection(const_cast<QObject *>(sender), signal_index,
- const_cast<QObject *>(receiver), method_index, type, types);
- return true;
- }
-
- void QConnectionList::addConnection(QObject *sender, int signal,
- QObject *receiver, int method,
- int type, int *types)
- {
-
- QConnection c = { sender, signal, receiver, method, 0, 0, types };
- c.type = type;
- int at = -1;
-
- for (int i = 0; i < unusedConnections.size(); ++i) {
- if (!connections.at(unusedConnections.at(i)).inUse) {
-
- at = unusedConnections.takeAt(i);
- connections[at] = c;
- break;
- }
- }
- if (at == -1) {
-
- at = connections.size();
-
- connections << c;
- }
-
- sendersHash.insert(sender, at);
- receiversHash.insert(receiver, at);
- }
通过connect函数,我们建立了信号和槽的连接,并且把信号类+信号索引+槽类,槽索引作为记录写到了全局的connect列表中
一旦我们发送了信号,就应该调用相关槽中的方法了,这个过程其实就是查找全局的connect列表的过程,当然还要注意其中要对相关的参数打包和解包
- void Foo::setValue(int v)
- {
- if (v != val)
- {
- val = v;
- emit valueChanged(v);
- }
- }
-
- void Foo::valueChanged(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)
- {
-
- int offset = m->methodOffset();
- activate(sender, offset + local_signal_index, offset + local_signal_index, argv);
- }
-
- void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)
- {
-
- if (sender->d_func()->blockSig)
- return;
-
-
- QConnectionList * const list = ::connectionList();
- if (!list)
- return;
-
- QReadLocker locker(&list->lock);
-
- void *empty_argv[] = { 0 };
- if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
- locker.unlock();
- qt_signal_spy_callback_set.signal_begin_callback(sender, from_signal_index,
- argv ? argv : empty_argv);
- locker.relock();
- }
-
-
- QConnectionList::Hash::const_iterator it = list->sendersHash.find(sender);
- const QConnectionList::Hash::const_iterator end = list->sendersHash.constEnd();
-
- if (it == end) {
- if (qt_signal_spy_callback_set.signal_end_callback != 0) {
- locker.unlock();
- qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
- locker.relock();
- }
- return;
- }
-
- QThread * const currentThread = QThread::currentThread();
- const int currentQThreadId = currentThread ? QThreadData::get(currentThread)->id : -1;
-
-
- QVarLengthArray<int> connections;
- for (; it != end && it.key() == sender; ++it) {
- connections.append(it.value());
-
- list->connections[it.value()].inUse = 1;
- }
-
- for (int i = 0; i < connections.size(); ++i) {
- const int at = connections.constData()[connections.size() - (i + 1)];
- QConnectionList * const list = ::connectionList();
-
- QConnection &c = list->connections[at];
- c.inUse = 0;
- if (!c.receiver || (c.signal < from_signal_index || c.signal > to_signal_index))
- continue;
-
-
-
-
- if ((c.type == Qt::AutoConnection
- && (currentQThreadId != sender->d_func()->thread
- || c.receiver->d_func()->thread != sender->d_func()->thread))
- || (c.type == Qt::QueuedConnection)) {
- ::queued_activate(sender, c, argv);
- continue;
- }
-
-
- const int method = c.method;
- QObject * const previousSender = c.receiver->d_func()->currentSender;
- c.receiver->d_func()->currentSender = sender;
- list->lock.unlock();
-
- if (qt_signal_spy_callback_set.slot_begin_callback != 0)
- qt_signal_spy_callback_set.slot_begin_callback(c.receiver, method, argv ? argv : empty_argv);
-
- #if defined(QT_NO_EXCEPTIONS)
- c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
- #else
- try {
-
- c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
- } catch (...) {
- list->lock.lockForRead();
- if (c.receiver)
- c.receiver->d_func()->currentSender = previousSender;
- throw;
- }
- #endif
-
- if (qt_signal_spy_callback_set.slot_end_callback != 0)
- qt_signal_spy_callback_set.slot_end_callback(c.receiver, method);
-
- list->lock.lockForRead();
- if (c.receiver)
- c.receiver->d_func()->currentSender = previousSender;
- }
-
- if (qt_signal_spy_callback_set.signal_end_callback != 0) {
- locker.unlock();
- qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
- locker.relock();
- }
- }
-
- int Foo::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: valueChanged(*reinterpret_cast< int(*)>(_a[1])); break;
- case 1: setValue(*reinterpret_cast< int(*)>(_a[1])); break;
- }
- _id -= 2;
- }
- return _id;
- }
原文地址:http://blog.csdn.net/oowgsoo/article/details/1529411
为了查看方便,我调整了它的源代码格式。
http://blog.csdn.net/small_qch/article/details/7225347
Qt源码分析之信号和槽机制
标签:
原文地址:http://www.cnblogs.com/findumars/p/4851262.html