额,还是从一个window程序的基本结构看起吧
注册窗口类别 RegisterClass
创建窗口 CreateWindow
启动由GetMessage和DispatchMessage构成的事件循环
被注册的回调函数 WndProc 负责相应各类事件
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 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; } 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为当前执行的每个Windows程式维护一个消息队列,通过PostMessage、PostThreadMessage可以把消息放入队列,在事件循环中通过GetMessage、PeekMessage可以或者队列中的消息并可通过DispatchMessage将消息派发到相应的窗口回调函数。
消息队列为空时,GetMessage 会阻塞,而PeekMessage不会。现在一般都使用后者
SendMessage发送消息直接到窗口回调函数,不进入消息队列
WH_GETMESSAGE类型的钩子可以截获GetMessage和PeekMessage的消息
截获SendMessage发送的消息,需要WH_CALLWNDPROC或WH_CALLWNDPROCRET类型的钩子
安装WH_GETMESSAGE类型的钩子函数 qt_GetMessageHook()
这3个比较容易和前面的对应上。但是... 该怎么叙述呢...
Qt中的事件循环式通过 QEventLoop::exec() 来启动的。(它通常以QCoreApplication::exec()、QDialog::exec()等身份出现)
在 QEventLoop::exec() 内,是一个while 循环。
int QEventLoop::exec(ProcessEventsFlags flags) { while (!d->exit) //...
该循环内,调用的就是 QEventDispatcherWin32::processEvents() 函数
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) { ... haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE); ... TranslateMessage(&msg); DispatchMessage(&msg); ...
这样一来,消息被派发到窗口回调函数中。进而可以被
钩子 qt_GetMessageHook()
处理
Qt事件队列以及程序的消息队列是否有非空
processEvents() 可能会处于一个阻塞状态,比如调用MsgWaitForMultipleObjectsEx(),此时需要唤醒它。
比如QCoreApplication::postEvent()往队列放入新的事件时,会调用该函数。
打断processEvents() ,使其尽快返回,即使队列中还有很多东西。
主要是定时器的注册与反注册,相关的成员函数
QList<TimerInfo> QAbstractEventDispatcher::registeredTimers ( QObject * object )
在Windows层面,
普通定时器由 SetTimer()、KillTimer() 开启和关闭,在窗口回调函数中接收 WM_TIMER 消息,进而转换成Qt事件
在源码中,你可以看到 WSAAsyncSelect 这个函数的身影,但是它不是Windows的同名的api函数。而是一个本地的函数。
在Qt下,它是通过创建新线程,并在线程内调用 ::select() 函数来实现的。线程内通过SendMessage将消息传递到窗口的回调函数中。
int select( __in int nfds, __inout fd_set *readfds, __inout fd_set *writefds, __inout fd_set *exceptfds, __in const struct timeval *timeout );
QSocketNotifier::Read |
FD_READ |
有可读消息通知 |
FD_CLOSE |
关闭消息通知 |
|
FD_ACCEPT |
链接请求消息通知 |
|
QSocketNotifier::Write |
FD_WRITE |
有可写消息通知 |
FD_CONNECT |
希望得到connect或多点join操作完成信息通知 |
|
QSocketNotifier::Exception |
FD_OOB |
有外带消息通知 |
这是windows下特有的一个。当某个事件内核对象被触发时,我们可以以异步的方式得到通知。
在是在
QEventDispatcherWin32::processEvents()
函数内,通过调用
MsgWaitForMultipleObjectsEx()
来实现的。
恩,似乎还少点这部分的东西。Qt的事件都是要派发到 QObject::event() 这个函数中去的。
bool QCoreApplication::sendEvent ( QObject * receiver, QEvent * event )
直接将直接派发到 QObject::event()
而
void QCoreApplication::postEvent ( QObject * receiver, QEvent * event )
确实将事件放置到Qt的事件队列中。可是这些事件是怎么取出来,并派发到 QObject::event() 中去的呢?
负责这个工作的是:
void QCoreApplication::sendPostedEvents() void QCoreApplication::sendPostedEvents ( QObject * receiver, int event_type )
将事件队列中的事件取出,并使用QCoreApplication::sendEvent ()将其派发出去。
恩,exec() 启动的事件循环调用 event dispatcher 的 processEvents(),该函数负责调用前面的QCoreApplication::sendPostedEvents()函数。
它会通过PostMessage向程序消息队列放置WM_QT_SENDPOSTEDEVENTS消息
这样工作又转移到了一开始提到窗口回调函数中了。