qt-事件循环系统

2023-11-15

        Qt中,如果创建的console程序,使用的是QCoreApplication对象;如果创建的是GUI程序,使用的是QApplication对象,而QApplication 继承自 QGUIApplication ,最终继承QCoreApplication,GUI是对console的封装。

        QEventDispatcherWin32(win32事件调度器),主要功能是执行程序运行期间所涉及到的过程事件以及创建windows的隐藏窗口。C:\Qt\5.9.8\Src\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp  创建了一个隐藏窗口


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
                            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, (LONG_PTR)eventDispatcher);
#else
    SetWindowLong(wnd, GWL_USERDATA, (LONG)eventDispatcher);
#endif

    return wnd;
}

        QEventDispatcherWin32Private(win32事件调度器私有),主要存储了qt与windows操作系统之间的事件交互队列(用户输入事件队列、用户套接字事件队列)。

        qt与windows之间使用MSG对象进行交互,MSG是windows的原生对象,qt 以QList<MSG>的方式来存储windows的事件对象,QEventDispatcherWin32Private提供了套接字\定时器相关的操作接口,和用户自定义的外部窗口所涉及到的事件队列:QWinEventNotifier,QSocketNotifier

    QWinEventNotifier:用户自定义的窗口事件.

      qt与windows交互时,主要用到了三个回调

qt_internal_proc  解析windows系统给qt传输的MSG消息对象,qt解析MSG后进行相应的处理

qt_GetMessageHook:qt 手动调用,向windows操作系统传输MSG消息对象

qt_fast_timer_proc:主要用于工作线程,qt向操作系统传入定时器相关的数据

数据存储 :QCoreApplication的初始化

        系统级EventLoop :qt系统是如何对接windows操作系统,存储和调度MSG

        用户级EventLoop:qt内部的消息处理

qt  console程序

C:\qt5base\qtbase\src\corelib\kernel\qcoreapplication.cpp

        构造一个 Qt 核心应用程序。 核心应用是没有图形用户界面。 此类应用程序用于控制台或作为服务器进程。argc 和 argv 参数由应用程序处理,并通过 arguments() 以更方便的形式提供功能。

QCoreApplication::QCoreApplication(int &argc, char **argv
#ifndef Q_QDOC
                                   , int _internal
#endif
                                   )
#ifdef QT_NO_QOBJECT
    : d_ptr(new QCoreApplicationPrivate(argc, argv, _internal))
#else

    //1 构建QCoreApplicationPrivate对象 并且传入命令行参数
    : QObject(*new QCoreApplicationPrivate(argc, argv, _internal))
#endif
{
    d_func()->q_ptr = this;
    //2 调用QCoreApplicationPrivate init函数  创建事件调度器
    d_func()->init();
#ifndef QT_NO_QOBJECT
    // 3 启动事件调度器
    QCoreApplicationPrivate::eventDispatcher->startingUp();
#endif
}

调用 void QCoreApplicationPrivate::init() 

void QCoreApplicationPrivate::init()
{
#if defined(Q_OS_MACOS)
    QMacAutoReleasePool pool;
#endif

    Q_Q(QCoreApplication);

    initLocale();

    Q_ASSERT_X(!QCoreApplication::self, "QCoreApplication", "there should be only one application object");
    QCoreApplication::self = q;


    //1 设置应用名称和版本
    // Store app name/version (so they're still available after QCoreApplication is destroyed)
    if (!coreappdata()->applicationNameSet)
        coreappdata()->application = appName();

    if (!coreappdata()->applicationVersionSet)
        coreappdata()->applicationVersion = appVersion();

......

#ifndef QT_NO_QOBJECT

    // 2 创建事件调度器
    // use the event dispatcher created by the app programmer (if any)
    if (!eventDispatcher)//第一次threadData->eventDispatcher.load()返回为空
        eventDispatcher = threadData->eventDispatcher.load();
    // otherwise we create one
    if (!eventDispatcher)
        createEventDispatcher();//创建事件调度器
    Q_ASSERT(eventDispatcher);

    //3 将事件调度器的槽函数放到新的线程threadData->thread中
    if (!eventDispatcher->parent()) {
        eventDispatcher->moveToThread(threadData->thread);
        eventDispatcher->setParent(q);
    }

    //4 重置当前线程的事件调度器
    threadData->eventDispatcher = eventDispatcher;
    eventDispatcherReady();//事件调度器处于准备状态
#endif

#ifdef QT_EVAL
    extern void qt_core_eval_init(QCoreApplicationPrivate::Type);
    qt_core_eval_init(application_type);
#endif

    processCommandLineArguments();//处理命令行参数

    qt_call_pre_routines();
    qt_startup_hook();//启动hook 函数
#ifndef QT_BOOTSTRAPPED
    if (Q_UNLIKELY(qtHookData[QHooks::Startup]))
        reinterpret_cast<QHooks::StartupCallback>(qtHookData[QHooks::Startup])();
#endif

#ifndef QT_NO_QOBJECT
    is_app_running = true; // No longer starting up.
#endif
}

        事件调度器类型    static QAbstractEventDispatcher *eventDispatcher;,它继承于QObject;

         QThreadData *threadData; // id of the thread that owns the object  位于QObjectPrivate中(它继承QObject)

createEventDispatcher

        系统创建事件调度器:void QCoreApplicationPrivate::createEventDispatcher(),在windows屏幕创建的是    eventDispatcher = new QEventDispatcherWin32(q);  QEventDispatcherWin32 :公有继承 QAbstractEventDispatcher

void QCoreApplicationPrivate::createEventDispatcher()
{
    Q_Q(QCoreApplication);
#if defined(Q_OS_UNIX)
#  if defined(Q_OS_DARWIN)
    bool ok = false;
    int value = qEnvironmentVariableIntValue("QT_EVENT_DISPATCHER_CORE_FOUNDATION", &ok);
    if (ok && value > 0)
        eventDispatcher = new QEventDispatcherCoreFoundation(q);
    else
        eventDispatcher = new QEventDispatcherUNIX(q);
#  elif !defined(QT_NO_GLIB)
    if (qEnvironmentVariableIsEmpty("QT_NO_GLIB") && QEventDispatcherGlib::versionSupported())
        eventDispatcher = new QEventDispatcherGlib(q);
    else
        eventDispatcher = new QEventDispatcherUNIX(q);
#  else
        eventDispatcher = new QEventDispatcherUNIX(q);
#  endif
#elif defined(Q_OS_WINRT)
    eventDispatcher = new QEventDispatcherWinRT(q);
#elif defined(Q_OS_WIN)
    eventDispatcher = new QEventDispatcherWin32(q);
#else
#  error "QEventDispatcher not yet ported to this platform"
#endif
}

C:\Qt\5.9.8\Src\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp

在QEventDispatcherWin32构造函数中,并没有什么操作,仅仅创建了QEventDispatcherWin32Private的实例对象

QEventDispatcherWin32::QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent)
    : QAbstractEventDispatcher(dd, parent)
{ }

QEventDispatcherWin32Private 也没有实质操作 

QEventDispatcherWin32Private::QEventDispatcherWin32Private()
    : threadId(GetCurrentThreadId()), interrupt(false), closingDown(false), internalHwnd(0),
      getMessageHook(0), serialNumber(0), lastSerialNumber(0), sendPostedEventsWindowsTimerId(0),
      wakeUps(0)
    , activateNotifiersPosted(false)
{
}

QEventDispatcherWin32Private 的定义:

class Q_CORE_EXPORT QEventDispatcherWin32Private : public QAbstractEventDispatcherPrivate
{
    Q_DECLARE_PUBLIC(QEventDispatcherWin32)
public:
    QEventDispatcherWin32Private();
    ~QEventDispatcherWin32Private();

    DWORD threadId;

    bool interrupt;
    bool closingDown;
 
    //用于 socketnotifiers/timers/etc 的内部窗口句柄
    // internal window handle used for socketnotifiers/timers/etc
    HWND internalHwnd;
    HHOOK getMessageHook;

    //用于控制何时发送发布的事件
    // for controlling when to send posted events
    QAtomicInt serialNumber;
    int lastSerialNumber, sendPostedEventsWindowsTimerId;
    QAtomicInt wakeUps;

    // timers  定时器
    WinTimerVec timerVec;
    WinTimerDict timerDict;
    void registerTimer(WinTimerInfo *t);
    void unregisterTimer(WinTimerInfo *t);
    void sendTimerEvent(int timerId);

    // socket notifiers  socket通知
    QSNDict sn_read;
    QSNDict sn_write;
    QSNDict sn_except;
    QSFDict active_fd;
    bool activateNotifiersPosted;
    void postActivateSocketNotifiers();
    void doWsaAsyncSelect(int socket, long event);

    QList<QWinEventNotifier *> winEventNotifierList;//QWinEventNotifier 列表
    void activateEventNotifier(QWinEventNotifier * wen);

    QList<MSG> queuedUserInputEvents;//用户输入事件队列
    QList<MSG> queuedSocketEvents;//socket事件队列
};

事件循环启动过程

        QCoreApplication 在初始化时,会创建win32事件调度器QEventDispatcherWin32,QEventDispatcherWin32


class Q_CORE_EXPORT QEventDispatcherWin32 : public QAbstractEventDispatcher
{
    Q_OBJECT
    Q_DECLARE_PRIVATE(QEventDispatcherWin32)

protected:
    void createInternalHwnd();
    void installMessageHook();
    void uninstallMessageHook();

public:
    explicit QEventDispatcherWin32(QObject *parent = 0);
    ~QEventDispatcherWin32();

    bool QT_ENSURE_STACK_ALIGNED_FOR_SSE processEvents(QEventLoop::ProcessEventsFlags flags);
    bool hasPendingEvents();

