| 目录 [+] |
如果只是大略把握,那信号槽机制并不复杂,可以轻松愉快地去了解。
本文没有做深入分析,只是用比较简单直观的方法探究一下,展示的是大概的面貌,因为我不想写一篇很长很吓人的文章,有了大略的把握,再加上一定的方法,以后想深入了解也方便。本文假设读者了解信号槽的写法。
1 Q_OBJECT
The Q_OBJECT macro must appear in the private section of a class definition that declares its own signals and slots or that uses other services provided by Qt's meta-object system.这是宏,在qobjectdefs.h定义。
/* qmake ignore 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: \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
struct QPrivateSignal {};
这里添加的方法是在相应的moc_xyz.cpp实现的。2 signals, slots, emit
这些都是宏,在qobjectdefs.h定义。# if defined(QT_NO_KEYWORDS) # define QT_NO_EMIT # else # ifndef QT_NO_SIGNALS_SLOTS_KEYWORDS # define slots # define signals public # endif # endif # define Q_SLOTS # define Q_SIGNALS public # define Q_PRIVATE_SLOT(d, signature) # define Q_EMIT #ifndef QT_NO_EMIT # define emit #endifemit跟slots都是空,增加代码可读性而已。signals定义为了public,因为信号函数需要在外部访问。
3 SIGNAL, SLOT
SIGNAL跟SLOT没法在源码点开,在Qt5.0.2/5.0.2/Src/qtbase/src/corelib/kernel,"grep SIGNAL *.h"就能找到。这些都是宏,在qobjectdefs.h定义。
Pre[-]
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
#ifndef QT_NO_META_MACROS
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__LINE__)
# ifndef QT_NO_KEYWORDS
# define METHOD(a) qFlagLocation("0"#a QLOCATION)
# endif
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
#else
# ifndef QT_NO_KEYWORDS
# define METHOD(a) "0"#a
# endif
# define SLOT(a) "1"#a
# define SIGNAL(a) "2"#a
#endif // QT_NO_DEBUG
#define QMETHOD_CODE 0 // member type codes
#define QSLOT_CODE 1
#define QSIGNAL_CODE 2
#endif // QT_NO_META_MACROS
qglobal.h
#define QT_STRINGIFY2(x) #x #define QT_STRINGIFY(x) QT_STRINGIFY2(x)
SIGNAL跟SLOT产生字符串,这跟connect函数的定义相符。
4 信号槽函数的连接
4.1 QObject::connect
bool connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection )这是绑定信号槽的方法。
4.2 enum Qt::ConnectionType
This enum describes the types of connection that can be used between signals and slots. In particular, it determines whether a particular signal is delivered to a slot immediately or queued for delivery at a later time.最常用的几个选项:
| Constant | Value | Description |
| Qt::AutoConnection | 0 | (default) If the signal is emitted from a different thread than the receiving object, the signal is queued, behaving as Qt::QueuedConnection. Otherwise, the slot is invoked directly, behaving as Qt::DirectConnection. The type of connection is determined when the signal is emitted. |
| Qt::DirectConnection | 1 | The slot is invoked immediately, when the signal is emitted. |
| Qt::QueuedConnection | 2 | The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread. |
5 槽函数如何被调用
emit一个信号函数之后,槽函数是怎么被调用到的呢?由于槽函数可以在不同的线程被执行,不可能通过直接回调来实现。我打开的是Qt的AddressBook例子,在addresswidget.cpp的AddressWidget::addEntry()方法加了断点,debug。
5.1 mainwindows.cpp # MainWindow::createMenus()
addAct = new QAction(tr("&Add Entry..."), this);
toolMenu->addAction(addAct);
connect(addAct, SIGNAL(triggered()), addressWidget, SLOT(addEntry()));
triggered()是信号函数,addEntry()是槽函数,做了绑定。5.2 调用堆栈
我在菜单点了"Add Entry",触发了断点。先看看堆栈,做个大略了解。Pre[-]
0 AddressWidget::addEntry addresswidget.cpp 64 1 AddressWidget::qt_static_metacall moc_addresswidget.cpp 89 2 QMetaObject::activate(QObject*, int, int, void**) libQt5Core.so.5 3 QAction::triggered(bool) libQt5Widgets.so.5 4 QAction::activate(QAction::ActionEvent) libQt5Widgets.so.5 5 ?? libQt5Widgets.so.5 6 ?? libQt5Widgets.so.5 7 QMenu::mouseReleaseEvent(QMouseEvent*) libQt5Widgets.so.5 8 QWidget::event(QEvent*) libQt5Widgets.so.5 9 QMenu::event(QEvent*) libQt5Widgets.so.5 10 QApplicationPrivate::notify_helper(QObject*, QEvent*) libQt5Widgets.so.5 11 QApplication::notify(QObject*, QEvent*) libQt5Widgets.so.5 12 QCoreApplication::notifyInternal(QObject*, QEvent*) libQt5Core.so.5 13 QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) libQt5Widgets.so.5 14 ?? libQt5Widgets.so.5 15 ?? libQt5Widgets.so.5 16 QApplicationPrivate::notify_helper(QObject*, QEvent*) libQt5Widgets.so.5 17 QApplication::notify(QObject*, QEvent*) libQt5Widgets.so.5 18 QCoreApplication::notifyInternal(QObject*, QEvent*) libQt5Core.so.5 19 QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) libQt5Gui.so.5 20 QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent*) libQt5Gui.so.5 21 QWindowSystemInterface::sendWindowSystemEventsImplementation(QFlags<QEventLoop::ProcessEventsFlag>) libQt5Gui.so.5 22 ?? gcc_64/plugins/platforms/libqxcb.so 23 g_main_context_dispatch /usr/lib64/libglib-2.0.so.0 24 ?? /usr/lib64/libglib-2.0.so.0 25 g_main_context_iteration /usr/lib64/libglib-2.0.so.0 26 QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) libQt5Core.so.5 27 QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) libQt5Core.so.5 28 QCoreApplication::exec() libQt5Core.so.5 29 main main.cpp 51
5.3 addresswidget.h # slots
槽函数声明:
Q_OBJECT
public slots:
void addEntry();
void addEntry(QString name, QString address);
void editEntry();
void removeEntry();
5.4 moc_addresswidget.cpp # qt_static_metacall
moc文件是Qt5.0.2/5.0.2/gcc_64/examples/widgets/itemviews/build-addressbook-Desktop_Qt_5_0_2_GCC_64bit-Debug/moc_addresswidget.cpp,摘录部分源码:Pre[-]
void AddressWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
AddressWidget *_t = static_cast<AddressWidget *>(_o);
switch (_id) {
case 0: _t->selectionChanged((*reinterpret_cast< const QItemSelection(*)>(_a[1]))); break;
case 1: _t->addEntry(); break;
case 2: _t->addEntry((*reinterpret_cast< QString(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2]))); break;
case 3: _t->editEntry(); break;
case 4: _t->removeEntry(); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
void **func = reinterpret_cast<void **>(_a[1]);
{
typedef void (AddressWidget::*_t)(const QItemSelection & );
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&AddressWidget::selectionChanged)) {
*result = 0;
}
}
}
}
addresswidget也有信号函数,原理是跟上面一样的。
5.5 addresswidget.h # signals
信号函数声明:
signals:
void selectionChanged (const QItemSelection &selected);
5.6 moc_addresswidget.cpp # selectionChanged
// SIGNAL 0
void AddressWidget::selectionChanged(const QItemSelection & _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
selectionChanged函数是生成的,同样调用了QMetaObject::activate。Qt::DirectConnection就到此为止了,下面是Qt::QueuedConnection相关的部分。
QMetaObject是个结构体,定义是在include/QtCore/qobjectdefs.h,实现在好几个类,其中activate方法是在qobject.cpp(在Qt5.0.2/5.0.2/Src/qtbase/src/corelib/kernel目录,用grep "::activate(" *.cpp搜索)。
5.7 QMetaObject::activate
这个方法很长,不全部列举了。
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
queued_activate(sender, signal_index, c, argv ? argv : empty_argv);
continue;
}
5.8 queued_activate
static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv)
{
// other code...
QMetaCallEvent *ev = c->isSlotObject ?
new QMetaCallEvent(c->slotObj, sender, signal, nargs, types, args) :
new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs, types, args);
QCoreApplication::postEvent(c->receiver, ev);
}
给receiver发了一个QMetaCallEvent。QCoreApplication::postEvent会把event给receiver所在的线程。线程应该会在事件循环当中调用相应的方法,这部分代码没看。6 深入阅读
* http://qt-project.org/doc/qt-4.8/signalsandslots.html* Qt Internals & Reversing - Author: Daniel Pistelli - www.ntcore.com
No comments:
Post a Comment