QT 源码之QT元对象系统和信号槽机制是本文要介绍的内容。QT的信号和槽机制是用来在对象间通讯的方法,当一个特定事件发生的时候,signal会被 emit 出来,slot 调用是用来响应相应的 signal 的。简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用多了,也复杂多了。
下面的代码是我写的一个继承QLabel的类,是QLabel可以响应鼠标单击的消息。
- view plaincopy to clipboardprint?
- #include <QLabel>
- #include <QWidget>
- #include <QMessageBox>
- #include <QApplication>
- class ClickedLabel : public QLabel
- {
- Q_OBJECT
- signals:
- void Clicked(ClickedLabel* clicked);
- public:
- ClickedLabel(const QString &text,QWidget *parent=0): QLabel(text,parent){ };
- ~ClickedLabel() {};
- protected:
- void mouseReleaseEvent( QMouseEvent* ){emit Clicked(this);};
- public slots:
- void OnCLicked( ClickedLabel* ) {QMessageBox::information(topLevelWidget(), "Message from Qt", "Label Clicked!"); };
- };
- #include "main.moc"
- int main(int argc,char* argv[])
- {
- QApplication app(argc,argv);
- ClickedLabel label("<h2>test</h2>");
- QObject::connect( &label, SIGNAL( Clicked(ClickedLabel*) ),&label, SLOT( OnCLicked(ClickedLabel*) ) ) ;
- label.show();
- return app.exec();
- }
- #include <QLabel>
- #include <QWidget>
- #include <QMessageBox>
- #include <QApplication>
- class ClickedLabel : public QLabel
- {
- Q_OBJECT
- signals:
- void Clicked(ClickedLabel* clicked);
- public:
- ClickedLabel(const QString &text,QWidget *parent=0): QLabel(text,parent){ };
- ~ClickedLabel() {};
- protected:
- void mouseReleaseEvent( QMouseEvent* ){emit Clicked(this);};
- public slots:
- void OnCLicked( ClickedLabel* ) {QMessageBox::information(topLevelWidget(), "Message from Qt", "Label Clicked!"); };
- };
- #include "main.moc"
- int main(int argc,char* argv[])
- {
- QApplication app(argc,argv);
- ClickedLabel label("<h2>test</h2>");
- QObject::connect( &label, SIGNAL( Clicked(ClickedLabel*) ),&label, SLOT( OnCLicked(ClickedLabel*) ) ) ;
- label.show();
- return app.exec();
- }
这段代码很简单,讲述了QT的singal和slot的使用。下面我们就深入QT的源码内部,来看一看QT是如何实现singal和slots的。
#include “main.moc” 的意思就是使编译器找到moc对Q_OBJECT处理后的标准C++文件。编译的时候我们需要首先在该目录中使用 qmake -project 生成一个 .pro 文件,该文件含有工程细节,然后使用 qmake 产生 Makefile,最后 nmake 就可以产生可执行文件了。我们看看在nmake之后除了生成目标代码和可执行文件之外,还有一个main.moc文件,这个文件是moc产生的一个中间文件。
现在我们要看一下Q_OBJECT宏到底是什么?他与main.moc有什么关联呢?相信我介绍完了Q_OBJECT宏之后,再看main.moc就能明白其所有函数的含义了。我们先到objectdefs.h 文件中看一下Q_OBJECT宏的定义:
- #define Q_OBJECT \
- public: \
- Q_OBJECT_CHECK \
- 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:
1首先调用了 Q_OBJECT_CHECK (插入了一个 qt_check_for_QOBJECT_macro 的 template function)
2 然后是全局常量 QMetaObject 对象,因此可以用 QClassname::staticMetaObject 直接访问,另外提供了两个接口函数 metaObject() 用于不同的 class 返回自己的 staticMetaObject、qt_metacast() 用于转换,我们在 moc 产生的文件里面可以找到这两个接口的实现:
- const QMetaObject *ClickedLabel::metaObject() const
- {
- return &staticMetaObject;
- }
- void *ClickedLabel::qt_metacast(const char *_clname)
- {
- if (!_clname) return 0;
- if (!strcmp(_clname, qt_meta_stringdata_ClickedLabel))
- return static_cast<void*>(const_cast< ClickedLabel*>(this));
- return QLabel::qt_metacast(_clname);
- }
- 3 宏QT_TR_FUNCTIONS是和i18n相关的,我们暂时不用去管它。
- # define QT_TR_FUNCTIONS \
- static inline QString tr(const char *s, const char *c = 0) \
- { return staticMetaObject.tr(s, c); }
- 4 最后是接口函数qt_metacall,他的作用是查表,调用函数
- int ClickedLabel::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
- {
- _id = QLabel::qt_metacall(_c, _id, _a);
- if (_id < 0)
- return _id;
- if (_c == QMetaObject::InvokeMetaMethod) {
- switch (_id) {
- case 0: Clicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- case 1: OnCLicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- }
- _id -= 2;
- }
- return _id;
- }
- 我们来仔细看看 QMetaObject,这就是meta-object的数据结构定义
- struct Q_CORE_EXPORT QMetaObject
- {
- const char *className() const;
- const QMetaObject *superClass() const;
- QObject *cast(QObject *obj) const;
- // ...
- struct { // private data
- const QMetaObject *superdata;
- const char *stringdata;
- const uint *data;
- const void *extradata;
- } d;
- } ;
#p#
下面看看我们生成的具体的代码:
- static const uint qt_meta_data_ClickedLabel[] = {
- // content:
- 1, // revision
- 0, // classname
- 0, 0, // classinfo
- 2, 10, // methods
- 0, 0, // properties
- 0, 0, // enums/sets
- // signals: signature, parameters, type, tag, flags
- 22, 14, 13, 13, 0x05,
- // slots: signature, parameters, type, tag, flags
- 45, 13, 13, 13, 0x0a,
- 0 // eod
- };
- static const char qt_meta_stringdata_ClickedLabel[] = {
- "ClickedLabel\0\0clicked\0Clicked(ClickedLabel*)\0"
- "OnCLicked(ClickedLabel*)\0"
- };
- const QMetaObject ClickedLabel::staticMetaObject = {
- { &QLabel::staticMetaObject, qt_meta_stringdata_ClickedLabel,
- qt_meta_data_ClickedLabel, 0 }
- };
这就是meta-object的初始化代码,meta-object包含所有继承QObject类的元对象信息。包括class name, superclass name, properties, signals and slots等等。
ClickedLabel的staticMetaObject初始化用到了QLabel::staticMetaObject,
qt_meta_stringdata_ClickedLabel是元数据的签名
qt_meta_data_ClickedLabel,是元数据的索引数组指针。
qt_meta_data_ClickedLabel中这些莫名其妙的数字是如何变成QMetaObject的呢?
在qmetaobject.cpp中我们找到了QMetaObjectPrivate的定义:
- struct QMetaObjectPrivate
- {
- int revision;
- int className;
- int classInfoCount, classInfoData;
- int methodCount, methodData;
- int propertyCount, propertyData;
- int enumeratorCount, enumeratorData;
- };
很明显,利用qt_meta_data_ClickedLabel中存储的索引和qt_meta_stringdata_ClickedLabel中存储的值,我们很容易将QMetaObject构建起来。这中间的转换是通过
- static inline const QMetaObjectPrivate *priv(const uint* data)
- { return reinterpret_cast<const QMetaObjectPrivate*>(data); }
这个函数来完成的。
#p#
下面我们着重看看几个与 signal/slot 相关的代码
qobject.cpp 文件中关于 QObject::connect() 函数的代码,
- bool QObject::connect(const QObject *sender, const char *signal,
- const QObject *receiver, const char *method,
- Qt::ConnectionType type)
- {
- {
- const void *cbdata[] = { sender, signal, receiver, method, &type };
- if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
- return true;
- }
- #ifndef QT_NO_DEBUG
- bool warnCompat = true;
- #endif
- if (type == Qt::AutoCompatConnection) {
- type = Qt::AutoConnection;
- #ifndef QT_NO_DEBUG
- warnCompat = false;
- #endif
- }
- //判断是否是NULL
- if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
- qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
- sender ? sender->metaObject()->className() : "(null)",
- (signal && *signal) ? signal+1 : "(null)",
- receiver ? receiver->metaObject()->className() : "(null)",
- (method && *method) ? method+1 : "(null)");
- return false;
- }
- QByteArray tmp_signal_name;
- if (!check_signal_macro(sender, signal, "connect", "bind"))
- return false;
- const QMetaObject *smeta = sender->metaObject();
- ++signal; //skip code
- int signal_index = smeta->indexOfSignal(signal);
- if (signal_index < 0) {
- // check for normalized signatures
- tmp_signal_name = QMetaObject::normalizedSignature(signal).prepend(*(signal - 1));
- signal = tmp_signal_name.constData() + 1;
- signal_index = smeta->indexOfSignal(signal);
- if (signal_index < 0) {
- err_method_notfound(QSIGNAL_CODE, sender, signal, "connect");
- err_info_about_objects("connect", sender, receiver);
- return false;
- }
- }
- QByteArray tmp_method_name;
- int membcode = method[0] - '0';
- if (!check_method_code(membcode, receiver, method, "connect"))
- return false;
- ++method; // skip code
- 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) {
- // check for normalized methods
- 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) {
- err_method_notfound(membcode, receiver, method, "connect");
- err_info_about_objects("connect", sender, receiver);
- return false;
- }
- if (!QMetaObject::checkConnectArgs(signal, method)) {
- qWarning("QObject::connect: Incompatible sender/receiver arguments"
- "\n\t%s::%s --> %s::%s",
- sender->metaObject()->className(), signal,
- receiver->metaObject()->className(), method);
- return false;
- }
- int *types = 0;
- if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
- && !(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("QObject::connect: Connecting from COMPAT signal (%s::%s)", smeta->className(), signal);
- } else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) {
- qWarning("QObject::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;
- }
上面这段代码首先调用了 QInternal 这个 namespace 里面 activateCallbacks 这个函数,然后根据 QMetaObject 信息检查了 sender、receiver 以及对应 signal/slots 的匹配性,得到元数据类。把 signal/slot 字符串转换成为了对应的 index,然后检查信号和槽的参数是否一致,槽函数的参数可以小于信号函数的参数。
最后得到method的元数据QMetaMethod,然后调用QMetaObject::connect的方法。
- 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;
- }
QMetaObject::connect代码中QWriteLocker是为了防止多线程操作引起问题。
一旦我们发送了信号,就应该调用相关槽中的方法了,这个过程其实就是查找全局的connect列表的过程。真正发出信号是在main.moc中。
- void ClickedLabel::Clicked(ClickedLabel * _t1)
- {
- void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
- QMetaObject::activate(this, &staticMetaObject, 0, _a);
- }
- void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)
- {
- // 这里得到的是QObject的数据,首先判断是否为阻塞设置
- 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();
- }
- // 在sender的哈希表中得到sender的连接
- 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;
- // 记录sender连接的索引
- 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;
- // 判断是否放到队列中
- // determine if this connection should be sent immediately or
- // put into the event queue
- 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;
- }
- // 为receiver设置当前发送者
- 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 {
- // 调用receiver的方法
- 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();
- }
- }
响应信号也是在main.moc中实现的。
- int ClickedLabel::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
- {
- _id = QLabel::qt_metacall(_c, _id, _a);
- if (_id < 0)
- return _id;
- if (_c == QMetaObject::InvokeMetaMethod) {
- switch (_id) {
- case 0: Clicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- case 1: OnCLicked((*reinterpret_cast< ClickedLabel*(*)>(_a[1]))); break;
- }
- _id -= 2;
- }
- return _id;
- }
小结: QT 源码之QT元对象系统和信号槽机制的内容介绍完了,希望本文对你有所帮助!