    void registerSocketNotifier(QSocketNotifier *notifier);
    void unregisterSocketNotifier(QSocketNotifier *notifier);

    void registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object);
    bool unregisterTimer(int timerId);
    bool unregisterTimers(QObject *object);
    QList<TimerInfo> registeredTimers(QObject *object) const;

    bool registerEventNotifier(QWinEventNotifier *notifier);
    void unregisterEventNotifier(QWinEventNotifier *notifier);
    void activateEventNotifiers();

    int remainingTime(int timerId);

    void wakeUp();
    void interrupt();
    void flush();

    void startingUp();
    void closingDown();

    bool event(QEvent *e);

    HWND internalHwnd();

protected:
    QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent = 0);
    virtual void sendPostedEvents();
    void doUnregisterSocketNotifier(QSocketNotifier *notifier);

private:
    friend LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp);
    friend LRESULT QT_WIN_CALLBACK qt_GetMessageHook(int, WPARAM, LPARAM);
};

 QCoreApplication调用exec接口完成程序的启动:


    进入主事件循环并等待直到 exit() 被调用。 需要调用此函数来启动事件处理。主事件循环从窗口系统接收事件并将这些分派给应用程序小部件。使您的应用程序执行空闲处理(通过执行
每当没有未决事件时的特殊功能),使用超时时间为 0 的 QTimer。更高级的空闲处理方案可以
使用 processEvents() 来实现。

int QCoreApplication::exec()
{
    //1 检查自身实例是否已经存在
    if (!QCoreApplicationPrivate::checkInstance("exec"))
        return -1;


    //2 检查QCoreApplication对象是否位于主线程中
    QThreadData *threadData = self->d_func()->threadData;
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }

    //3 检查主线程中是否已经存在事件调度器
    if (!threadData->eventLoops.isEmpty()) {
        qWarning("QCoreApplication::exec: The event loop is already running");
        return -1;
    }

    //4 创建事件调度器 并且启动时间调度器
    threadData->quitNow = false;
    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    int returnCode = eventLoop.exec();
    threadData->quitNow = false;


    //5 程序退出,执行清理操作
    if (self)
        self->d_func()->execCleanup();

    return returnCode;
}

int QEventLoop::exec(ProcessEventsFlags flags)

QEventLoop 做了什么


先看构造函数:

QEventLoop::QEventLoop(QObject *parent)
    : QObject(*new QEventLoopPrivate, parent)
{
    Q_D(QEventLoop);
    //QApplication是所有线程共享的对象,全局且唯一  
    if (!QCoreApplication::instance()) {
        qWarning("QEventLoop: Cannot be used without QApplication");
    } else if (!d->threadData->eventDispatcher.load()) {
    //如果当前线程还没有事件派发器,那就创建一个
        QThreadPrivate::createEventDispatcher(d->threadData);
    }
}


        一,QApplication是所有线程共享的对象,全局且唯一 ;

        二,一个线程有且只有一个eventDispatcher,如果不存在,则创建一个。而且,由于QEventLoop不能在QApplication之前创建,所以,如果QEventLoop是在GUI线程中构造,那么eventDispatcher早在QApplication构造时就被创建了,所以免了自己创建eventDispatcher的步骤。如果QEventLoop是在非GUI线程中构造呢?这种情况肯定是存在的,因为非GUI线程可能也需要处理事件,这些事件不是来自可见窗口,而是来自自己或其他线程。例如,使用跨线程的信号和槽(如postEvent 跨线程调用时,发送者将Post事件发到接收者的postevent列表中,然后调用接收者的事件分发器的wakeup,使得接收者的消息循环处理事件,在下面的Postevent的实现中有讲述)。下面看看在非GUI下的QEventLoop:

class MyThread :public QThread
{
    void run() Q_DECL_OVERRIDE{
        //start a event loop
        this->exec();
    }
 
};
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
 
    MyThread t;
    t.start();
 
    return a.exec();
}


        现在,程序有两个线程,一个是GUI线程,一个是线程t。GUI线程有一个事件循环,在a.exec中创建并启动,线程t也有一个事件循环,在t.exec中创建并启动。

        就像在GUI线程中的事件循环需要使用一个事件派发器一样,任何一个线程中的事件循环都需要一个派发器。GUI线程中的事件派发器是在构造QApplication时创建的,是一个QWindowsGuiEventDispatcher类的派发器,在这个派发器的构造函数中同时还创建了一个message-only窗口。

        对于需要事件循环的非GUI线程,message-only窗口是不可或缺的,因为没有他,线程就没有消息队列,何谈消息循环,除非Qt使用另外的机制而非消息循环机制来支持非GUI线程的事件循环,不过这完全没必要。我们来看看这些步骤在非GUI线程中是怎么完成的:

        第一步,创建一个事件派发器

事件派发器在t.start中被创建:

unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
{
    QThread *thr = reinterpret_cast<QThread *>(arg);
    QThreadData *data = QThreadData::get2(thr);
 
    qt_create_tls();
    TlsSetValue(qt_current_thread_data_tls_index, data);
    data->threadId = reinterpret_cast<Qt::HANDLE>(GetCurrentThreadId());
  ...
    if (data->eventDispatcher.load()) // custom event dispatcher set?
        data->eventDispatcher.load()->startingUp();
    else
        createEventDispatcher(data); //创建事件派发器
  ...
}


        创建的是一个QEventDispatcherWin32类的事件派发器,它并不像QWindowsGuiEventDispatcher一样在构造的同时还创建 message-only 窗口。

第二步,创建一个 message-only 窗口

        如果在启动事件循环的过程中发现当前的事件派发器还没有创建 message-only 窗口的话,那就会为其创建一个这样的窗口。

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
      Q_D(QEventDispatcherWin32);
 
    if (!d->internalHwnd) {
        createInternalHwnd();
        wakeUp(); // trigger a call to sendPostedEvents()
    }
 ...
}


        对比GUI线程创建事件派发器和message-only窗口的一步到位,非GUI线程采用延迟的方式来处理。为什么要这样做呢?像GUI线程一步到位不行吗?当然可以,但是没必要,因为创建一个 message-only 窗口是要占用内核资源的,GUI线程一定需要一个消息循环来实现事件循环,所以一步到位的创建没毛病,但是非GUI线程可能根本就不需要一个事件循环,所以,白白浪费资源干嘛呢?

int QEventLoop::exec(ProcessEventsFlags flags)

         进入主事件循环并等待直到 exit() 被调用。返回传递给 exit() 的值。需要调用此函数来启动事件处理。 这主事件循环从窗口系统接收事件并将这些分派给应用程序小部件。

         一般来说,之前不能进行任何用户交互调用 exec()。 作为一个特例,像 QMessageBox 这样的模态小部件可以在调用 exec() 之前使用,因为模态小部件使用他们自己的本地事件循环。

int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    //we need to protect from race condition with QThread::exit
    QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread))->mutex);
    if (d->threadData->quitNow)
        return -1;

    if (d->inExec) {
        qWarning("QEventLoop::exec: instance %p has already called exec()", this);
        return -1;
    }

    struct LoopReference {
        QEventLoopPrivate *d;
        QMutexLocker &locker;

        bool exceptionCaught;
        LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true)
        {
            d->inExec = true;
            d->exit.storeRelease(false);
            ++d->threadData->loopLevel;
//将事件循环器添加到该线程的事件循环器容器中:
            d->threadData->eventLoops.push(d->q_func());
            locker.unlock();
        }

        ~LoopReference()
        {
            if (exceptionCaught) {
                qWarning("Qt has caught an exception thrown from an event handler. Throwing\n"
                         "exceptions from an event handler is not supported in Qt.\n"
                         "You must not let any exception whatsoever propagate through Qt code.\n"
                         "If that is not possible, in Qt 5 you must at least reimplement\n"
                         "QCoreApplication::notify() and catch all exceptions there.\n");
            }
            locker.relock();
            QEventLoop *eventLoop = d->threadData->eventLoops.pop();
            Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
            Q_UNUSED(eventLoop); // --release warning
            d->inExec = false;
            --d->threadData->loopLevel;
        }
    };
    LoopReference ref(d, locker);


   // 判断当前线程是不是主线程,如果不是则清除 QEvent::Quit事件
    // remove posted quit events when entering a new event loop
    QCoreApplication *app = QCoreApplication::instance();
    if (app && app->thread() == thread())
        QCoreApplication::removePostedEvents(app, QEvent::Quit);

   //循环:如果程序没有退出,则循环执行processEvents:flags此时默认值是AllEvents
   //执行所有事件
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);

    ref.exceptionCaught = false;
    return d->returnCode.load();
}

bool QEventLoop::processEvents(ProcessEventsFlags flags)

        处理匹配 flags标志的未决事件,直到没有更多要处理的事件。 如果处理了未决事件,则返回 true;否则返回 false。

     这个函数只是一个包装器 ,具体由QAbstractEventDispatcher::processEvents()执行

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    //检查QEventLoopPrivate中的事件调度器是否存在
    if (!d->threadData->eventDispatcher.load())
        return false;

    //调用事件调度器处理函数
    return d->threadData->eventDispatcher.load()->processEvents(flags);
}

在windows平台 即bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)

QEventDispatcherWin32::processEvents

        QEventDispatcherWin32 由 createEventDispatcher接口在主线程中由void QCoreApplicationPrivate::init()中创建;

        同时,如果是创建一个线程,那么在QThread调用windows api创建线程传递函数地址QThreadPriavate::start中,也会创建:

         createEventDispatcher在windows平台仅仅是创建了一个QEventDispatcherWin32实例对象,并没有其他操作;QEventDispatcherWin32的事件处理过程是通过 (主)线程调用exec函数最终调用bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)来完成的。

