信息技术做网站,seo百度快照优化公司,wordpress如何设置用户登录,华汇建设集团有限公司网站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实现。
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;
}
三、总结
依据上述对QEventLoop的实现分析大体上可有以下结论
1. QEventLoop内部实际上是通过QAbstractEventDispatcher子类来完成了消息(事件)分发这实际上就是GoFs Bridge Pattern。
2. QAbstractEventDispatcher定义了事件分发的接口而针对具体窗口系统的实现则有QEventDispatcherWin32、QEventDispatcherWinRT、QEventDispatcherUNIX、QEventDispatcherGlib等负责。
3. 每个线程都有唯一的同一事件分发器线程内的窗体共用同一事件分发器。
4. 在Windows系统下QEventLoop::processEvents函数大体等价于PeekMessage/TranslateMessage/DispatchMessage。 参考文献
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