Window系统下开发Qt(10)-- Qt事件

概述

在讨论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的事件分为两种:

  1. 与用户交互时发生。比如移动鼠标(enterEvent),敲击键盘(keyPressEvent)等。
  2. 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循环。分析这段代码,概括地讲大体做了以下几件事:

  1. 初始化一个不可见窗体(如果没有初始化过的话);
  2. 如果用户输入事件或socket 事件队列不空,就取一个事件出来;
  3. 如果 2 中没有获取到消息,则调用PeekMessage从系统中取一个消息(这个函数是非阻塞的);
  4. 如果3中的PeekMessage也没取到消息,证明app没有需要处理的消息。就调用MsgWaitForMultipleObjectsEx阻塞,直到有新的消息到达。
  5. 如果3中获取到了消息或MsgWaitForMultipleObjectsEx阻塞时得到了消息,先依次对Posted Event 、 Timer Event、 Quit Event进行预处理。然后,使用 windowsAPI TranslateMessage、DispatchMessage 分发消息;
  6. 对于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;
}

这函数里面,有几个重点的代码,分别是

  1. sendThroughApplicationEventFilters
  2. sendThroughObjectEventFilters
  3. 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个过程,都可以在从重写或拦截。


 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值