C:\Qt\5.9.8\Src\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp

        QEventDispatcherWin32 的d 指针QEventDispatcherWin32Private对象中存储了

    QList<MSG> queuedUserInputEvents;//用户输入事件队列
    QList<MSG> queuedSocketEvents;//socket事件队列

        QEventDispatcherWin32 的主要作用是使得QT系统 和windows系统进行数据的交互,使用的MSG消息对象。


bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{

    //1 获取QEventDispatcherWin32Private对象
    Q_D(QEventDispatcherWin32);

    //2 检测QEventDispatcherWin32Private对象是否已经创建了隐藏窗口,
       //如果没有则创建隐藏窗口
    if (!d->internalHwnd) {
        createInternalHwnd();
        wakeUp(); // trigger a call to sendPostedEvents()
    }

    d->interrupt = false;
    //3 发送隐藏窗口已创建的信号
    emit awake();

    bool canWait;
    bool retVal = false;
    bool seenWM_QT_SENDPOSTEDEVENTS = false;
    bool needWM_QT_SENDPOSTEDEVENTS = false;
    do {
        DWORD waitRet = 0;
        HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
        QVarLengthArray<MSG> processedTimers;
        while (!d->interrupt) {
            DWORD nCount = d->winEventNotifierList.count();
            Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);

            MSG msg;
            bool haveMessage;


            //4 对两个队列中的数据分别进行处理
                 //检查用户输入事件队列是否为空
            if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
                // process queued user input events
                //处理输入事件,获取队列中最先来的事件
                haveMessage = true;
                msg = d->queuedUserInputEvents.takeFirst();
               //检查socket消息事件队列
            } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
                // process queued socket events
                haveMessage = true;
                msg = d->queuedSocketEvents.takeFirst();
            } else {
                //检查消息队列中事件的可用性:PM_REMOVE PeekMessage处理后,消息从队列里除掉。
                haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
                //如果有消息,判断消息类型
                if (haveMessage) {
                    //将可用的事件进行转化,重新添加到 用户输入事件或者socket事件中
                    if ((flags & QEventLoop::ExcludeUserInputEvents)
                        && ((msg.message >= WM_KEYFIRST
                             && msg.message <= WM_KEYLAST)
                            || (msg.message >= WM_MOUSEFIRST
                                && msg.message <= WM_MOUSELAST)
                            || msg.message == WM_MOUSEWHEEL
                            || msg.message == WM_MOUSEHWHEEL
                            || msg.message == WM_TOUCH
#ifndef QT_NO_GESTURES
                            || msg.message == WM_GESTURE
                            || msg.message == WM_GESTURENOTIFY
#endif
                            || msg.message == WM_CLOSE)) {
                        // 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;
                    }
                }
            }

            // 5 如果消息无效,进行下列操作
            if (!haveMessage) {
                // no message - check for signalled objects
                for (int i=0; i<(int)nCount; i++)
                    pHandles[i] = d->winEventNotifierList.at(i)->handle();
                waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
                if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) {
                    // a new message has arrived, process it
                    continue;
                }
            }

            // 6 如果消息有效
            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, (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(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
		    //检查数量,如果数量不对,激活本地事件过滤器
            } else if (waitRet - WAIT_OBJECT_0 < nCount) {
                d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
            } else {
                // nothing todo so break
                break;
            }
            retVal = true;
        }

        // still nothing - wait for message or signalled objects
        canWait = (!retVal
                   && !d->interrupt
                   && (flags & QEventLoop::WaitForMoreEvents));
        if (canWait) {
            DWORD nCount = d->winEventNotifierList.count();
            Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
            for (int i=0; i<(int)nCount; i++)
                pHandles[i] = d->winEventNotifierList.at(i)->handle();

            emit aboutToBlock();
            waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
            emit awake();
            if (waitRet - WAIT_OBJECT_0 < nCount) {
                d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
                retVal = true;
            }
        }
    } while (canWait);

    if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == 0) {
        // when called "manually", always send posted events
        sendPostedEvents();//发送当前线程中,postevent事件队列中所有剩余的post事件到操作系统中
    }

    if (needWM_QT_SENDPOSTEDEVENTS)
        PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);

    return retVal;
}

创建隐藏窗口的过程 QEventDispatcherWin32::createInternalHwnd()

        qt获取windows系统的消息主要是通过windows原生接口来获取消息数据,从而对消息进行解包,分类,从而将数据发送到qt系统处理

        qt向windows传递事件消息,主要是通过向windows系统中安装挂钩,从而进一步通过事先创建的隐藏窗口将qt本身的 事件打包,进一步通过隐藏窗口进行事件的转发,传输到windows操作系统

void QEventDispatcherWin32::createInternalHwnd()
{
    Q_D(QEventDispatcherWin32);

    if (d->internalHwnd)
        return;
   
    //1 创建 windows原生窗口
    d->internalHwnd = qt_create_internal_window(this);

    //2 安装消息钩子
    installMessageHook();

    // start all normal timers
    for (int i = 0; i < d->timerVec.count(); ++i)
        d->registerTimer(d->timerVec.at(i));
}

创建windows原生窗口,一些自定义消息都是在这个窗口的过程回调中处理


static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{
    // 1 创建一个windows 消息上下文对象
    QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();
    if (!ctx->atom)
        return 0;

    //2 创建一个大小为0x0 的窗口
    HWND wnd = CreateWindow(ctx->className,    // classname
                            ctx->className,    // window name
                            0,                 // style
                            0, 0, 0, 0,        // geometry
                            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, (LONG_PTR)eventDispatcher);
#else
    SetWindowLong(wnd, GWL_USERDATA, (LONG)eventDispatcher);
#endif

    return wnd;
}

QWindowsMessageWindowClassContext  在windows消息的上下文对象中,首先完成类名的注册,其中窗口过程函数是qt_internal_proc

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;
    }
}

窗口过程函数:获取windows系统传递到qt系统的 MSG消息对象,进行解析,并且传递到qt系统进行相应的处理。

socket消息,也是Qt内部自定义的消息WM_QT_SOCKETNOTIFIER
postEvent到一些对象的消息,也是自定义消息WM_QT_SENDPOSTEDEVENTS
通过QObject注册的定时器消息WM_TIMER


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;
    } else if (dispatcher->filterNativeEvent(QByteArrayLiteral("windows_dispatcher_MSG"), &msg, &result)) {
        return result;
    }

#ifdef GWLP_USERDATA
    QEventDispatcherWin32 *q = (QEventDispatcherWin32 *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
#else
    QEventDispatcherWin32 *q = (QEventDispatcherWin32 *) GetWindowLong(hwnd, GWL_USERDATA);
#endif
    QEventDispatcherWin32Private *d = 0;
    if (q != 0)
        d = q->d_func();

    if (message == 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;
    } else if (message == 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;
    } else if (message == WM_QT_SENDPOSTEDEVENTS
               // we also use a Windows timer to send posted events when the message queue is full
               || (message == WM_TIMER
                   && d->sendPostedEventsWindowsTimerId != 0
                   && wp == (uint)d->sendPostedEventsWindowsTimerId)) {
        const int localSerialNumber = d->serialNumber.load();
        if (localSerialNumber != d->lastSerialNumber) {
            d->lastSerialNumber = localSerialNumber;
            q->sendPostedEvents();
        }
        return 0;
    } else if (message == WM_TIMER) {
        Q_ASSERT(d != 0);
        d->sendTimerEvent(wp);
        return 0;
    }

    return DefWindowProc(hwnd, message, wp, lp);
}

安装消息钩子,将qt的钩子函数qt_GetMessageHook添加到系统hooks中, 监听当前程序中该线程中的所有事件

       SetWindowsHookEx 钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。

void QEventDispatcherWin32::installMessageHook()
{
    Q_D(QEventDispatcherWin32);

    if (d->getMessageHook)
        return;

    // setup GetMessage hook needed to drive our posted events
    d->getMessageHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC) qt_GetMessageHook, NULL, GetCurrentThreadId());
    if (Q_UNLIKELY(!d->getMessageHook)) {
        int errorCode = GetLastError();
        qFatal("Qt: INTERNAL ERROR: failed to install GetMessage hook: %d, %s",
               errorCode, qPrintable(qt_error_string(errorCode)));
    }
}

LRESULT QT_WIN_CALLBACK qt_GetMessageHook(int code, WPARAM wp, LPARAM lp)
{

    //1 获取事件调度器
    QEventDispatcherWin32 *q = qobject_cast<QEventDispatcherWin32 *>(QAbstractEventDispatcher::instance());
    Q_ASSERT(q != 0);

    if (wp == PM_REMOVE) {
        if (q) {
            MSG *msg = (MSG *) lp;
            QEventDispatcherWin32Private *d = q->d_func();
            const int localSerialNumber = d->serialNumber.load();
            static const UINT mask = inputTimerMask();
            if (HIWORD(GetQueueStatus(mask)) == 0) {
                // no more input or timer events in the message queue, we can allow posted events to be sent normally now
                if (d->sendPostedEventsWindowsTimerId != 0) {
                    // stop the timer to send posted events, since we now allow the WM_QT_SENDPOSTEDEVENTS message
                    KillTimer(d->internalHwnd, d->sendPostedEventsWindowsTimerId);
                    d->sendPostedEventsWindowsTimerId = 0;
                }
                (void) d->wakeUps.fetchAndStoreRelease(0);
                if (localSerialNumber != d->lastSerialNumber
                    // if this message IS the one that triggers sendPostedEvents(), no need to post it again
                    && (msg->hwnd != d->internalHwnd
                        || msg->message != WM_QT_SENDPOSTEDEVENTS)) {

                   //使用windows api PostMessage将消息发送到隐藏窗口,从而发送给windows操作系统
                    PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
                }
            } else if (d->sendPostedEventsWindowsTimerId == 0
                       && localSerialNumber != d->lastSerialNumber) {
                // start a special timer to continue delivering posted events while
                // there are still input and timer messages in the message queue
                d->sendPostedEventsWindowsTimerId = SetTimer(d->internalHwnd,
                                                             SendPostedEventsWindowsTimerId,
                                                             0, // we specify zero, but Windows uses USER_TIMER_MINIMUM
                                                             NULL);
                // we don't check the return value of SetTimer()... if creating the timer failed, there's little
                // we can do. we just have to accept that posted events will be starved
            }
        }
    }
    return q->d_func()->getMessageHook ? CallNextHookEx(0, code, wp, lp) : 0;
}

