1. 程式人生 > >qt 訊息處理機制深入分析 (Qt訊息機制與window程式訊息的對比)

qt 訊息處理機制深入分析 (Qt訊息機制與window程式訊息的對比)

理解Qt訊息機制刻不容緩,那我們從對比傳統的windows訊息處理機制對比來說起;
只有知道QT底層的訊息處理、對我們理解並學習Qt有很大幫助;
下面我將對windows程式與Qt對比,並在核心程式碼處並給出註釋進行對比、方便學習。

注意重點看程式碼中的注視進行對比:!

注意重點看程式碼中的注視進行對比:!

注意重點看程式碼中的注視進行對比:!


一:windows程式的訊息處理 
windows程式的處理大概一致

如下:

1.0 windows 訊息處理機制:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Hello");
    HWND   hwnd;
    MSG    msg;
    WNDCLASS wndclass;
    //fill wndclass
    wndclass.lpfnWndProc  = WndProc;
    ...
    RegisterClass(&wndclass);
    hwnd = CreateWindow( .... );      // creation parameters
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);
    while(GetMessage(&msg, NULL, 0, 0))  { //這一塊位置得到訊息
        TranslateMessage(&msg);//轉換訊息
        DispatchMessage(&msg);//分發訊息到系統處理
    }
    return msg.wParam;
}

1.1 這是分發訊息的回撥函式熟悉windows程式應該不難看懂

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;
    switch(message) {
        case WM_CREATE:
            return 0;
        case WM_PAINT://重繪、比如視窗大小拉伸
            ...
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam); // windows 作業系統內部的訊息機制、系統呼叫函式;
}

二 Qt訊息處理機制

2.0 Qt的訊息機制


QEventDispatcherWin32:
註冊視窗類別,並建立一個隱藏視窗 (QEventDispatcherWin32_Internal_WidgetXXXX)
視窗的回撥函式 qt_internal_proc()
安裝WH_GETMESSAGE型別的鉤子函式 qt_GetMessageHook()

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) 
if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0))  與上面的訊息迴圈:的while一樣 、得到過濾所有訊息
{
	TranslateMessage(&msg);//轉換訊息
	DispatchMessage(&msg); //分發訊息
}

DispatchMessage(&msg); //分發訊息
分發訊息的或回撥函式、這個與windows程式的CALLBACK WndProc一樣
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)

2.1 下面就是從系統獲得的訊息後Qt封裝訊息後所作的事情

這個就是Qt的訊息回撥:

LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
    if (message == WM_NCCREATE)
        return true;
	// MSG windows 訊息結構:
    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();
	// 下面 WM_QT_SOCKETNOTIFIER socket Qt 事件底層的處理機制、處理網路的訊息事件
    if (message == WM_QT_SOCKETNOTIFIER) {
        // socket notifier message
        int type = -1;
        switch (WSAGETSELECTEVENT(lp)) { //在非阻塞模式下利用socket事件的訊息機制,Server端與Client端之間的通訊處於非同步狀態下
        case FD_READ: //socket 檔案描述符 read 、有資料到達時發生
        case FD_ACCEPT: //socket 檔案描述符 接收連線 、 作為客戶端連線成功時發生
            type = 0;
            break;
        case FD_WRITE: //socket 檔案描述符寫 、有資料傳送時產生
        case FD_CONNECT:  //socket 檔案描述符發起連線 、 作為服務端等待連線成功時發生	
            type = 1;
            break;
        case FD_OOB: //socket 檔案描述符收到資料 、 收到外帶資料時發生
            type = 2;
            break;
        case FD_CLOSE: //socket 檔案描述符關閉斷開連線 、套介面關閉時發生
            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) { // 處理postEvent事件
        Q_ASSERT(d != 0);
		// postEvent() 事件,因為是佇列的儲存的方式,郵遞傳送訊息、直接返回
		
        // 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()) { // d->queuedSocketEvents 訊息佇列如果訊息有很多的時候都會加入這個佇列、這個就是關於Qt::connect();引數:地址
            // 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  // 處理 Qt sendPostEvent()傳送事件
               // we also use a Windows timer to send posted events when the message queue is full
			   // WM_QT_SENDPOSTEDEVENTS : 這個訊息是我們Qt程式大部分走的事件,特殊情況除外。
               || (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();//因為sendevent是同步所以直接進入進行呼叫
        }
        return 0;
    } else if (message == WM_TIMER) {//系統定時器超時
        Q_ASSERT(d != 0);
        d->sendTimerEvent(wp);
        return 0;
    }

    return DefWindowProc(hwnd, message, wp, lp); // 這個與windows程式一樣的地方。
}

