消息机制
Windows程序是事件驱动,消息传递的,而消息分为队列消息和非队列消息。
1. 队列消息:
对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE, WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT, WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。
2. 非队列消息
非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程,。系统发送非队列消息通知窗口,系统发送消息通知窗口。 例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。
在讲消息的时候提到了系统消息队列和线程消息队列。系统中有一个系统消息队列,它由系统维护,队列消息会发到它那里,然后再发送到相应的线程消息队列里面。线程消息队列有相应的线程在维护,但并不是每个线程都有线程消息队列,仅当线程第一次调用GDI函数时系统给线程创建一个消息队列(例如MFC的UI线程)。还有消息队列是相对线程而言,而非进程。对于MFC的UI线程的线程消息队列有了消息之后,还会派送消息到消息对应窗口(消息结构体中有窗口句柄)。
MFC UI线程和工作线程
之前挺好奇,MFC程序的主线程一直在跑消息循环,一直在while里面,界面为什么不卡住还能响应?
这取决与PeekMessage(),OnIdle()等函数。
BOOL PeekMessage(…):该函数为一个消息检查线程消息队列,并将该消息(如果存在)放于指定的结构。和GetMessage()不一样的是,GetMessage()从线程消息队列获取消息,将消息从队列中移除,属于阻塞函数。当队列无消息时,GetMessage会等待下一条消息。而函数PeekMesssge是以查看的方式从队列中获取消息,可以不将消息从系统中移除,是非阻塞函数;当队列无消息时,返回FALSE,继续执行后续代码。
virtual BOOL OnIdle( LONG lCount ):执行空闲时间处理。不需要更多空闲时间返回0,否则返回非0。在这个空闲时间处理中将更新UI界面(比如工具栏按钮的enable和disable状态),删除临时对象(比如用FromHandle得到的对象指针。由于这个原因,在函数之间传递由FromHandle得到的对象指针是不安全的,因为他没有持久性)。在下面可以看到对传入参数lCount的处理。
后来找到下面这篇博文,主要讲MFC的UI线程和工作线程,个人感觉讲得很清晰,借助博主贴的代码,也更好地理解了消息机制。
MFC的AfxBeginThread提供了两个版本:
CWinThread* AFXAPI AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority =THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
CWinThread* AFXAPI AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
第一个版本用来让人创建“工作线程”,第二个版本让人用来创建“UI线程”。
1. UI线程
继承CWinThread->启动线程->进入CWinThread::Run()。
CWinThread::Run()的实现:
int CWinThread::Run()
{
ASSERT_VALID(this);
_AFX_THREAD_STATE*