自定义一个类如qttest 继承QWidget时,它是如何收到鼠标离开事件的呢?

bool QEventDispatcherWin32::processEvents中循环的 peekMessage 并                     TranslateMessage(&msg);

DispatchMessage(&msg);将鼠标离开事件分发到相应窗口句柄的窗口过程中

当执行DispatchMessage windowsapi时,如上图,当前消息窗口句柄是0x000c1b1a;该消息会被分发到该窗口句柄的过程函数中 

实际窗口的窗口函数:C:\Qt\5.9.8\Src\qtbase\src\plugins\platforms\windows\qwindowscontext.cpp


extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT result;
    // 1 计算消息类型 
    const QtWindows::WindowsEventType et = windowsEventType(message, wParam, lParam);
    QWindowsWindow *platformWindow = nullptr;
    const RECT ncCalcSizeFrame = rectFromNcCalcSize(message, wParam, lParam, 0);

    // 2 处理消息
    const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result, &platformWindow);
    if (QWindowsContext::verbose > 1 && lcQpaEvents().isDebugEnabled()) {
        if (const char *eventName = QWindowsGuiEventDispatcher::windowsMessageName(message)) {
            qCDebug(lcQpaEvents) << "EVENT: hwd=" << hwnd << eventName << hex << "msg=0x"  << message
                << "et=0x" << et << dec << "wp=" << int(wParam) << "at"
                << GET_X_LPARAM(lParam) << GET_Y_LPARAM(lParam) << "handled=" << handled;
        }
    }
    if (!handled)
        result = DefWindowProc(hwnd, message, wParam, lParam);

    // Capture WM_NCCALCSIZE on top level windows and obtain the window margins by
    // subtracting the rectangles before and after processing. This will correctly
    // capture client code overriding the message and allow for per-monitor margins
    // for High DPI (QTBUG-53255, QTBUG-40578).
    if (message == WM_NCCALCSIZE && !isEmptyRect(ncCalcSizeFrame) && isTopLevel(hwnd) && !isMinimized(hwnd)) {
        const QMargins margins =
            marginsFromRects(ncCalcSizeFrame, rectFromNcCalcSize(message, wParam, lParam, 0));
        if (margins.left() >= 0) {
            if (platformWindow) {
                platformWindow->setFrameMargins(margins);
            } else {
                const QSharedPointer<QWindowCreationContext> ctx = QWindowsContext::instance()->windowCreationContext();
                if (!ctx.isNull())
                    ctx->margins = margins;
            }
        }
    }
    return result;
}

QtWindows::WindowsEventType windowsEventType 将windows消息类型 转化成qt消息类型

C:\Qt\5.9.8\Src\qtbase\src\plugins\platforms\windows\qwindowscontext.cpp


inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamIn, LPARAM lParamIn)
{
    switch (message) {
    case WM_PAINT:
    case WM_ERASEBKGND:
        return QtWindows::ExposeEvent;
    case WM_CLOSE:
        return QtWindows::CloseEvent;
    case WM_DESTROY:
        return QtWindows::DestroyEvent;
    case WM_ACTIVATEAPP:
        return (int)wParamIn ?
        QtWindows::ActivateApplicationEvent : QtWindows::DeactivateApplicationEvent;
    case WM_MOUSEACTIVATE:
        return QtWindows::MouseActivateWindowEvent;
    case WM_ACTIVATE:
        return  LOWORD(wParamIn) == WA_INACTIVE ?
            QtWindows::DeactivateWindowEvent : QtWindows::ActivateWindowEvent;
    case WM_SETCURSOR:
        return QtWindows::CursorEvent;
    case WM_MOUSELEAVE:
        return QtWindows::MouseEvent;
    case WM_HSCROLL:
        return QtWindows::ScrollEvent;
    case WM_MOUSEWHEEL:
    case WM_MOUSEHWHEEL:
        return QtWindows::MouseWheelEvent;
 ......
        return QtWindows::ExitSizeMoveEvent;
    default:
        break;
    }
    if (message >= WM_NCMOUSEMOVE && message <= WM_NCMBUTTONDBLCLK)
        return QtWindows::NonClientMouseEvent; //
    if ((message >= WM_MOUSEFIRST && message <= WM_MOUSELAST)
         || (message >= WM_XBUTTONDOWN && message <= WM_XBUTTONDBLCLK))
        return QtWindows::MouseEvent;
    return QtWindows::UnknownEvent;
}

 QWindowsContext::windowsProc  对相应消息进行处理


/*!
     \brief Main windows procedure registered for windows.

     \sa QWindowsGuiEventDispatcher
*/

bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
                                  QtWindows::WindowsEventType et,
                                  WPARAM wParam, LPARAM lParam,
                                  LRESULT *result,
                                  QWindowsWindow **platformWindowPtr)
{
    *result = 0;

    MSG msg;
    msg.hwnd = hwnd;         // re-create MSG structure
    msg.message = message;   // time and pt fields ignored
    msg.wParam = wParam;
    msg.lParam = lParam;
    msg.pt.x = msg.pt.y = 0;
    if (et != QtWindows::CursorEvent && (et & (QtWindows::MouseEventFlag | QtWindows::NonClientEventFlag))) {
        msg.pt.x = GET_X_LPARAM(lParam);
        msg.pt.y = GET_Y_LPARAM(lParam);
        // For non-client-area messages, these are screen coordinates (as expected
        // in the MSG structure), otherwise they are client coordinates.
        if (!(et & QtWindows::NonClientEventFlag)) {
            ClientToScreen(msg.hwnd, &msg.pt);
        }
    } else {
        GetCursorPos(&msg.pt);
    }

    QWindowsWindow *platformWindow = findPlatformWindow(hwnd);
    *platformWindowPtr = platformWindow;

    // Run the native event filters.
    long filterResult = 0;
    QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance();
    if (dispatcher && dispatcher->filterNativeEvent(d->m_eventType, &msg, &filterResult)) {
        *result = LRESULT(filterResult);
        return true;
    }

    if (platformWindow) {
        filterResult = 0;
        if (QWindowSystemInterface::handleNativeEvent(platformWindow->window(), d->m_eventType, &msg, &filterResult)) {
            *result = LRESULT(filterResult);
            return true;
        }
    }
    if (et & QtWindows::InputMethodEventFlag) {
        QWindowsInputContext *windowsInputContext = ::windowsInputContext();
        // Disable IME assuming this is a special implementation hooking into keyboard input.
        // "Real" IME implementations should use a native event filter intercepting IME events.
        if (!windowsInputContext) {
            QWindowsInputContext::setWindowsImeEnabled(platformWindow, false);
            return false;
        }
        switch (et) {
        case QtWindows::InputMethodStartCompositionEvent:
            return windowsInputContext->startComposition(hwnd);
        case QtWindows::InputMethodCompositionEvent:
            return windowsInputContext->composition(hwnd, lParam);
        case QtWindows::InputMethodEndCompositionEvent:
            return windowsInputContext->endComposition(hwnd);
        case QtWindows::InputMethodRequest:
            return windowsInputContext->handleIME_Request(wParam, lParam, result);
        default:
            break;
        }
    } // InputMethodEventFlag

    switch (et) {
    case QtWindows::GestureEvent:
#if QT_CONFIG(sessionmanager)
        return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result);
#else
        return d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result);
#endif
    case QtWindows::InputMethodOpenCandidateWindowEvent:
    case QtWindows::InputMethodCloseCandidateWindowEvent:
        // TODO: Release/regrab mouse if a popup has mouse grab.
        return false;
    case QtWindows::DestroyEvent:
        if (platformWindow && !platformWindow->testFlag(QWindowsWindow::WithinDestroy)) {
            qWarning() << "External WM_DESTROY received for " << platformWindow->window()
                       << ", parent: " << platformWindow->window()->parent()
                       << ", transient parent: " << platformWindow->window()->transientParent();
            }
        return false;
 ......
     // 这里以LeaveEvent为例
    case QtWindows::MouseWheelEvent:
    case QtWindows::MouseEvent:
    case QtWindows::LeaveEvent:
        {
            QWindow *window = platformWindow->window();//找到原生窗口
            //如果该窗口不响应输入事件,则向上找父窗口
            while (window && (window->flags() & Qt::WindowTransparentForInput))
                window = window->parent();
            if (!window)
                return false;
#if QT_CONFIG(sessionmanager)
          //调用m_mouseHandler.translateMouseEvent进行鼠标事件处理
        return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result);
#else
        return d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result);
#endif
        }
    case QtWindows::TouchEvent:
#if QT_CONFIG(sessionmanager)
        return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result);
#else
        return d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result);
#endif
    case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow().
 .......
    default:
        break;
    }
    return false;
}

C:\Qt\5.9.8\Src\qtbase\src\plugins\platforms\windows\qwindowsmousehandler.cpp 


bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
                                               QtWindows::WindowsEventType et,
                                               MSG msg, LRESULT *result)
{
.......
    QWindowSystemInterface::handleMouseEvent(window, winEventPosition, globalPosition, buttons,
                                             QWindowsKeyMapper::queryKeyboardModifiers(),
                                             source);
    m_previousCaptureWindow = hasCapture ? window : 0;
    // QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND
    // is sent for unhandled WM_XBUTTONDOWN.
    return (msg.message != WM_XBUTTONUP && msg.message != WM_XBUTTONDOWN && msg.message != WM_XBUTTONDBLCLK)
        || QWindowSystemInterface::flushWindowSystemEvents();
}

接下来的过程:

void QWindowSystemInterface::handleFrameStrutMouseEvent(QWindow *window, const QPointF &local, const QPointF &global, Qt::MouseButtons b,
                                                        Qt::KeyboardModifiers mods, Qt::MouseEventSource source)
{
    const unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed();
    handleFrameStrutMouseEvent(window, time, local, global, b, mods, source);
}

其中: QPointF &local  鼠标在本窗口的位置; const QPointF &global  鼠标全局位置 ,如:

QWindowSystemInterface::handleFrameStrutMouseEvent 将鼠标的数据封装成 MouseEvent  

void QWindowSystemInterface::handleFrameStrutMouseEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, Qt::MouseButtons b,
                                                        Qt::KeyboardModifiers mods, Qt::MouseEventSource source)
{
    QWindowSystemInterfacePrivate::MouseEvent * e =
            new QWindowSystemInterfacePrivate::MouseEvent(window, timestamp,
                                                          QWindowSystemInterfacePrivate::FrameStrutMouse,
                                                          QHighDpi::fromNativeLocalPosition(local, window), QHighDpi::fromNativePixels(global, window), b, mods, source);
    QWindowSystemInterfacePrivate::handleWindowSystemEvent(e);
}

 QWindowSystemInterfacePrivate::handleWindowSystemEvent
     处理窗口系统事件。 默认情况下此函数将事件发布到窗口系统事件队列中,并且唤醒 Gui 事件调度器。 Qt Gui 将异步处理事件稍后。 返回值不用于异步模式,将永远是真实的。

     在同步模式下,Qt Gui 会立即处理事件。 返回值指示 Qt 是否接受该事件。 如果事件是从另一个线程传递的 比 Qt 主线程刷新窗口系统事件队列,这可能会传递其他事件也是如此。


template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::DefaultDelivery>(QWindowSystemInterfacePrivate::WindowSystemEvent *ev)
{
    if (synchronousWindowSystemEvents)//同步处理
        return handleWindowSystemEvent<QWindowSystemInterface::SynchronousDelivery>(ev);
    else//异步处理
        return handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(ev);
}

QWindowSystemInterfacePrivate::handleWindowSystemEvent
     通过将事件发布到 Qt Gui 来异步处理窗口系统事件。此函数将事件发布到窗口系统事件队列并唤醒 Gui 事件调度程序。 然后 Qt Gui 将异步处理事件。

        ev当前是MouseEvent 被加入windowSystemEventQueue,然后唤醒事件调度器wake


template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(WindowSystemEvent *ev)
{
    windowSystemEventQueue.append(ev);
    if (QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::qt_qpa_core_dispatcher())
        dispatcher->wakeUp();
    return true;
}


  void QEventDispatcherWin32::wakeUp()    post 一个WM_QT_SENDPOSTEDEVENTS消息到本线程 ,PostMessage是windows原生接口

 

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
        //将消息发送到隐藏窗口internalHwnd 中
       //发送WM_QT_SENDPOSTEDEVENTS(1025)给QApplication的
       //message-only窗口
        PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
    }
}

之后:最开始创建的隐藏窗口的窗口过程回调收到该消息进行处理

LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
    if (message == WM_NCCREATE)
        return true;
.......
     //收到消息
    } else if (message == WM_QT_SENDPOSTEDEVENTS
               // we also use a Windows timer to send posted events when the message queue is full
               || (message == WM_TIMER
                   && d->sendPostedEventsWindowsTimerId != 0
                   && wp == (uint)d->sendPostedEventsWindowsTimerId)) {
        const int localSerialNumber = d->serialNumber.load();
        if (localSerialNumber != d->lastSerialNumber) {
            d->lastSerialNumber = localSerialNumber;
            q->sendPostedEvents();//执行
        }
        return 0;
    } else if (message == WM_TIMER) {
        Q_ASSERT(d != 0);
        d->sendTimerEvent(wp);
        return 0;
    }

    return DefWindowProc(hwnd, message, wp, lp);
}

        注意,GUI线程使用的派发器是QWindowsGuiEventDispatcher,所以除了处理postEventList队列之外,还会处理windowSystemEventsQueued。但是非GUI线程使用的派发器是QEventDispatcherWin32,所以非GUI线程就只负责处理他的postEventList队列。

      

void QWindowsGuiEventDispatcher::sendPostedEvents()
{
    QEventDispatcherWin32::sendPostedEvents();
    QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}

bool QWindowSystemInterface::sendWindowSystemEvent 将循环遍历系统事件队列,并发送


bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{
    int nevents = 0;

    while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {
        QWindowSystemInterfacePrivate::WindowSystemEvent *event =
            (flags & QEventLoop::ExcludeUserInputEvents) ?
                QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :
// 1 从系统事件中获取之前存入的事件:队列 先进先出
                QWindowSystemInterfacePrivate::getWindowSystemEvent();
        if (!event)
            break;

        if (QWindowSystemInterfacePrivate::eventHandler) {
            if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event))
                nevents++;
        } else {
            nevents++;
            //2 gui 处理事件
            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);
}

//返回一个之前进入的事件 如MouseEvent
QWindowSystemInterfacePrivate::WindowSystemEvent * QWindowSystemInterfacePrivate::getWindowSystemEvent()
{
    return windowSystemEventQueue.takeFirstOrReturnNull();
}





void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{
    switch(e->type) {
    case QWindowSystemInterfacePrivate::FrameStrutMouse:
    case QWindowSystemInterfacePrivate::Mouse:
       //处理鼠标事件
    QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));
        break;
......
}

之后的传递过程不再展开,可以看堆栈:

 最终我们自定义的qttest 重写的leaveEvent收到鼠标离开事件

总结:

 windows消息处理机制:

1  注册窗口类(指定窗口函数),可以注册多个窗口类

2  进入消息循环形式如下:


首先取消息PeekMessage(&msg,0,0,0,PM_REMOVE); 从应用程序队列,也就是从主线程的消息队列中。 

接着转换消息并派发:
TranslateMessage(&msg);

DispatchMessage(&msg);

3  窗口函数就负责处理相应的消息。


二 qt中的消息处理机制:

      1 首先我们要关注的CApplication类,该类在初始化的时候会通过QWindowsClipboard::registerViewer()注册一个名字为“Qt5ClipboardView”窗口类,其中窗口函数为:qClipboardViewerWndProc,同时该类初始的调度器为QWindowsGuiEventDispatcher,该调度器也注册了一个窗口类,类名为"QEventDispatcherWin32_Internal_Widget1806862873",该类的窗口函数为:qt_internal_proc(这个是qt自定义事件的主要处理函数)

      2 如果我们开发的是wiget风格应用,还会初始化一个原生窗口类,该类也会注册一个窗口函数,类名为"Qt5QWindowIcon",窗口函数为qWindowsWndProc(这个函数处理的是用户操作引起的系统事件,比如鼠标事件,键盘事件,点击事件。。。,这些事件可能又会触发Qt定义的事件,此时这类事件会被qt_internal_proc函数处理)

      3 前面两步是注册了窗口函数,接着我们要看qt是如何进行消息派发的,在1中已经说到调度器为QWindowsGuiEventDispatcher,该类会调用:processEvents成员函数,调用父类的QEventDispatcherWin32::processEvents函数,在该函数中做了如下事情:

a 取消息

        来源有三个:

  1. 1)msg = d->queuedUserInputEvents.takeFirst();
  2. 2)msg = d->queuedSocketEvents.takeFirst();
  3. 3)haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);

这三类消息代表不同的来源,其中第三个是系统推送来的消息(包括系统消息和用户自定义消息)
b 如果没有消息的话,判断关注的句柄是否有触发的消息,句柄通过QEventDispatcherWin32::registerEventNotifier注册
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
c 通过ab两种监测消息的机制,若有消息就直接:
if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) 这函数是过滤
{
      TranslateMessage(&msg);
      DispatchMessage(&msg);
}
至此qt的消息循环机制就已经完成。
   4 回调函数,之前已经说过,由于注册了三个窗口类所以对应的应该有三个窗口函数,在此我分析其中两个关键的:
 1)LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp):该函数是只处理自定义消息的, 套接字读写消息WM_QT_SOCKETNOTIFIER,WM_QT_ACTIVATENOTIFIERS(对应套接字激活和失活状态)WM_QT_SENDPOSTEDEVENTS(异步消息),这三类消息都是qt自定义的,

        对于第一个消息,主要处理的是套接字读写事件,将套接字事件转化为QEvent事件然后通过QCoreApplication::sendEvent(sn->obj, &event)立即处理;

        第二类消息是指套接字注册成功或关闭时响应的消息,主要功能就是向函数WSAAsyncSelect(socket, internalHwnd, event ? int(WM_QT_SOCKETNOTIFIER) : 0, event)重新注册关注的事件;

        第三个消息是最应用最广的消息,比如当我们调用异步函数发送消息时会触发该类消息QCoreApplication::postEvent(),该消息被触发后会从当前线程的自定义队列中取消息执行;

