佛山网站开发哪家专业,东莞网络营销推广软件,北京公司网站建设服务,直通车官网QEventLoop屏蔽了底层消息循环实现细节#xff0c;向上提供了与平台无关的消息/事件循环。
本文拟对Windows系统下QEventLoop的实现原理予以分析。 注1#xff1a;限于研究水平#xff0c;分析难免不当#xff0c;欢迎批评指正。
注2#xff1a;文章内容会不定期更新。 …QEventLoop屏蔽了底层消息循环实现细节向上提供了与平台无关的消息/事件循环。
本文拟对Windows系统下QEventLoop的实现原理予以分析。 注1限于研究水平分析难免不当欢迎批评指正。
注2文章内容会不定期更新。 一、研究素材Win32应用程序框架
在Win32应用程序中wWinMain是整个程序的入口点整个代码段主要包括窗口类注册、创建窗口、消息循环等。
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR lpCmdLine,_In_ int nCmdShow)
{UNREFERENCED_PARAMETER(hPrevInstance);UNREFERENCED_PARAMETER(lpCmdLine);// TODO: Place code here.// Initialize global stringsLoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);// Perform application initialization:if (!InitInstance (hInstance, nCmdShow)){return FALSE;}HACCEL hAccelTable LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));MSG msg;// Main message loop:while (GetMessage(msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)){TranslateMessage(msg);DispatchMessage(msg);}}return (int) msg.wParam;
}
MyRegisterClass用于注册创库类可以指定窗口样式、窗口过程函数等。
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{WNDCLASSEXW wcex;wcex.cbSize sizeof(WNDCLASSEX);wcex.style CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc WndProc;wcex.cbClsExtra 0;wcex.cbWndExtra 0;wcex.hInstance hInstance;wcex.hIcon LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));wcex.hCursor LoadCursor(nullptr, IDC_ARROW);wcex.hbrBackground (HBRUSH)(COLOR_WINDOW1);wcex.lpszMenuName MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);wcex.lpszClassName szWindowClass;wcex.hIconSm LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));return RegisterClassExW(wcex);
}
完成窗口类注册之后可以依据窗口类创建窗口实例
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{hInst hInstance; // Store instance handle in our global variableHWND hWnd CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE;
}
当完成窗口类注册之后便可以依据窗口类名称来创建窗口。在Windows系统下消息队列用于管理线程内窗口相关的消息同一线程内的窗口对象共享同一各消息队列。 The Message Loop For each thread that creates a window, the operating system creates a queue for window messages. This queue holds messages for all the windows that are created on that thread. MSG msg;// Main message loop:while (GetMessage(msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)){TranslateMessage(msg);DispatchMessage(msg);}}
当消息被投递到窗口时便会调用对应的窗口过程函数
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_COMMAND:{int wmId LOWORD(wParam);// Parse the menu selections:switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;case WM_PAINT:{PAINTSTRUCT ps;HDC hdc BeginPaint(hWnd, ps);// TODO: Add any drawing code that uses hdc here...EndPaint(hWnd, ps);}break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
} 二、QEventLoop实现原理
QEventLoop实际上是通过QAbstractEventDispatcher子类来屏蔽了底层窗口系统的消息循环。
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{Q_D(QEventLoop);if (!d-threadData-hasEventDispatcher())return false;return d-threadData-eventDispatcher.loadRelaxed()-processEvents(flags);
}
从中可以看出QAbstractEventDispatcher是线程级别的存在每个线程有唯一的QAbstractEventDispatcher实现线程内的所有QEventLoop共享同一个QAbstractEventDispatcher实现。 Ref. from QAbstractEventDispatcher An event dispatcher receives events from the window system and other sources. It then sends them to the QCoreApplication or QApplication instance for processing and delivery. QAbstractEventDispatcher provides fine-grained control over event delivery. 2.1 QEventDispatcherWin32的创建
实际上在Windows系统下当创建QCoreApplication时便会创建主线程相关的QEventDispatcherWin32而QEventDispatcherWin32正是QAbstractEventDispatcher的Windows系统实现。
void QCoreApplicationPrivate::init()
{// ...
#ifndef QT_NO_QOBJECT// use the event dispatcher created by the app programmer (if any)Q_ASSERT(!eventDispatcher);eventDispatcher threadData-eventDispatcher.loadRelaxed();// otherwise we create oneif (!eventDispatcher)createEventDispatcher();Q_ASSERT(eventDispatcher);if (!eventDispatcher-parent()) {eventDispatcher-moveToThread(threadData-thread.loadAcquire());eventDispatcher-setParent(q);}threadData-eventDispatcher eventDispatcher;eventDispatcherReady();
#endif// ...
}
void QCoreApplicationPrivate::createEventDispatcher()
{Q_Q(QCoreApplication);QThreadData *data QThreadData::current();Q_ASSERT(!data-hasEventDispatcher());eventDispatcher data-createEventDispatcher();eventDispatcher-setParent(q);
}
QAbstractEventDispatcher *QThreadData::createEventDispatcher()
{QAbstractEventDispatcher *ed QThreadPrivate::createEventDispatcher(this);eventDispatcher.storeRelease(ed);ed-startingUp();return ed;
}
QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *data)
{Q_UNUSED(data);
#ifndef Q_OS_WINRTreturn new QEventDispatcherWin32;
#elsereturn new QEventDispatcherWinRT;
#endif
}
2.2 QEventDispatcherWin32的实现
由前面的分析可知在Windows系统下QEventLoop实际上是使用QEventDispatcherWin32来完成消息循环。那QEventDispatcherWin32又是如何实现对Windows消息循环的封装呢
在QEventDispatcherWin32中注册了一个QEventDispatcherWin32_Internal_Widget窗口类而对应的窗口过程函数主要是提供了对定时器、套接字等消息处理。
QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext(): atom(0), className(0)
{// make sure that multiple Qts can coexist in the same processconst 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(%ls RegisterClass() failed, qUtf16Printable(qClassName));delete [] className;className 0;}
}
同时依据该窗口类创建了一个内部窗口
void QEventDispatcherWin32::createInternalHwnd()
{Q_D(QEventDispatcherWin32);if (d-internalHwnd)return;d-internalHwnd qt_create_internal_window(this);// setup GetMessage hook needed to drive our posted eventsd-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, %ls,errorCode, qUtf16Printable(qt_error_string(errorCode)));}// start all normal timersfor (int i 0; i d-timerVec.count(); i)d-registerTimer(d-timerVec.at(i));
}
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{QWindowsMessageWindowClassContext *ctx qWindowsMessageWindowClassContext();if (!ctx-atom)return 0;HWND wnd CreateWindow(ctx-className, // classnamectx-className, // window name0, // style0, 0, 0, 0, // geometryHWND_MESSAGE, // parent0, // menu handleGetModuleHandle(0), // application0); // windows creation data.if (!wnd) {qErrnoWarning(CreateWindow() for QEventDispatcherWin32 internal window failed);return 0;}#ifdef GWLP_USERDATASetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_castLONG_PTR(eventDispatcher));
#elseSetWindowLong(wnd, GWL_USERDATA, reinterpret_castLONG(eventDispatcher));
#endifreturn wnd;
}
在QEventDispatcherWin32::processEvents中会不断的调用PeekMessage检查消息队列然后调用TranslateMessage与DispatchMessage进行消息转发。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{Q_D(QEventDispatcherWin32);if (!d-internalHwnd) {createInternalHwnd();wakeUp(); // trigger a call to sendPostedEvents()}d-interrupt.storeRelaxed(false);emit awake();// To prevent livelocks, send posted events once per iteration.// QCoreApplication::sendPostedEvents() takes care about recursions.sendPostedEvents();bool canWait;bool retVal false;do {DWORD waitRet 0;DWORD nCount 0;HANDLE *pHandles nullptr;if (d-winEventNotifierActivatedEvent) {nCount 1;pHandles d-winEventNotifierActivatedEvent;}QVarLengthArrayMSG processedTimers;while (!d-interrupt.loadRelaxed()) {MSG msg;bool haveMessage;if (!(flags QEventLoop::ExcludeUserInputEvents) !d-queuedUserInputEvents.isEmpty()) {// process queued user input eventshaveMessage true;msg d-queuedUserInputEvents.takeFirst();} else if(!(flags QEventLoop::ExcludeSocketNotifiers) !d-queuedSocketEvents.isEmpty()) {// process queued socket eventshaveMessage true;msg d-queuedSocketEvents.takeFirst();} else {haveMessage PeekMessage(msg, 0, 0, 0, PM_REMOVE);if (haveMessage) {if (flags.testFlag(QEventLoop::ExcludeUserInputEvents) isUserInputMessage(msg.message)) {// queue user input events for later processingd-queuedUserInputEvents.append(msg);continue;}if ((flags QEventLoop::ExcludeSocketNotifiers) (msg.message WM_QT_SOCKETNOTIFIER msg.hwnd d-internalHwnd)) {// queue socket events for later processingd-queuedSocketEvents.append(msg);continue;}}}if (!haveMessage) {// no message - check for signalled objectswaitRet MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);if ((haveMessage (waitRet WAIT_OBJECT_0 nCount))) {// a new message has arrived, process itcontinue;}}if (haveMessage) {if (d-internalHwnd msg.hwnd msg.message WM_QT_SENDPOSTEDEVENTS) {// Set result to true, if the message was sent by wakeUp().if (msg.wParam WMWP_QT_FROMWAKEUP)retVal true;continue;}if (msg.message WM_TIMER) {// avoid live-lock by keeping track of the timers weve already sentbool 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) {activateEventNotifiers();} else {// nothing todo so breakbreak;}retVal true;}// still nothing - wait for message or signalled objectscanWait (!retVal !d-interrupt.loadRelaxed() (flags QEventLoop::WaitForMoreEvents));if (canWait) {emit aboutToBlock();waitRet MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);emit awake();if (waitRet - WAIT_OBJECT_0 nCount) {activateEventNotifiers();retVal true;}}} while (canWait);return retVal;
}
2.3 小结
依据上述对QEventLoop的实现分析大体上可有以下结论
1. QEventLoop内部实际上是通过QAbstractEventDispatcher子类来完成了消息(事件)分发这实际上就是GoFs Bridge Pattern。
2. QAbstractEventDispatcher定义了事件分发的接口而针对具体窗口系统的实现则有QEventDispatcherWin32、QEventDispatcherWinRT、QEventDispatcherUNIX、QEventDispatcherGlib等负责。
3. 每个线程都有唯一的同一事件分发器线程内的窗体共用同一事件分发器。
4. 在Windows系统下QEventLoop::processEvents函数大体等价于PeekMessage/TranslateMessage/DispatchMessage。 三、Qt中的事件路由
3.1 消息转换
在Qt中当窗体接受到底层窗口系统消息之后又是如何将这些平台相关的消息转换成Qt事件呢 Ref. from QEvent Qts main event loop (QCoreApplication::exec()) fetches native window system events from the event queue, translates them into QEvents, and sends the translated events to QObjects. In general, events come from the underlying window system (spontaneous() returns true), but it is also possible to manually send events using QCoreApplication::sendEvent() and QCoreApplication::postEvent() (spontaneous() returns false). 实际上对于QGuiApplication会基于QPA (Qt Platform Abstraction)来创建QPlatformIntegration进而创建特定的QAbstractEventDispatcher。 Ref. from Qt Platform Abstraction The Qt Platform Abstraction (QPA) is the platform abstraction layer for Qt 5 and replaces Qt for Embedded Linux and the platform ports from Qt 4. QPA plugins are implemented by subclassing various QPlatform* classes. There are several root classes, such as QPlatformIntegration and QPlatformWindow for window system integration and QPlatformTheme for deeper platform theming and integration. QStyle is not a part of QPA. void QGuiApplicationPrivate::createEventDispatcher()
{Q_ASSERT(!eventDispatcher);if (platform_integration 0)createPlatformIntegration();// The platform integration should not mess with the event dispatcherQ_ASSERT(!eventDispatcher);eventDispatcher platform_integration-createEventDispatcher();
}
对于Windows系统QGuiApplication会通过加载qwindows.dll插件来创建QWindowsIntegration对象。
QAbstractEventDispatcher * QWindowsIntegration::createEventDispatcher() const
{return new QWindowsGuiEventDispatcher;
}
也就是说在Windows系统下QGuiApplication实际上是通过QWindowsGuiEventDispatcher来实现对底层消息的调度。
void QWindowsGuiEventDispatcher::sendPostedEvents()
{QEventDispatcherWin32::sendPostedEvents();QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}
bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{int nevents 0;while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {QWindowSystemInterfacePrivate::WindowSystemEvent *event nullptr;if (QWindowSystemInterfacePrivate::platformFiltersEvents) {event QWindowSystemInterfacePrivate::getWindowSystemEvent();} else {event flags QEventLoop::ExcludeUserInputEvents ?QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :QWindowSystemInterfacePrivate::getWindowSystemEvent();}if (!event)break;if (QWindowSystemInterfacePrivate::eventHandler) {if (QWindowSystemInterfacePrivate::eventHandler-sendEvent(event))nevents;} else {nevents;QGuiApplicationPrivate::processWindowSystemEvent(event);}// Record the accepted state for the processed event// (excluding flush events). This state can then be// returned by flushWindowSystemEvents().if (event-type ! QWindowSystemInterfacePrivate::FlushEvents)QWindowSystemInterfacePrivate::eventAccepted.storeRelaxed(event-eventAccepted);delete event;}return (nevents 0);
}
对于来自底层窗口系统的鼠标消息可以看到QWindowsGuiEventDispatcher最终是通过调用QGuiApplicationPrivate::processMouseEvent
void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{Q_TRACE_SCOPE(QGuiApplicationPrivate_processWindowSystemEvent, e-type);switch(e-type) {case QWindowSystemInterfacePrivate::Mouse:QGuiApplicationPrivate::processMouseEvent(static_castQWindowSystemInterfacePrivate::MouseEvent *(e));break;// ...default:qWarning() Unknown user input event type: e-type;break;}
}
而正是在这个函数中实现了底层窗口消息到Qt事件的转换。
void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent *e)
{// ...QMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e-buttons, e-modifiers, e-source);ev.setTimestamp(e-timestamp);if (window-d_func()-blockedByModalWindow !qApp-d_func()-popupActive()) {// a modal window is blocking this window, dont allow mouse events throughreturn;}if (doubleClick (ev.type() QEvent::MouseButtonPress)) {// QtBUG-25831, used to suppress delivery in qwidgetwindow.cppsetMouseEventFlags(ev, ev.flags() | Qt::MouseEventCreatedDoubleClick);}QGuiApplication::sendSpontaneousEvent(window, ev);// ...
}
以上代码分析也可通过在Qt应用程序设置断点观察函数堆栈调用来进一步佐证。 Message from the underlying window system 3.2 事件处理
当底层窗口消息转换成Qt事件之后最终会由QCoreApplication进行事件路由。
bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{// no events are delivered after ~QCoreApplication() has startedif (QCoreApplicationPrivate::is_app_closing)return true;return doNotify(receiver, event);
}static bool doNotify(QObject *receiver, QEvent *event)
{if (receiver 0) { // serious errorqWarning(QCoreApplication::notify: Unexpected null receiver);return true;}#ifndef QT_NO_DEBUGQCoreApplicationPrivate::checkReceiverThread(receiver);
#endifreturn receiver-isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{// Note: when adjusting the tracepoints in here// consider adjusting QApplicationPrivate::notify_helper too.Q_TRACE(QCoreApplication_notify_entry, receiver, event, event-type());bool consumed false;bool filtered false;Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);// send to all application event filters (only does anything in the main thread)if (QCoreApplication::self receiver-d_func()-threadData-thread.loadAcquire() mainThread() QCoreApplication::self-d_func()-sendThroughApplicationEventFilters(receiver, event)) {filtered true;return filtered;}// send to all receiver event filtersif (sendThroughObjectEventFilters(receiver, event)) {filtered true;return filtered;}// deliver the eventconsumed receiver-event(event);return consumed;
}
从中可以看出Qt事件首先分别让应用程序过滤器、对象过滤器进行处理最后再交由虚函数QObject::event(QEvent *e)进行处理。
对于QWidgetQWidget::event(QEvent *e)实际上就是通过调用mousePressEvent(QMouseEvent *event) 、mouseReleaseEvent(QMouseEvent *event)等虚函数来处理各种具体的事件。
3.3 小结
基于上述分析可有以下结论
1. Qt基于QPA实现了跨窗口系统。QPA实际上是一种插件系统。
2. 在Windows系统下QGuiApplication创建QWindowsIntegration。
3. QCoreApplication使用QEventDispatcherWin32进行消息调度
4. QWindowsIntegration使用QWindowsGuiEventDispatcher进行消息调度。
5. QEvent先有系统级的filter处理再有对象级的filter处理最后交由目标对象处理。 四、扩展Qt插件系统 4.1 动态插件 4.2 静态插件 参考文献
Erich Gamma. Design Patterns:elements of reusable object-oriented software. Addison Wesley, 1994.
Joseph Ingeno. Handbook of Software Architecture. 参考资料
QEventLoop
Get Started with Win32 and C
QAbstractEventDispatcher
QThread
QCoreApplication