概述
在讨论Qt的事件之前,先叙述两个知识点。
1. 典型的win32程序如下所示:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
...
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM, LPARAM)
{
switch(message)
{
case WM_DESTORY:
...
}
}
因为windows消息机制不是本文的重点,因此,暂不详细讨论windows的消息机制。请阅读相关资料。
2.一个典型的QtGUI应用程序,如下代码所示:
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QWidget widget;
widget.show();
return app.exec();
}
代码中,实例化了一个QApplication对象,Qt应用程序要求,每个程序必须先构造一个QApplication对象,通过这个对象来让程序跑起来。也就是实例化一个QApplication对象后,就为App建立了一个消息循环。并且,此段代码还实例化了一个QWidget对象,通过show方法,让widget显示出来。
Qt的事件分为两种:
- 与用户交互时发生。比如移动鼠标(enterEvent),敲击键盘(keyPressEvent)等。
- Qt程序自己发生,比如计时器事件(timerEvent)等。
QApplication的消息循环
以上面的QtGUI例程为例子,从起点开始分析,Qt的消息循环是如何建立起来的。
QApplication的exec的实现如下:
int QApplication::exec()
{
int r = QGuiApplication::exec();
QApplication *app = qobject_cast<QApplication*>(QCoreApplication::instance());
if (app && app->hasDeviceLost()) // TDR has priority
return 0xdeeedeee;
return r;
}
可以看到,QApplication的exec内部实现是调用基类的exec。深入内部查看源码发现,最终调用了基类QCoreApplication::exec。
int QCoreApplication::exec()
{
...
threadData->quitNow = false;
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
int returnCode = eventLoop.exec();
threadData->quitNow = false;
if (self)
self->d_func()->execCleanup();
return returnCode;
}
QCoreApplication::exec的内部创建了一个QEventLoop,并调用了QEventLoop的exec,正是这个函数开启了消息循环。继续分析QEventLoop::exec的实现。
int QEventLoop::exec(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
...
// remove posted quit events when entering a new event loop
QCoreApplication *app = QCoreApplication::instance();
if (app && app->thread() == thread())
QCoreApplication::removePostedEvents(app, QEvent::Quit);
...
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false;
return d->returnCode.load();
}
这个方法的主体是 while 循环,在 exit之前,会一直循环调用 processEvents() 方法。看来重点是processEvents,processEvents的实现如下:
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher())
return false;
return d->threadData->eventDispatcher.load()->processEvents(flags);
}
processEvents方法转调了eventDispatcher的processEvents。eventDispatcher的类型是QAbstractEventDispatcher,这是个抽象类,因此,很容易想到,这里使用了多态的方式。
的确,在不同的平台上,eventDispatcher的实现类是不一样的,在Windows上的实现是QWindowsGuiEventDispatcher,本篇主要讨论Windows平台。其他的平台暂不讨论。QWindowsGuiEventDispatcher内部又转调QEventDispatcherWin32::processEvents,下面代码是QEventDispatcherWin32::processEvents的实现。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);
if (!d->internalHwnd) {
createInternalHwnd();
wakeUp(); // trigger a call to sendPostedEvents()
}
d->interrupt.store(false);
emit awake();
bool canWait;
bool retVal = false;
bool seenWM_QT_SENDPOSTEDEVENTS = false;
bool needWM_QT_SENDPOSTEDEVENTS = false;
do {
DWORD waitRet = 0;
DWORD nCount = 0;
HANDLE *pHandles = nullptr;
if (d->winEventNotifierActivatedEvent) {
nCount = 1;
pHandles = &d->winEventNotifierActivatedEvent;
}
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt.load()) {
MSG msg;
bool haveMessage;
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
// process queued user input events
haveMessage = true;
msg = d->queuedUserInputEvents.takeFirst();
} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
// process queued socket events
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst();
} else {
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage) {
if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
&& isUserInputMessage(msg.message)) {
// queue user input events for later processing
d->queuedUserInputEvents.append(msg);
continue;
}
if ((flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
// queue socket events for later processing
d->queuedSocketEvents.append(msg);
continue;
}
}
}
if (!haveMessage) {
// no message - check for signalled objects
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) {
// a new message has arrived, process it
continue;
}
}
if (haveMessage) {
// WinCE doesn't support hooks at all, so we have to call this by hand :(
if (!d->getMessageHook)
(void) qt_GetMessageHook(0, PM_REMOVE, reinterpret_cast<LPARAM>(&msg));
if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
if (seenWM_QT_SENDPOSTEDEVENTS) {
// when calling processEvents() "manually", we only want to send posted
// events once
needWM_QT_SENDPOSTEDEVENTS = true;
continue;
}
seenWM_QT_SENDPOSTEDEVENTS = true;
} else if (msg.message == WM_TIMER) {
// avoid live-lock by keeping track of the timers we've already sent
bool found = false;
for (int i = 0; !found && i < processedTimers.count(); ++i) {
const MSG processed = processedTimers.constData()[i];
found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam = msg.lParam);
}
if (found)
continue;
processedTimers.append(msg);
} else if (msg.message == WM_QUIT) {
if (QCoreApplication::instance())
QCoreApplication::instance()->quit();
return false;
}
if (!filterNativeEvent(false, QAbstractNativeEventFilter::WinProcessMsg, ByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} else if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();
} else {
// nothing todo so break
break;
}
retVal = true;
}
// still nothing - wait for message or signalled objects
canWait = (!retVal
&& !d->interrupt.load()
&& (flags & QEventLoop::WaitForMoreEvents));
if (canWait) {
emit aboutToBlock();
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
emit awake();
if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();
retVal = true;
}
}
} while (canWait);
if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == 0) {
// when called "manually", always send posted events
sendPostedEvents();
}
if (needWM_QT_SENDPOSTEDEVENTS)
PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
return retVal;
}
QEventDispatcherWin32::processEvents的内部实现也是个while循环。分析这段代码,概括地讲大体做了以下几件事:
- 初始化一个不可见窗体(如果没有初始化过的话);
- 如果用户输入事件或socket 事件队列不空,就取一个事件出来;
- 如果 2 中没有获取到消息,则调用PeekMessage从系统中取一个消息(这个函数是非阻塞的);
- 如果3中的PeekMessage也没取到消息,证明app没有需要处理的消息。就调用MsgWaitForMultipleObjectsEx阻塞,直到有新的消息到达。
- 如果3中获取到了消息或MsgWaitForMultipleObjectsEx阻塞时得到了消息,先依次对Posted Event 、 Timer Event、 Quit Event进行预处理。然后,使用 windowsAPI TranslateMessage、DispatchMessage 分发消息;
- 对于Posted Event,设置needWM_QT_SENDPOSTEDEVENTS 为true后,continue循环,并在循环外面,调用PostMessage添加WM_QT_SENDPOSTEDEVENTS消息到消息队列中。
可以看到,QEventDispatcherWin32::processEvents的实现原理是使用了标准的 Windows32 消息机制(macOS 上使用 CFRunLoop,UNIX 上则是 epoll)。
下面讨论为什么需要在第1步中创建一个不可见的窗体。先看创建的过程。
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{
QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();
if (!ctx->atom)
return 0;
HWND wnd = CreateWindow(ctx->className, // classname
ctx->className, // window name
0, // style
0, 0, 0, 0, // geometry
0/*HWND_MESSAGE*/, // parent
0, // menu handle
GetModuleHandle(0), // application
0); // windows creation data.
if (!wnd) {
qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed");
return 0;
}
#ifdef GWLP_USERDATA
SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(eventDispatcher));
#else
SetWindowLong(wnd, GWL_USERDATA, reinterpret_cast<LONG>(eventDispatcher));
#endif
return wnd;
}
其中qWindowsMessageWindowClassContext的代码如下。
QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext()
: atom(0), className(0)
{
// make sure that multiple Qt's can coexist in the same process
const QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget")
+ QString::number(quintptr(qt_internal_proc));
className = new wchar_t[qClassName.size() + 1];
qClassName.toWCharArray(className);
className[qClassName.size()] = 0;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = qt_internal_proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(0);
wc.hIcon = 0;
wc.hCursor = 0;
wc.hbrBackground = 0;
wc.lpszMenuName = NULL;
wc.lpszClassName = className;
atom = RegisterClass(&wc);
if (!atom) {
qErrnoWarning("%s RegisterClass() failed", qPrintable(qClassName));
delete [] className;
className = 0;
}
}
这是一个Win32窗口创建过程。在 Windows 中,没有像 macOS 的 CFRunLoop 那样比较通用的消息循环,但是,当应用程序一旦创建了一个窗体后,系统就在应用与操作系统之间建立了一个桥梁,应用程序通过这个窗体就可以充分利用 Windows 的消息机制了。同时 Windows API 也允许你绑定一些自定义指针,这样每个窗体都与 event loop 建立了关系。上述代码中,绑定的回调函数是qt_internal_proc(也就是调用DispatchMessage 时,对于属于这个窗口的消息,会回调的这个函数进行处理)。
先对上述过程总结一下:Qt创建QApplication的时候,通过创建一个隐藏窗口,引入了windows的消息机制。在进入QApplication的event loop,通过经过层层调用,最终通过与平台相关的AbstractEventDispatcher的子类QEventDispatcherWin32,将app队列中的消息取出来查看类型,对于特定的message,通过标准的Windows API(PostMessage)传递给Windows OS。Windows OS得到通知后回调qt_internal_proc,由qt_internal_proc来处理。
调用栈:
1 main(int argc, char** argv)
2 QApplication::exec()
3 QCoreApplication::exec()
4 QEventLoop::exec(ProcessEventsFlags )
5 QEventLoop::processEvents(ProcessEventsFlags )
6 QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)
接着,分析不可见窗口的窗口处理过程qt_internal_proc,其实现为:
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
if (message == WM_NCCREATE)
return true;
MSG msg;
msg.hwnd = hwnd;
msg.message = message;
msg.wParam = wp;
msg.lParam = lp;
QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance();
long result;
if (!dispatcher) {
if (message == WM_TIMER)
KillTimer(hwnd, wp);
return 0;
}
if (dispatcher->filterNativeEvent(QByteArrayLiteral("windows_dispatcher_MSG"), &msg, &result))
return result;
#ifdef GWLP_USERDATA
auto q = reinterpret_cast<QEventDispatcherWin32 *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
#else
auto q = reinterpret_cast<QEventDispatcherWin32 *>(GetWindowLong(hwnd, GWL_USERDATA));
#endif
QEventDispatcherWin32Private *d = 0;
if (q != 0)
d = q->d_func();
switch (message) {
case WM_QT_SOCKETNOTIFIER: {
// socket notifier message
int type = -1;
switch (WSAGETSELECTEVENT(lp)) {
case FD_READ:
case FD_ACCEPT:
type = 0;
break;
case FD_WRITE:
case FD_CONNECT:
type = 1;
break;
case FD_OOB:
type = 2;
break;
case FD_CLOSE:
type = 3;
break;
}
if (type >= 0) {
Q_ASSERT(d != 0);
QSNDict *sn_vec[4] = { &d->sn_read, &d->sn_write, &d->sn_except, &d->sn_read };
QSNDict *dict = sn_vec[type];
QSockNot *sn = dict ? dict->value(wp) : 0;
if (sn == nullptr) {
d->postActivateSocketNotifiers();
} else {
Q_ASSERT(d->active_fd.contains(sn->fd));
QSockFd &sd = d->active_fd[sn->fd];
if (sd.selected) {
Q_ASSERT(sd.mask == 0);
d->doWsaAsyncSelect(sn->fd, 0);
sd.selected = false;
}
d->postActivateSocketNotifiers();
// Ignore the message if a notification with the same type was
// received previously. Suppressed message is definitely spurious.
const long eventCode = WSAGETSELECTEVENT(lp);
if ((sd.mask & eventCode) != eventCode) {
sd.mask |= eventCode;
QEvent event(type < 3 ? QEvent::SockAct : QEvent::SockClose);
QCoreApplication::sendEvent(sn->obj, &event);
}
}
}
return 0;
}
case WM_QT_ACTIVATENOTIFIERS: {
Q_ASSERT(d != 0);
// Postpone activation if we have unhandled socket notifier messages
// in the queue. WM_QT_ACTIVATENOTIFIERS will be posted again as a result of
// event processing.
MSG msg;
if (!PeekMessage(&msg, d->internalHwnd,
WM_QT_SOCKETNOTIFIER, WM_QT_SOCKETNOTIFIER, PM_NOREMOVE)
&& d->queuedSocketEvents.isEmpty()) {
// register all socket notifiers
for (QSFDict::iterator it = d->active_fd.begin(), end = d->active_fd.end();
it != end; ++it) {
QSockFd &sd = it.value();
if (!sd.selected) {
d->doWsaAsyncSelect(it.key(), sd.event);
// allow any event to be accepted
sd.mask = 0;
sd.selected = true;
}
}
}
d->activateNotifiersPosted = false;
return 0;
}
case WM_TIMER:
if (d->sendPostedEventsWindowsTimerId == 0
|| wp != uint(d->sendPostedEventsWindowsTimerId)) {
Q_ASSERT(d != 0);
d->sendTimerEvent(wp);
return 0;
}
// we also use a Windows timer to send posted events when the message queue is full
Q_FALLTHROUGH();
case WM_QT_SENDPOSTEDEVENTS: {
const int localSerialNumber = d->serialNumber.load();
if (localSerialNumber != d->lastSerialNumber) {
d->lastSerialNumber = localSerialNumber;
q->sendPostedEvents();
}
return 0;
}
} // switch (message)
return DefWindowProc(hwnd, message, wp, lp);
}
qt_internal_proc函数分别处理 Socket、Notifier、Posted Event 和 Timer等Qt内部发生的事件。Posted Event是一个比较常见和重要的事件类型,本文主要分析这个event的传递和调用。Posted Event 的类型为WM_QT_SENDPOSTEDEVENTS,调用的是QWindowsGuiEventDispatcher::sendPostedEvents,QWindowsGuiEventDispatcher::sendPostedEvents的实现如下:
void QWindowsGuiEventDispatcher::sendPostedEvents()
{
QEventDispatcherWin32::sendPostedEvents();
QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}
QWindowsGuiEventDispatcher::sendPostedEvents先调用了Dispatcher的sendPostedEvents,然后又调用了SystemInterface的sendWindowSystemEvents函数。前者是处理Qt内部自己发生的事件,例如调用postEvent放到队列中的事件、QObject::startTimer()会触发QTimerEvent、用户自定义事件等。后者是处理用户界面交互时发生的,只有Qt的GUI程序,才需要窗口消息,因此,只有当程序是Qt GUI程序时,后者的调用才有效。先讨论前者,后面再讨论后者。
QEventDispatcherWin32::sendPostedEvents的实现
void QEventDispatcherWin32::sendPostedEvents()
{
Q_D(QEventDispatcherWin32);
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
}
进一步看QEventDispatcherWin32::sendPostedEvents的实现, 由于sendPostedEvents太复杂,本文将其简化一下,其简化的实现如下:
void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type, QThreadData *data)
{
...
while (i < data->postEventList.size()) {
// avoid live-lock
if (i >= data->postEventList.insertionOffset)
break;
const QPostEvent &pe = data->postEventList.at(i);
++i;
...
// after all that work, it's time to deliver the event.
QCoreApplication::sendEvent(r, e);
}
}
在 sendPostedEvents() 方法会循环取出已入队的事件,这些事件被封装入 QPostEvent。并再次传入 QCoreApplication::sendEvent() 方法,在此之后的调用过程就与操作系统无关了。
一般来说,signals/slots 在同一线程下会直接调用 QCoreApplication::sendEvent() 传递消息,这样事件就能直接得到处理,不必等待下一次 event loop。而处于不同线程中的对象在 emit signals 之后,会通过 QCoreApplication::postEvent() 来发送消息.
postEvent和sendEvent
QCoreApplication::sendEvent()的实现后面再讨论,先看看QCoreApplication::postEvent() 的实现。
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());
if (receiver == 0) {
qWarning("QCoreApplication::postEvent: Unexpected null receiver");
delete event;
return;
}
QThreadData * volatile * pdata = &receiver->d_func()->threadData;
QThreadData *data = *pdata;
if (!data) {
// posting during destruction? just delete the event to prevent a leak
delete event;
return;
}
// lock the post event mutex
data->postEventList.mutex.lock();
// if object has moved to another thread, follow it
while (data != *pdata) {
data->postEventList.mutex.unlock();
data = *pdata;
if (!data) {
// posting during destruction? just delete the event to prevent a leak
delete event;
return;
}
data->postEventList.mutex.lock();
}
QMutexUnlocker locker(&data->postEventList.mutex);
// if this is one of the compressible events, do compression
if (receiver->d_func()->postedEvents
&& self && self->compressEvent(event, receiver, &data->postEventList)) {
Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
return;
}
if (event->type() == QEvent::DeferredDelete)
receiver->d_ptr->deleteLaterCalled = true;
if (event->type() == QEvent::DeferredDelete && data == QThreadData::current())
{
// remember the current running eventloop for DeferredDelete
// events posted in the receiver's thread.
// Events sent by non-Qt event handlers (such as glib) may not
// have the scopeLevel set correctly. The scope level makes sure that
// code like this:
// foo->deleteLater();
// qApp->processEvents(); // without passing QEvent::DeferredDelete
// will not cause "foo" to be deleted before returning to the event loop.
// If the scope level is 0 while loopLevel != 0, we are called from a
// non-conformant code path, and our best guess is that the scope level
// should be 1. (Loop level 0 is special: it means that no event loops
// are running.)
int loopLevel = data->loopLevel;
int scopeLevel = data->scopeLevel;
if (scopeLevel == 0 && loopLevel != 0)
scopeLevel = 1;
static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
}
// delete the event on exceptions to protect against memory leaks till the event is
// properly owned in the postEventList
QScopedPointer<QEvent> eventDeleter(event);
Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
data->postEventList.addEvent(QPostEvent(receiver, event, priority));
eventDeleter.take();
event->posted = true;
++receiver->d_func()->postedEvents;
data->canWait = false;
locker.unlock();
QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
if (dispatcher)
dispatcher->wakeUp();
}
可以看到内部实现,先将消息加入到postEventList,然后调用dispatcher的wakeUp方法,唤醒正在被阻塞的 MsgWaitForMultipleObjectsEx 函数。
void QEventDispatcherWin32::wakeUp()
{
Q_D(QEventDispatcherWin32);
d->serialNumber.ref();
if (d->internalHwnd && d->wakeUps.testAndSetAcquire(0, 1)) {
// post a WM_QT_SENDPOSTEDEVENTS to this thread if there isn't one already pending
PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
}
}
唤醒的方法就是往这个线程所对应的窗体Post消息。Post消息后,系统会将此消息放在消息队列中,在下一次窗口的消息循环中,再处理这个消息,因此,postEvent是异步的。注意到PostMessage的这个消息类型为WM_QT_SENDPOSTEDEVENTS,在下一次的消息循环中就又回调用到QEventDispatcherWin32::sendPostedEvents。而在上面已经分析过了,:sendPostedEvents内部会调用QCoreApplication::sendEvent()来处理消息。
因此,通过QCoreApplication::postEvent的event,最终还是被QCoreApplication::sendEvent处理的。只不过,QCoreApplication::postEvent的过程曲折一些,中间经过了PostMessage这个系统API,在下一次消息循环时被处理,这就是postEvent异步调用的实现方式。
通过上面的讨论知道,Qt内部自己发生的事件,最后都会调用QCoreApplication::sendEvent来处理,下面就对QCoreApplication::sendEvent进行重点分析。sendEvent的调用不再和系统有关,不会用系统api的出现。
bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{
Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());
if (event)
event->spont = false;
return notifyInternal2(receiver, event);
}
内部是调用notifyInternal2实现的,notifyInternal2的实现如下:
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
if (!self && selfRequired)
return false;
// Make it possible for Qt Script to hook into events even
// though QApplication is subclassed...
bool result = false;
void *cbdata[] = { receiver, event, &result };
if (QInternal::activateCallbacks(QInternal::EventNotifyCallback, cbdata)) {
return result;
}
// Qt enforces the rule that events can only be sent to objects in
// the current thread, so receiver->d_func()->threadData is
// equivalent to QThreadData::current(), just without the function
// call overhead.
QObjectPrivate *d = receiver->d_func();
QThreadData *threadData = d->threadData;
QScopedScopeLevelCounter scopeLevelCounter(threadData);
if (!selfRequired)
return doNotify(receiver, event);
return self ? self->notify(receiver, event) : false;
}
重点在最后一行代码
self->notify(receiver, event)
这个self,就是QApplication对象实例了。也就是调用QApplication::notify,QApplication::notify的代码实现太长,这里就不展示了。其主要流程是,根据event的type,switch到不同的分支进行一些处理。并最终调用QApplicationPrivate::notify_helper。另外,QApplication::notify是个虚函数,可以被继承自QApplication的子类重写。
QApplicationPrivate::notify_helper的实现如下:
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{
// These tracepoints (and the whole function, actually) are very similar
// to the ones in QCoreApplicationPrivate::notify_helper; the reason for their
// duplication is because tracepoint symbols are not exported by QtCore.
// If you adjust the tracepoints here, consider adjusting QCoreApplicationPrivate too.
Q_TRACE(QApplication_notify_entry, receiver, e, e->type());
bool consumed = false;
bool filtered = false;
Q_TRACE_EXIT(QApplication_notify_exit, consumed, filtered);
// send to all application event filters
if (threadRequiresCoreApplication()
&& receiver->d_func()->threadData->thread == mainThread()
&& sendThroughApplicationEventFilters(receiver, e)) {
filtered = true;
return filtered;
}
if (receiver->isWidgetType()) {
QWidget *widget = static_cast<QWidget *>(receiver);
#if !defined(QT_NO_CURSOR)
// toggle HasMouse widget state on enter and leave
if ((e->type() == QEvent::Enter || e->type() == QEvent::DragEnter) &&
(!QApplication::activePopupWidget() || QApplication::activePopupWidget() == widget->window()))
widget->setAttribute(Qt::WA_UnderMouse, true);
else if (e->type() == QEvent::Leave || e->type() == QEvent::DragLeave)
widget->setAttribute(Qt::WA_UnderMouse, false);
#endif
if (QLayout *layout=widget->d_func()->layout) {
layout->widgetEvent(e);
}
}
// send to all receiver event filters
if (sendThroughObjectEventFilters(receiver, e)) {
filtered = true;
return filtered;
}
// deliver the event
consumed = receiver->event(e);
QCoreApplicationPrivate::setEventSpontaneous(e, false);
return consumed;
}
这函数里面,有几个重点的代码,分别是
- sendThroughApplicationEventFilters
- sendThroughObjectEventFilters
- receiver->event(e)
sendThroughApplicationEventFilters实现
sendThroughApplicationEventFilters函数的代码如下:
bool QCoreApplicationPrivate::sendThroughApplicationEventFilters(QObject *receiver, QEvent *event)
{
// We can't access the application event filters outside of the main thread (race conditions)
Q_ASSERT(receiver->d_func()->threadData->thread == mainThread());
if (extraData) {
// application event filters are only called for objects in the GUI thread
for (int i = 0; i < extraData->eventFilters.size(); ++i) {
QObject *obj = extraData->eventFilters.at(i);
if (!obj)
continue;
if (obj->d_func()->threadData != threadData) {
qWarning("QCoreApplication: Application event filter cannot be in a different thread.");
continue;
}
if (obj->eventFilter(receiver, event))
return true;
}
}
return false;
}
从QCoreApplicationPrivate内部的eventfilters列表中取出对象,依次调用对象的eventFilter函数。eventfilters列表中的对象是从哪里来的呢?正是通过调用QCoreApplication::instance()->installEventFilter(QObject*obj)函数,将obj添加到列表中的。
sendThroughObjectEventFilters实现
sendThroughObjectEventFilters的代码:
bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event)
{
if (receiver != QCoreApplication::instance() && receiver->d_func()->extraData) {
for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) {
QObject *obj = receiver->d_func()->extraData->eventFilters.at(i);
if (!obj)
continue;
if (obj->d_func()->threadData != receiver->d_func()->threadData) {
qWarning("QCoreApplication: Object event filter cannot be in a different thread.");
continue;
}
if (obj->eventFilter(receiver, event))
return true;
}
}
return false;
}
可以看到,sendThroughObjectEventFilters内部实现与sendThroughApplicationEventFilters很相似,只不过,sendThroughObjectEventFilters是从receiver内部的eventfilters列表中取出对象,并依次调用对象的eventFilter函数。同理,这些列表中的对象是通过调用receiver obj的installEventFilter(QObject\*obj)函数,将obj添加到obj自身的列表中的。
event实现
最后会调用 receiver->event(e)处理event,receiver是指接受event的对象,event是虚函数,接受的对象如果重写event函数,那么就会调用到对应对象的event函数中;如果不重写,就是调用基类QObject::event。在本例中,receiver是QWidget对象,QWidget对event重写了,下面是QWidget::event的代码。(省略了大部分代码)
bool QWidget::event(QEvent *event)
{
Q_D(QWidget);
...
switch (event->type()) {
...
case QEvent::Enter:
enterEvent(event);
break;
case QEvent::Leave:
leaveEvent(event);
break;
case QEvent::HoverEnter:
case QEvent::HoverLeave:
update();
break;
case QEvent::Paint:
// At this point the event has to be delivered, regardless
// whether the widget isVisible() or not because it
// already went through the filters
paintEvent((QPaintEvent*)event);
break;
...
default:
return QObject::event(event);
}
return true;
}
可以看到,其内部实现是根据event的type(类型),跳转到不同的分支,会调用对应的event处理函数。例如:QEvent::Enter(当鼠标移动到Widget内部时,就触发此事件),就调用enterEvent函数。enterEvent也是虚函数,可以被子类重写,以实现特定的逻辑。
QWindowSystemInterface::sendWindowSystemEvents的实现。
前面已经提到,只有程序是Qt GUI程序时,sendWindowSystemEvents的调用才是有效的。
先看sendWindowSystemEvents的内部实现。
bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{
int nevents = 0;
while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {
QWindowSystemInterfacePrivate::WindowSystemEvent *event = nullptr;
if (QWindowSystemInterfacePrivate::platformFiltersEvents) {
event = QWindowSystemInterfacePrivate::getWindowSystemEvent();
} else {
event = flags & QEventLoop::ExcludeUserInputEvents ?
QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :
QWindowSystemInterfacePrivate::getWindowSystemEvent();
}
if (!event)
break;
if (QWindowSystemInterfacePrivate::eventHandler) {
if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event))
nevents++;
} else {
nevents++;
QGuiApplicationPrivate::processWindowSystemEvent(event);
}
// Record the accepted state for the processed event
// (excluding flush events). This state can then be
// returned by flushWindowSystemEvents().
if (event->type != QWindowSystemInterfacePrivate::FlushEvents)
QWindowSystemInterfacePrivate::eventAccepted.store(event->eventAccepted);
delete event;
}
return (nevents > 0);
}
内部会不停的调用windowSystemEventsQueued来查看系统的消息队列是否为空,若不为空,就从队列中取一个event出来,并调用QGuiApplicationPrivate::processWindowSystemEvent处理这个event(QWindowSystemInterfacePrivate::eventHandler->sendEvent的过程暂不讨论),然后删除event。
QGuiApplicationPrivate::processWindowSystemEvent的实现如下:
void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{
Q_TRACE_SCOPE(QGuiApplicationPrivate_processWindowSystemEvent, e->type);
...
switch(e->type) {
case QWindowSystemInterfacePrivate::Mouse:
QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));
break;
case QWindowSystemInterfacePrivate::Wheel:
QGuiApplicationPrivate::processWheelEvent(static_cast<QWindowSystemInterfacePrivate::WheelEvent *>(e));
break;
...
default:
qWarning() << "Unknown user input event type:" << e->type;
break;
}
}
类似的逻辑 -- 根据event的类型不同,调用不同的处理过程。但基本所有的处理过程,都会调用到 QGuiApplication::sendSpontaneousEvent。sendSpontaneousEvent的实现如下:
bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{
Q_TRACE(QCoreApplication_sendSpontaneousEvent, receiver, event, event->type());
if (event)
event->spont = true;
return notifyInternal2(receiver, event);
}
似曾相识,前面的QCoreApplication::sendEvent的实现和这里的实现是完全一样的。到这里,所有的事件处理都会调用到notifyInternal2函数里面,notifyInternal2在前面已经分析过。
前面我们已经知道,QWindowSystemInterface::sendWindowSystemEvents是调用windowSystemEventsQueued来检查是否存在系统消息,windowSystemEventsQueued的实现:
int QWindowSystemInterfacePrivate::windowSystemEventsQueued()
{
return windowSystemEventQueue.count();
}
实现很简单,取windowSystemEventQueue这个容器存储的元素数量。毫无疑问,windowSystemEventQueue是用来存储系统消息的。那么,系统产生的消息是如何加入到这个队列中的呢?
答案是调用QWindowSystemInterfacePrivate::handleWindowSystemEvent添加到队列中的。
template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>WindowSystemEvent *ev)
{
windowSystemEventQueue.append(ev);
if (QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::qt_qpa_core_dispatcher())
dispatcher->wakeUp();
return true;
}
handleWindowSystemEvent不仅将消息添加到队列中,还调用awake唤醒线程来处理。
那么最开始是谁调用handleWindowSystemEvent来往队列里面添加消息呢?是 QWindowsContext::windowsProc函数。
bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
QtWindows::WindowsEventType et,
WPARAM wParam, LPARAM lParam,
LRESULT *result,
QWindowsWindow **platformWindowPtr)
{
...
switch (et) {
case QtWindows::KeyDownEvent:
case QtWindows::KeyEvent:
case QtWindows::InputMethodKeyEvent:
case QtWindows::InputMethodKeyDownEvent:
case QtWindows::AppCommandEvent:
return sessionManagerInteractionBlocked() ||
d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result);
...
case QtWindows::MoveEvent:
platformWindow->handleMoved();
default:
break
}
...
}
windowsProc会根据不同的类型,调用对应事件的handle,调用hander的时候,最终调用到handleWindowSystemEvent,将消息加到队列中。
再往前追索,windowsProc又是被谁调起来的呢? windowsProc是被qWindowsWndProc调用的,而qWindowsWndProc是个窗口过程的回调函数,由系统调用的。这与前面的qt_internal_proc 如出一辙,都是窗口过程的回调函数。不同的,qWindowsWndProc是真实的窗口(本例中的QWidget窗口)的回调过程,而qt_internal_proc是一个隐藏窗口的回调过程,隐藏窗口本身没什么意义,只是为了让QApplication利用windows的消息机制。
熟悉Win32消息机制的对下面的这段代码应该很熟悉,这是向windows注册一个窗口,并绑定了一个窗口过程的回调函数。
QString QWindowsContext::registerWindowClass(const QWindow *w)
{
...
return registerWindowClass(cname, qWindowsWndProc, style, GetSysColorBrush(COLOR_WINDOW), icon);
}
上面的代码中,qWindowsWndProc传递给registerWindowClass作为窗口过程处理的回调函数。
QString QWindowsContext::registerWindowClass(QString cname,
WNDPROC proc,
unsigned style,
HBRUSH brush,
bool icon)
{
// since multiple Qt versions can be used in one process
// each one has to have window class names with a unique name
// The first instance gets the unmodified name; if the class
// has already been registered by another instance of Qt then
// add a UUID.
static int classExists = -1;
const HINSTANCE appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
if (classExists == -1) {
WNDCLASS wcinfo;
classExists = GetClassInfo(appInstance, reinterpret_cast<LPCWSTR>(cname.utf16()), &wcinfo);
classExists = classExists && wcinfo.lpfnWndProc != proc;
}
if (classExists)
cname += QUuid::createUuid().toString();
if (d->m_registeredWindowClassNames.contains(cname)) // already registered in our list
return cname;
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = style;
wc.lpfnWndProc = proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = appInstance;
wc.hCursor = nullptr;
wc.hbrBackground = brush;
wc.lpszMenuName = nullptr;
wc.lpszClassName = reinterpret_cast<LPCWSTR>(cname.utf16());
ATOM atom = RegisterClassEx(&wc);
if (!atom)
qErrnoWarning("QApplication::regClass: Registering window class '%s' failed.", qPrintable(cname));
d->m_registeredWindowClassNames.insert(cname);
return cname;
}
在Qt中,创建一个root级别的Widget。就会创建一个win32窗口,也就是会调用registerWindowClass向系统注册窗口。
至此, Qt 事件系统的一些底层的原理和大概流程,就已经讨论完了。
总结一下:
1、一个Qt GUI程序会创建两个窗口,一个是隐藏窗口,目的是让QApplication与Windows之间建立桥梁,使QApplication能利用windows的消息机制,其回调函数为qt_internal_proc。另一个是真实的Widget创建的,其回调过程为qWindowsWndProc。qt_internal_proc回调主要处理QApp自身产生的一些事件,qWindowsWndProc主要处理真实窗口交互时产生的事件。
2、不管是qt_internal_proc还是qWindowsWndProc,最终都调用notifyInternal2来处理event。
3、QApplication::exec调用QEventDispatcherWin32::processEvents,开启了Qt应用程序的消息循环过程,内部会源源不断的从队列中取出消息。根据消息所属窗口进行分发,如果是隐藏窗口的,调用qt_internal_proc处理,如果属于真是窗口的,调用qWindowsWndProc处理。或者将特定类型的消息通过PostMessage传递给Windows,下一次消息循环时再处理。
4、QApplication::postEvent内部是先通过PostMessage WM_QT_SENDPOSTEDEVENTS类型的消息,将消息放在队列中,在下一次循环时,通过QEventDispatcherWin32::sendPostedEvents从而调用QApplication::sendEvent,实现了异步。
5、Qt程序的事件(不管是自身产生的,还是系统产生的)最终都是notifyInternal2来处理。而notifyInternal2的实现又依次会调用notify、ApplicationEventFilters、ObjEventFilters、event、eventhandler。这5个过程,都可以在从重写或拦截。