另外還有一點很重要的:大家都知道Qt訊息處理比windows處理的塊,Qt程式通過一通處理才呼叫DefWindowProc傳給系統所以Qt系統的訊息機制是比windows的訊息慢的原因之一;
不過他也有它優點:qt的訊號與槽函式讓我們讓我們程式設計更方便。

2.3 訊息全域性通知事件

另外我們都知道我們開發的系統、所有的訊息都會到這個類QApplication::notify事件過濾所有的事件:
其實QApplication繼承QGuiApplication類;

bool QGuiApplication::notify(QObject *object, QEvent *event)
{
    if (object->isWindowType())
        QGuiApplicationPrivate::sendQWindowEventToQPlatformWindow(static_cast<QWindow *>(object), event);
    return QCoreApplication::notify(object, event);
}

2.4 訊息的組裝

下面是所有的訊息型別處理:在我們開發的系統中別人使用processEvent傳送訊息是比較高效的。
原因:這個訊息直達經過的處理的少。

void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{
    switch(e->type) {
    case QWindowSystemInterfacePrivate::FrameStrutMouse:
    case QWindowSystemInterfacePrivate::Mouse:
        QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Wheel:
        QGuiApplicationPrivate::processWheelEvent(static_cast<QWindowSystemInterfacePrivate::WheelEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Key:
        QGuiApplicationPrivate::processKeyEvent(static_cast<QWindowSystemInterfacePrivate::KeyEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Touch:
        QGuiApplicationPrivate::processTouchEvent(static_cast<QWindowSystemInterfacePrivate::TouchEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::GeometryChange:
        QGuiApplicationPrivate::processGeometryChangeEvent(static_cast<QWindowSystemInterfacePrivate::GeometryChangeEvent*>(e));
        break;
    case QWindowSystemInterfacePrivate::Enter:
        QGuiApplicationPrivate::processEnterEvent(static_cast<QWindowSystemInterfacePrivate::EnterEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Leave:
        QGuiApplicationPrivate::processLeaveEvent(static_cast<QWindowSystemInterfacePrivate::LeaveEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ActivatedWindow:
        QGuiApplicationPrivate::processActivatedEvent(static_cast<QWindowSystemInterfacePrivate::ActivatedWindowEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::WindowStateChanged:
        QGuiApplicationPrivate::processWindowStateChangedEvent(static_cast<QWindowSystemInterfacePrivate::WindowStateChangedEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::WindowScreenChanged:
        QGuiApplicationPrivate::processWindowScreenChangedEvent(static_cast<QWindowSystemInterfacePrivate::WindowScreenChangedEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ApplicationStateChanged: {
        QWindowSystemInterfacePrivate::ApplicationStateChangedEvent * changeEvent = static_cast<QWindowSystemInterfacePrivate::ApplicationStateChangedEvent *>(e);
        QGuiApplicationPrivate::setApplicationState(changeEvent->newState, changeEvent->forcePropagate); }
        break;
    case QWindowSystemInterfacePrivate::FlushEvents: {
        QWindowSystemInterfacePrivate::FlushEventsEvent *flushEventsEvent = static_cast<QWindowSystemInterfacePrivate::FlushEventsEvent *>(e);
        QWindowSystemInterface::deferredFlushWindowSystemEvents(flushEventsEvent->flags); }
        break;
    case QWindowSystemInterfacePrivate::Close:
        QGuiApplicationPrivate::processCloseEvent(
                static_cast<QWindowSystemInterfacePrivate::CloseEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ScreenOrientation:
        QGuiApplicationPrivate::reportScreenOrientationChange(
                static_cast<QWindowSystemInterfacePrivate::ScreenOrientationEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ScreenGeometry:
        QGuiApplicationPrivate::reportGeometryChange(
                static_cast<QWindowSystemInterfacePrivate::ScreenGeometryEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInch:
        QGuiApplicationPrivate::reportLogicalDotsPerInchChange(
                static_cast<QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInchEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ScreenRefreshRate:
        QGuiApplicationPrivate::reportRefreshRateChange(
                static_cast<QWindowSystemInterfacePrivate::ScreenRefreshRateEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ThemeChange:
        QGuiApplicationPrivate::processThemeChanged(
                    static_cast<QWindowSystemInterfacePrivate::ThemeChangeEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Expose:
        QGuiApplicationPrivate::processExposeEvent(static_cast<QWindowSystemInterfacePrivate::ExposeEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Tablet:
        QGuiApplicationPrivate::processTabletEvent(
                    static_cast<QWindowSystemInterfacePrivate::TabletEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::TabletEnterProximity:
        QGuiApplicationPrivate::processTabletEnterProximityEvent(
                    static_cast<QWindowSystemInterfacePrivate::TabletEnterProximityEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::TabletLeaveProximity:
        QGuiApplicationPrivate::processTabletLeaveProximityEvent(
                    static_cast<QWindowSystemInterfacePrivate::TabletLeaveProximityEvent *>(e));
        break;
#ifndef QT_NO_GESTURES
    case QWindowSystemInterfacePrivate::Gesture:
        QGuiApplicationPrivate::processGestureEvent(
                    static_cast<QWindowSystemInterfacePrivate::GestureEvent *>(e));
        break;
#endif
    case QWindowSystemInterfacePrivate::PlatformPanel:
        QGuiApplicationPrivate::processPlatformPanelEvent(
                    static_cast<QWindowSystemInterfacePrivate::PlatformPanelEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::FileOpen:
        QGuiApplicationPrivate::processFileOpenEvent(
                    static_cast<QWindowSystemInterfacePrivate::FileOpenEvent *>(e));
        break;
#ifndef QT_NO_CONTEXTMENU
        case QWindowSystemInterfacePrivate::ContextMenu:
        QGuiApplicationPrivate::processContextMenuEvent(
                    static_cast<QWindowSystemInterfacePrivate::ContextMenuEvent *>(e));
        break;
#endif
    case QWindowSystemInterfacePrivate::EnterWhatsThisMode:
        QGuiApplication::postEvent(QGuiApplication::instance(), new QEvent(QEvent::EnterWhatsThisMode));
        break;
    default:
        qWarning() << "Unknown user input event type:" << e->type;
        break;
    }
}

2.5 常用的訊息過濾事件

通過2.4組裝的訊息最終到達、此處。經過這些之後:之前的windows訊息現在都已對映為QT型別的訊息、進入Qt訊息處理機制中來、才有了我們的訊息過濾等等訊息事件鍵盤滑鼠等等;

virtual void mouseDoubleClickEvent(QMouseEvent *event)
virtual void mouseMoveEvent(QMouseEvent *event)
virtual void mousePressEvent(QMouseEvent *event)
virtual void mouseReleaseEvent(QMouseEvent *event)
virtual void moveEvent(QMoveEvent *event)
virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result)
virtual void paintEvent(QPaintEvent *event)
virtual void resizeEvent(QResizeEvent *event)
virtual void showEvent(QShowEvent *event)
virtual void tabletEvent(QTabletEvent *event)
virtual void wheelEvent(QWheelEvent *event)

重新實現上面的訊息事件做我們的功能;

總結:

通過windows訊息的執行機制與Qt的訊息機制進行了對比。其實Qt在windows平臺上的訊息傳遞依賴的是windows訊息機制。只不過在開始時進行註冊訊息攔截等一些封裝處理、加工成Qt訊息在Qt程式中進行傳遞