2)extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam):该函数处理用户操作产生的消息,比如鼠标等,它做了以下事情:
1)const QtWindows::WindowsEventType et = windowsEventType(message, wParam, lParam);将windows消息转为qt事件
2)const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result);在qt事件分类中处理该类消息。有的消息就直接处理,有的消息会postWindowSystemEvent(ev)到消息队列中windowSystemEventQueue;同时发出WM_QT_SENDPOSTEDEVENTS消息以经过window消息循环回调第1)个函数处理,达到异步效果。

  至此qt消息循环应该已经算基本清楚了

同步事件原理:sendEvent 只能在本线程中使用

inline bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{  if (event) 
        event->spont = false; //设定事件为非系统自发事件
   return notifyInternal2(receiver, event); 
}


bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
    //1 检测是否可以获取当前线程内的线程数据
    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;

    //2 将接受者对象和事件对象放入指针数组  激活事件的回调函数
    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);

    //3 调用notify
    return self->notify(receiver, event);
}

/*! \reimp
 */
bool QApplication::notify(QObject *receiver, QEvent *e)
{
    Q_D(QApplication);
    // no events are delivered after ~QCoreApplication() has started
    // 1 检查是否关闭应用
    if (QApplicationPrivate::is_app_closing)
        return true;

    if (Q_UNLIKELY(!receiver)) {                        // serious error
        qWarning("QApplication::notify: Unexpected null receiver");
        return true;
    }

#ifndef QT_NO_DEBUG
    //2 检查是否处于同一个线程中
    d->checkReceiverThread(receiver);
#endif

   // 3 检查接受者是否是window 类型
    if (receiver->isWindowType())
        QGuiApplicationPrivate::sendQWindowEventToQPlatformWindow(static_cast<QWindow *>(receiver), e);

    //4 根据消息类型,进入相应的处理函数 s如鼠标事件
......
case QEvent::MouseMove:
        {
            QWidget* w = static_cast<QWidget *>(receiver);

            QMouseEvent* mouse = static_cast<QMouseEvent*>(e);
            QPoint relpos = mouse->pos();

            if (e->spontaneous()) {
                if (e->type() != QEvent::MouseMove)
                    QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, e, relpos);

                // ### Qt 5 These dynamic tool tips should be an OPT-IN feature. Some platforms
                // like OS X (probably others too), can optimize their views by not
                // dispatching mouse move events. We have attributes to control hover,
                // and mouse tracking, but as long as we are deciding to implement this
                // feature without choice of opting-in or out, you ALWAYS have to have
                // tracking enabled. Therefore, the other properties give a false sense of
                // performance enhancement.
                if (e->type() == QEvent::MouseMove && mouse->buttons() == 0
                    && w->rect().contains(relpos)) { // Outside due to mouse grab?
                    d->toolTipWidget = w;
                    d->toolTipPos = relpos;
                    d->toolTipGlobalPos = mouse->globalPos();
                    QStyle *s = d->toolTipWidget->style();
                    int wakeDelay = s->styleHint(QStyle::SH_ToolTip_WakeUpDelay, 0, d->toolTipWidget, 0);
                    d->toolTipWakeUp.start(d->toolTipFallAsleep.isActive() ? 20 : wakeDelay, this);
                }
            }

            bool eventAccepted = mouse->isAccepted();

            QPointer<QWidget> pw = w;
            while (w) {
                QMouseEvent me(mouse->type(), relpos, mouse->windowPos(), mouse->globalPos(),
                               mouse->button(), mouse->buttons(), mouse->modifiers(), mouse->source());
                me.spont = mouse->spontaneous();
                me.setTimestamp(mouse->timestamp());
                QGuiApplicationPrivate::setMouseEventFlags(&me, mouse->flags());
                // throw away any mouse-tracking-only mouse events
                if (!w->hasMouseTracking()
                    && mouse->type() == QEvent::MouseMove && mouse->buttons() == 0) {
                    // but still send them through all application event filters (normally done by notify_helper)
                    d->sendThroughApplicationEventFilters(w, w == receiver ? mouse : &me);
                    res = true;
                } else {
                    w->setAttribute(Qt::WA_NoMouseReplay, false);
                    // 5 调用通知接口

 /*
               * 将事件传递下去,进行处理,如果返回值res为真且事件被接收,或者到了顶层窗口,
               * 或者w窗口设置了Qt::WA_NoMousePropagation,则停止事件的传递,退出循环。
               * 完成对该事件的处理。否则,将事件转发给w的父窗口处理:
               * w = w->parentWidget();
*/
                    res = d->notify_helper(w, w == receiver ? mouse : &me);
                    e->spont = false;
                }
                eventAccepted = (w == receiver ? mouse : &me)->isAccepted();
                if (res && eventAccepted)
                    break;
                if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))
                    break;
                relpos += w->pos();
                w = w->parentWidget();
            }

            mouse->setAccepted(eventAccepted);

            if (e->type() == QEvent::MouseMove) {
                if (!pw)
                    break;

                w = static_cast<QWidget *>(receiver);
                relpos = mouse->pos();
                QPoint diff = relpos - w->mapFromGlobal(d->hoverGlobalPos);
                while (w) {
                    if (w->testAttribute(Qt::WA_Hover) &&
                        (!QApplication::activePopupWidget() || QApplication::activePopupWidget() == w->window())) {
                        QHoverEvent he(QEvent::HoverMove, relpos, relpos - diff, mouse->modifiers());
                        d->notify_helper(w, &he);
                    }
                    if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))
                        break;
                    relpos += w->pos();
                    w = w->parentWidget();
                }
            }

            d->hoverGlobalPos = mouse->globalPos();
        }
        break;
......

bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{ 
    /*
    * 首先将事件交由安装到QApplication上的事件过滤器进行处理。如果事件被过滤器处理并返回
    * true,则从这里直接返回true
    */
    // send to all application event filters
    if (sendThroughApplicationEventFilters(receiver, e))
        return true;
 
    if (receiver->isWidgetType()) {
        QWidget *widget = static_cast<QWidget *>(receiver);
      ...
 
     /*
     * 在事件交由receiver的event处理之前,先将它交给安装到receiver上的过滤器处理。如果事件
     * 被过滤器处理并返回true,则从这里直接返回true
     */   
    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, e))
        return true;
 
    /*
    * 最后,交给receiver的event进行处理,返回event的返回值
    */  
    // deliver the event
    bool consumed = receiver->event(e);
    QCoreApplicationPrivate::setEventSpontaneous(e, false);
    return consumed;
}

来到QWidget的event 函数,调用mousePressEvent函数。


bool QWidget::event(QEvent *event)
{
    Q_D(QWidget);

 ......
    case QEvent::MouseButtonPress:
        mousePressEvent((QMouseEvent*)event);
        break;
......

子类qttest重写了该函数,最后调用到:

总结:

  1.  sendEvent是根据事件类型发送事件进行处理,且不借助QEventLoop发送事件
  2. 事件发送者对象和事件接收者对象必须处于同一个线程
  3. 遵循父子对象的事件处理机制
  4. 是堵塞的事件发送类型,需要重写接收对象event的处理函数
  5. 事件发生后会立即进入QObject::event函数进行处理
  6. sendEvent中事件对象的生命周期由qt平台管理,支持局部对象和new分配,new事件处理后内部自动销毁
  7. sendEvent会立即同步要发送的event,当sendEvent返回的时候,表示对方已经完成了对事件的处理

异步事件原理 postEvent:支持不同的线程间使用

        


void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{

    //1 如果接收者为0 则删除事件
    if (receiver == 0) {
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    }

    //2 检查接收者的线程数据是否为空
    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;
    }



    //3 对postEvent存储容器进行加锁操作
    // lock the post event mutex
    data->postEventList.mutex.lock();


    //4 对接收者数据进行校验,防止接收者通过movetoThread 转移到其他线程
    // 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);

    //5 事件压缩操作:目的是保证接收者对象posteEvents队列中只有一个DeferredDelete事件和quit事件
    // if this is one of the compressible events, do compression
    if (receiver->d_func()->postedEvents
        && self && self->compressEvent(event, receiver, &data->postEventList)) {
        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;
    }


    //6 使用智能指针对象包裹事件对象 将事件添加到接收者对象的postEventList中
//原因:postEvent并不会立即执行,需要等到事件调度器下一次循环,执行事件后,清理
    // delete the event on exceptions to protect against memory leaks till the event is
    // properly owned in the postEventList
    QScopedPointer<QEvent> eventDeleter(event);
    data->postEventList.addEvent(QPostEvent(receiver, event, priority));
    eventDeleter.take();
    event->posted = true;//设置事件对象为post事件
    ++receiver->d_func()->postedEvents;
    data->canWait = false;
    locker.unlock();


    //7 获取当前接收者对象的事件调度器 并进行唤醒
    QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
    if (dispatcher)
        dispatcher->wakeUp();
}

 class QThreadData中定义了      QPostEventList postEventList;存放post事件,类型是class QPostEventList : public QVector<QPostEvent>

class QPostEvent
{
public:
    QObject *receiver;//接收者
    QEvent *event;//事件
    int priority;//优先级
    inline QPostEvent()
        : receiver(0), event(0), priority(0)
    { }
    inline QPostEvent(QObject *r, QEvent *e, int p)
        : receiver(r), event(e), priority(p)
    { }
};


        dispatcher->wakeUp();发送一个自定义消息:

  void QEventDispatcherWin32::wakeUp()    post 一个WM_QT_SENDPOSTEDEVENTS消息到本线程 ,PostMessage是windows原生接口

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
        //将消息发送到隐藏窗口internalHwnd 中
        PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
    }
}

之后:最开始创建的隐藏窗口的窗口过程回调收到该消息进行处理

LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
    if (message == WM_NCCREATE)
        return true;
.......
     //收到消息
    } else if (message == WM_QT_SENDPOSTEDEVENTS
               // we also use a Windows timer to send posted events when the message queue is full
               || (message == WM_TIMER
                   && d->sendPostedEventsWindowsTimerId != 0
                   && wp == (uint)d->sendPostedEventsWindowsTimerId)) {
        const int localSerialNumber = d->serialNumber.load();
        if (localSerialNumber != d->lastSerialNumber) {
            d->lastSerialNumber = localSerialNumber;
            q->sendPostedEvents();//执行
        }
        return 0;
    } else if (message == WM_TIMER) {
        Q_ASSERT(d != 0);
        d->sendTimerEvent(wp);
        return 0;
    }

    return DefWindowProc(hwnd, message, wp, lp);
}

sendPostedEvents

       sendPostedEvents是阻塞型事件,事件发送后,等到receive执行后才返回,并删除事件对象。

void QWindowsGuiEventDispatcher::sendPostedEvents()
{

    // 1 发送post事件
    QEventDispatcherWin32::sendPostedEvents();
    // 2 发送系统事件 上面讲鼠标事件是通过这个接口调用的
    QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}

        QEventDispatcherWin32::sendPostedEvents将Post到当前线程QThreadData::postEventList中的QPostEvent包装着的QEvent发送给QPostEvent**指定的对象**。

void QEventDispatcherWin32::sendPostedEvents()
{
    Q_D(QEventDispatcherWin32);
    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
}


void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
                                               QThreadData *data)
{
    if (event_type == -1) {
        // we were called by an obsolete event dispatcher.
        event_type = 0;
    }

    if (receiver && receiver->d_func()->threadData != data) {
        qWarning("QCoreApplication::sendPostedEvents: Cannot send "
                 "posted events for objects in another thread");
        return;
    }

    ++data->postEventList.recursion;

    QMutexLocker locker(&data->postEventList.mutex);

    // by default, we assume that the event dispatcher can go to sleep after
    // processing all events. if any new events are posted while we send
    // events, canWait will be set to false.
    //默认情况下,事件循环器在处理完事件后canWait = true 进入休眠状态,当发布postevent时,
 //需要将其设置为false,唤醒事件循环器
    data->canWait = (data->postEventList.size() == 0);

    if (data->postEventList.size() == 0 || (receiver && !receiver->d_func()->postedEvents)) {
        --data->postEventList.recursion;
        return;
    }

    data->canWait = true;

    // okay. here is the tricky loop. be careful about optimizing
    // this, it looks the way it does for good reasons.
    int startOffset = data->postEventList.startOffset;
    int &i = (!event_type && !receiver) ? data->postEventList.startOffset : startOffset;
    data->postEventList.insertionOffset = data->postEventList.size();

    // Exception-safe cleaning up without the need for a try/catch block
    struct CleanUp {
        QObject *receiver;
        int event_type;
        QThreadData *data;
        bool exceptionCaught;

        inline CleanUp(QObject *receiver, int event_type, QThreadData *data) :
            receiver(receiver), event_type(event_type), data(data), exceptionCaught(true)
        {}
        inline ~CleanUp()
        {
            if (exceptionCaught) {
                // since we were interrupted, we need another pass to make sure we clean everything up
                data->canWait = false;
            }

            --data->postEventList.recursion;
            if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher())
                data->eventDispatcher.load()->wakeUp();

            // clear the global list, i.e. remove everything that was
            // delivered.
            if (!event_type && !receiver && data->postEventList.startOffset >= 0) {
                const QPostEventList::iterator it = data->postEventList.begin();
                data->postEventList.erase(it, it + data->postEventList.startOffset);
                data->postEventList.insertionOffset -= data->postEventList.startOffset;
                Q_ASSERT(data->postEventList.insertionOffset >= 0);
                data->postEventList.startOffset = 0;
            }
        }
    };
    CleanUp cleanup(receiver, event_type, data);

   //循环遍历PostEventList
    while (i < data->postEventList.size()) {
        // avoid live-lock
        if (i >= data->postEventList.insertionOffset)
            break;

        const QPostEvent &pe = data->postEventList.at(i);
        ++i;

        if (!pe.event)
            continue;
        if ((receiver && receiver != pe.receiver) || (event_type && event_type != pe.event->type())) {
            data->canWait = false;
            continue;
        }

        if (pe.event->type() == QEvent::DeferredDelete) {
            // DeferredDelete events are sent either
            // 1) when the event loop that posted the event has returned; or
            // 2) if explicitly requested (with QEvent::DeferredDelete) for
            //    events posted by the current event loop; or
            // 3) if the event was posted before the outermost event loop.

            int eventLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel();
            int loopLevel = data->loopLevel + data->scopeLevel;
            const bool allowDeferredDelete =
                (eventLevel > loopLevel
                 || (!eventLevel && loopLevel > 0)
                 || (event_type == QEvent::DeferredDelete
                     && eventLevel == loopLevel));
            if (!allowDeferredDelete) {
                // cannot send deferred delete
                if (!event_type && !receiver) {
                    // we must copy it first; we want to re-post the event
                    // with the event pointer intact, but we can't delay
                    // nulling the event ptr until after re-posting, as
                    // addEvent may invalidate pe.
                    QPostEvent pe_copy = pe;

                    // null out the event so if sendPostedEvents recurses, it
                    // will ignore this one, as it's been re-posted.
                    const_cast<QPostEvent &>(pe).event = 0;

                    // re-post the copied event so it isn't lost
                    data->postEventList.addEvent(pe_copy);
                }
                continue;
            }
        }

        // first, we diddle the event so that we can deliver
        // it, and that no one will try to touch it later.
        pe.event->posted = false;
        QEvent *e = pe.event;
        QObject * r = pe.receiver;

        --r->d_func()->postedEvents;
        Q_ASSERT(r->d_func()->postedEvents >= 0);

        // next, update the data structure so that we're ready
        // for the next event.
        const_cast<QPostEvent &>(pe).event = 0;

        struct MutexUnlocker
        {
            QMutexLocker &m;
            MutexUnlocker(QMutexLocker &m) : m(m) { m.unlock(); }
            ~MutexUnlocker() { m.relock(); }
        };
        MutexUnlocker unlocker(locker);

        QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)

        // after all that work, it's time to deliver the event.
        //sendEvent发送e   到receive
        QCoreApplication::sendEvent(r, e);

        // careful when adding anything below this point - the
        // sendEvent() call might invalidate any invariants this
        // function depends on.
    }

    cleanup.exceptionCaught = false;
}

sendPostedEvents 在接收者线程中 遍历postEventList,将事件通过sendEvent(r,e);发送到具体的接收者对象,下面的过程和上面鼠标点击的调用过程相同,最后qttest收到mousePressEvent事件

 总结:

postEvent和sendEvent区别

(1)

        qt事件循环需要维护一个事件队列,在Qt的main函数中最后一般调用QApplication::exec()成员函数来保持程序对事件队列的处理,exec()的实质是不停调用processEvent()函数从队列中获取事件,并处理,然后删除,postEvent的作用就是发送一个事件到此队列中,由于删除队列中事件调用delete运算符,所以,postEvent()传递的事件一定要是动态分配的。

        sendEvent()函数直接跳过事件循环队列,直接调用notify()函数发送事件到目标对象,并等待事件处理结果,所以其传递的事件直接在栈上分配即可,也可以通过堆空间,(postEvent()函数只负责添加事件到队列,不等待事件处理结果)。

(2) sendEvent是同步阻塞型事件发送,postEvent是异步非阻塞型事件发送

思考:在QWidget(顶层窗口)中放个QPushButton,QPushButton是如果收到鼠标点击事件的?

        首先,顶层窗口一定是一个QWindow ,它拥有一个windows native原生窗口来接收系统事件,因此事件首先被QWidget接收到,

Native widget or Alien widget


        Native widget是指拥有windows窗口句柄的widget,占用了内核资源,Alien widget是指依附在某个Native widget上的子窗口,没有windows窗口句柄,不占用内核资源。在qt4.4之前,所有的widget都是Native widget,而且伴随着被人诟病的闪烁现象。qt4.4之后,使用了Alien widget,子窗口默认是一个Alien widget,除非必要,qt不会创建一个Native widget。qt想尽可能地把对窗口的处理从内核转移到qt上,从而拥有更大的自主权。widget的Alien widget和Native widget属性是可以配置的,如果你确实需要一个Native widget,你可以对属性进行显示的配置

setAttribute(Qt::WA_NativeWindow);


创建Native widget


        如果一个窗口是Alien widget,没有窗口句柄,那他怎么得到windows的消息呢?不能,可以肯定的是一个没有窗口句柄的Alien widget是无法被操作系统感知的。所以,在windows之上,qt建立了自己的窗口系统。然而,qt程序逃避不了的一个事实是:它需要获取windows的消息。要获取操作系统消息的前提是拥有窗口句柄,所以,qt的窗口程序一般是这样的:

        一个Native widget作为顶层窗口,一些Alien widget窗口作为顶层窗口的后代,依附其上。
这样形式的qt程序,对于操作系统来说,它只看到了Native widget,所以它就认为消息是传递给这个Native widget的;对于qt的窗口系统来说,它看到的是依附于Native widget上绘制出来的数目众多的Alien widget,qt内部再确定该事件正真的目的地是哪个窗口。

对,没毛病,windows的观点没错,qt的观点就更对了。

创建一个Native widget就意味着需要注册一个窗口类或者使用同名的已经注册过的窗口类,然后创建一个内核窗口对象,并返回窗口句柄。

QWidget注册窗口类时使用的窗口过程为qWindowsWndProc:
 

extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT result;
    const QtWindows::WindowsEventType et = windowsEventType(message, wParam, lParam);
 
    const bool handled = 
      QWindowsContext::instance()->windowsProc(hwnd, message, et, 
                                               wParam, lParam, &result); 
...
    //qt不感兴趣的消息,直接交由DefWindowProc处理
    if (!handled)
        result = DefWindowProc(hwnd, message, wParam, lParam);
    return result;
}

        例如,当你点击窗口时,windows感知到了用户对native widget的点击操作,并将消息发送给native widget所在的线程的消息队列,在qt的消息循环中能够GetMessage获取消息,qt使用GetMessage的非阻塞版PeekMessage获取线程队列中的消息,然后通过DispatchMessage将消息转给Native widget的这个窗口过程qWindowsWndProc。

        窗口过程将消息映射到系统事件类型WindowsEventType,case各种类型,最后将封装好的WindowSystemEvent格式的系统事件放入QWindowSystemInterfacePrivate的全局系统事件队列中。

        对,一般都是将事件放入windowSystemEventQueue队列中排队。然后就一步步的返回了,最后从DispatchMessage回到事件循环。

        如果遇到qt不干兴趣的消息,他不会将其放入windowSystemEventQueue队列,而是从windowsProc返回false,交给DefWindowProc进行默认处理。
 

        整体过程了上面举例的QWidget接受到鼠标事件相同,仅仅是多了一步:因为QPushButton是Alien widget,它的鼠标事件首先被native  widget窗口收到,native  widget窗口调用pickMouseReceiver来判断鼠标事件是在哪个子窗口中

C:\Qt\5.9.8\Src\qtbase\src\widgets\kernel\qapplication.cpp 

void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
{
......
    //QWidget收到信号后,调用childAt判断鼠标位置在哪个子对象中
    // which child should have it?
    QWidget *widget = m_widget->childAt(event->pos());
    QPoint mapped = event->pos();

    if (!widget)
        widget = m_widget;

    if (event->type() == QEvent::MouseButtonPress)
        qt_button_down = widget;

    //判断真正的 receiver 该接口通过判断,确定鼠标信号应该给哪个控件
    QWidget *receiver = QApplicationPrivate::pickMouseReceiver(m_widget, event->windowPos().toPoint(), &mapped, event->type(), event->buttons(),
                                                               qt_button_down, widget);

......
}

void QWidgetWindow::handleMouseEvent(QMouseEvent *event)处理鼠标事件的最终receiver后

调用 QApplicationPrivate::sendMouseEvent 发送     

        QApplication::sendSpontaneousEvent(receiver, event);

                 notifyInternal2(receiver, event);

                        self->notify(receiver, event);

该通过与sendEvent类型

最终,走到  QPushButton event事件接口,收到鼠标事件

 测试代码:


class CustomBtn :public QPushButton {

public:
	CustomBtn(QWidget* parent) :QPushButton(parent) {}

	void mousePressEvent(QMouseEvent *)override {
	
	}

};


qttest::qttest(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);

	connect(this,&qttest::signal_test1,this,&qttest::slot_test1);
	connect(this, SIGNAL(signal_test1()) , this, SLOT(slot_test1()));
	connect(this, &qttest::signal_test1, [this]() {slot_test1(); });
	signal_test1();
	signal_test2(2);

	QPushButton  * btn = new CustomBtn(this);

	QMouseEvent *ev;
	ev = new QMouseEvent(QEvent::MouseButtonPress, QPoint(10,10), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
	//QApplication::postEvent(btn, ev);

}
void qttest::slot_test1() {

}
void qttest::slot_test2(int index) {


}

void qttest::mousePressEvent(QMouseEvent *) {

}

void qttest::leaveEvent(QEvent *event) {

}

断点:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

qt-事件循环系统 的相关文章

随机推荐

  • leetcode33. 搜索旋转排序数组

    整数数组 nums 按升序排列 数组中的值 互不相同 在传递给函数之前 nums 在预先未知的某个下标 k 0 lt k lt nums length 上进行了 旋转 使数组变为 nums k nums k 1 nums n 1 nums
  • 【Python编程】如何在 Jupyter Notebook 中切换虚拟环境

    如何在 Jupyter Notebook 中切换虚拟环境 一 操作步骤 1 首先切换到想要在 Jupyter Notebook 里使用的虚拟环境 conda activate 环境名称 2 安装 ipykernel conda instal
  • TCP详解(一)服务和首部介绍

    文章目录 引言 TCP的服务 TCP首部 引言 TCP全称传输控制协议 Transmission Control Protocol 它是一种传输层通信协议 提供了面向连接的 可靠的字节流服务 本文将会简要介绍TCP为应用层提供的服务以及TC
  • node基础之三:http 模块

    1 导入模块 const http require http 2 创建服务 const server http createServer request response gt 获取请求方法 request method 获取请求 url
  • 阿里云1核1G内存1M宽带可以支持多少IP访问量?

    阿里云1核CPU 1G内存 1M公网宽带云服务器够用吗 1M宽带可以支持多少IP的访问量 来说说1M宽带可以跑多少流量及1核1G服务器配置性能 1核 1G 1M宽带配置能跑多少IP 一般来讲 如果图片不多 每天3000PV是没问题的 如果将
  • 优秀的CobaltStrike插件推荐:编程

    优秀的CobaltStrike插件推荐 编程 CobaltStrike是一款功能强大的渗透测试工具 广泛应用于红队行动和网络安全评估 它的灵活性和可扩展性使得开发者可以编写自己的插件来增强其功能 在这篇文章中 我将向你推荐一些好用的Coba
  • java并发包:重入锁与Condition条件

    本文转载至 http blog csdn net a910626 article details 51900941 重入锁 这里介绍一下synchronized wait notify方法的替代品 或者说是增强版 重入锁 重入锁是可以完全替
  • redis--13--Jedis使用

    redis 13 Jedis使用 代码位置 https gitee com DanShenGuiZu learnDemo tree master redis learn jedis 1 redis conf 修改 允许远程连接 bind 1
  • Java异常-Exception

    一 异常介绍 基本概念 Java语言中 将程序执行中发生的不正常情况称为 异常 注 开发过程中的语法错误和逻辑错误不是异常 执行过程中所发生的异常事件可分为两大类 Error 错误 Java虚拟机无法解决的严重问题 如 JVM系统内部错误
  • 2023-05-18 题目

    2023 05 18 题目 1 String 字符串 String 不是基本数据类型 且是不能被继承的 因为string类被final修饰 源码 public final class String implements java io Se
  • [FreeRTOS入门学习笔记]定时器

    定时器的使用步骤 1 定义一个handle xTimerCreate创建 2 启动定时器 在Task1中调用 通过队列通知守护任务来执行定时器任务 要再config头文件中定义守护任务相关配置 虽然定时器是在task1中启动 但是定时器的任
  • qt实现opengl播放yuv视频

    qt使用opengl播放yuv视频 文章目录 qt使用opengl播放yuv视频 toc 1 实现效果 2 pro文件 3 xvideowidget h 4 xvideowidget cpp 更多精彩内容 个人内容分类汇总 1 实现效果 2
  • VS2022编译GDAL库报错: fatal error U1050: PROJ_INCLUDE should be defined. PROJ >= 6 is a required depende

    目录 场景复现 定位问题 解决方案 踩过的坑 场景复现 使用VS2022的Native Tools command prompt for 2022工具编译GDAL库时 报 fatal error U1050 PROJ INCLUDE sho
  • RTSP视频边缘计算网关EasyNVR在5G时代有什么运用价值?

    5G和互联网的发展在近几年一直被按下了加速键 物联网正在成为主流 毋庸置疑 云计算为越来越多智能设备的连接提供了基础 给我们生活带来了极大便利 而边缘计算是云计算物联当中的一个关键应用 当我们在考虑云计算带来的数据过度集中 信息传输堵塞问题
  • 2018年最好用的5个python网站开发框架

    python作为解释型脚本语言 是一种通用的编程语言 由于python社区拥有大量的库文件 框架和其他的一些实用工具 我们可以用python完成各种各样的任务 另外 由于python的代码构成和结构就像英语句子一样自然 这种语言的学习曲线也
  • Spring(三)-IOC使用

    目录 基于XML管理bean 入门案例 引入依赖 创建类HelloWorld 创建Spring的配置文件 在Spring的配置文件中配置bean 创建测试类测试 思路 获取bean 方式一 根据id获取 方式二 根据类型获取 方式三 根据i
  • 延迟渲染到最终结果------1,2,分配渲染目标和初始化窗口(大象无形11.3.1)

    版本不同 我这里延迟渲染是FDeferredShadingSceneRenderer类 即函数 void FDeferredShadingSceneRenderer Render FRHICommandListImmediate RHICm
  • 经过两年努力,我终于进入腾讯(PCG事业群4面总结)

    前言 为什么要尽量让自己进大厂 如果毕业就进了大厂 那你将得到业内大牛的指导 以及随处可见的技术碰撞 新技术的跟进也是非常快的 在这样的环境中 你的技术成长自然是非常快的 如果自己足够努力 用不了三年 你可能也将会跟他们水平差不多 所以 明
  • c语言编译过程

    C语言的编译过程一般分为四个步骤 预处理 编译 汇编和链接 预处理 Preprocessing 预处理器会处理源代码中以 开头的预处理指令 例如 include和 define等 将它们替换为相应的内容 同时 还会删除注释和空格 将多行代码
  • qt-事件循环系统

    Qt中 如果创建的console程序 使用的是QCoreApplication对象 如果创建的是GUI程序 使用的是QApplication对象 而QApplication 继承自 QGUIApplication 最终继承QCoreAppl