Microsoft Windows的讯息回圈

微软视窗操作系统是以事件驱动做为程式设计的基础。程式的执行绪会从作业系统获取讯息。应用程式会不断循环呼叫GetMessage函式（或是PeekMessage函式）来接收这些讯息，这个循环称之为“事件回圈”。基本上事件回圈的程式码如下所示（C语言 / C++程式语言）：
MSG msg; //用于存储一条消息
BOOL bRet;
//从UI线程消息队列中取出一条消息
while( (bRet = GetMessage( &amp;msg, NULL, 0, 0 )) != 0)
 if (bRet == -1)
 //错误处理代码，通常是直接退出程序
 else
 TranslateMessage(&amp;msg); //按键消息转换为字符消息
 DispatchMessage(&amp;msg); //分发消息给相应的窗体
虽然在程序上并没有很严格的规定与要求，但是一般来说，它的事件回圈通常会呼叫TranslateMessage函式与DispatchMessage函式，这两个函式会传递讯息给回呼函式，以及调用相应视窗的消息处理函数。
现在的绘图介面架构程式设计，例如Visual Basic与Qt基本上是不会要求应用程式直接拥有视窗程式的讯息回圈，但是会以键盘与滑鼠的按键动作来作为事件的处理机制。在这些架构底下，讯息回圈的痕迹还是可以被找到的。
注意：在上述的原始码里，尤其在while回圈"大于零"的条件。即使GetMessage函式的传回值型态是英文字大写的BOOL，但是在Win32视窗程式里，它是被定义成int整数型态，它有两个值，TRUE是整数的1，FALSE是整数的0。整数 -1代表error（例如第二个参数为输出的窗口句柄但取不到值的时候），整数0值当GetMessage获取到WM_QUIT讯息。假如有其他讯息，那么非零值会当成传回值（有讯息的传回值通常是正值，但是有些程式设计的说明文件不一定会说明的很详细）。
历史.
16位Windows系统为非抢先单线程模式，应用程序没有发送消息队列，向窗口发送一个消息总是按同步方式执行，也即发送程序要在接受消息的窗口完全处理完消息之后才能继续运行。这通常是一个所期望的特性。但是，如果接收消息的窗口花很长的时间来处理消息或者出现挂起，则发送程序就不能再执行。这意味着系统是不强壮的。如果应用程序消息队列（只用于存放投寄的消息）为空，由于没有虚拟输入消息队列，SendMessage或PeekMessage函数访问系统事件队列查取可用的鼠标或键盘输入消息。如果系统队列中没有需要处理的事件，SendMessage或PeekMessage函数扫描所有窗口以处理需要修改重绘的区域。如果没有需要重绘的区域，则交出CPU控制权。恢复CPU控制权时，查看是否有定时器过期。至此如果没有消息可返回，SendMessage进入睡眠，直至被输入事件唤醒；PeekMessage如果没有设置PM_NOYIELD标记，则会让出CPU控制权，但不会让线程休眠，重新获得CPU后PeekMessage将控制权返回到线程，并返回一个空值指出这个线程没有要处理的消息了。
本文主要关注Win32系统的消息处理机制。
背景.
UI线程.
Windows系统规定，窗口和钩子（hook）这两种User对象分别由建立窗口和安装钩子的线程所拥有，一旦该线程结束，操作系统会自动删除窗口或卸载钩子。而其他的User对象（图标icon、光标cursor、窗口类WndClass、菜单、加速键表等）则归进程所有，进程结束时操作系统会自动删除这些对象。
建立窗口的线程必须就是处理窗口所有消息的线程，即UI线程（User Interface Thread）创建了窗体及窗体上的各种控件，系统为UI线程分配一个消息队列用于窗口消息的派送（dispatch）。为了使窗口处置这些消息，线程必须有它自己的“消息循环”。只有当一个线程调用Windows API中的GDI（Graphics Device Interface）和User函数时，操作系统才会将其看成是一个UI线程，并为它分配一些另外的资源，创建一套线程消息队列；否则，操作系统把非UI线程视作普通工作线程（Workhorse），不会为它创建消息队列。因此，调用PostThreadMessage前，这个线程必须是UI线程从而有投寄消息的队列，通常可在该线程中调用一次PeekMessage函数以达到这个目的。
如果一个UI线程结束运行，操作系统会自动回收它所创建的所有窗体。 
窗体过程.
窗体过程（Window Procedure）是一个函数，每个窗体有一个窗体过程，负责处理该窗体的所有消息。
UI控件也是独立的“Window”，拥有自己的“窗体过程”。
消息队列.
Windows操作系统的内核空间中有一个系统消息队列（system message queue），在内核空间中还为每个UI线程分配各自的线程消息队列(Thread message queue)。在发生输入事件之后，Windows操作系统的输入设备驱动程序将输入事件转换为一个「消息」投寄到系统消息队列；操作系统的一个专门线程从系统消息队列取出消息，分发到各个UI线程的输入消息队列中。
每个UI线程的线程信息块TIB分配一个THREADINFO的结构，该结构包含一族成员变量，包括：
应用程序的每个UI线程中有一段称之为“消息循环”的代码，通过GetMessage系统调用（或是PeekMessage系统调用）访问系统空间中的对应的UI线程的消息队列，并依照下述次序处理：
需要注意的是，GetMessage如果在应用程序消息队列未获取消息，则GetMessage调用不返回，该线程挂起，CPU使用权交给操作系统。即GetMessage为阻塞调用。
由此可见，Windows的事件驱动模式，并不是操作系统把消息主动分发给应用程序；而是由应用程序的每个UI线程通过“消息循环”代码从UI线程消息队列获取消息。
Windows消息类别.
键盘输入时需要明确插入符位置，相关API函数为：CreateCaret、SetCaretPos、ShowCaret、HideCaret、DestroyCaret、GetCaretPos、GetCaretBlinkTime、SetCaretBlinkTime。
3个获得键状态的函数：GetKeyState、GetAsyncKeyState、GetKeyboardState。
对于自定义的控件，当单击子窗口时，父窗口会得到焦点。但对于标准子窗口控件，单击时会自动获得焦点（子窗口过程在WM_LBUTTONDOWN中实现了SetFocus(hwnd)）。如果一个子窗口拥有输入焦点，鼠标单击另一个兄弟子窗口，则兄弟子窗口获得输入焦点。
同步与阻塞.
Windows API函数SendMessage是个同步调用，即它发出的Windows消息没被处理完之前这个函数就不返回。但这个函数不是阻塞的。分两种情形：
异步发送或投寄消息的函数，如PostMessage、SendMessageCallback、SendNotifyMessage，消息参数中不能使用指针，否则函数调用失败。
GetMessage伪算法.
BOOL GetMessage(MSG *lpMsg, HWND hWnd , UINT wMsgFilterMin, UINT wMsgFilterMax)
 //查看QS_SENDMESSAGE标志，如果有的话循环处理，直到没有消息位置
 DWORD dwRetVal = 0;
 ThreadInfo threadInfo;
FLAG_SENDPROCLOOP:
 GetThreadInfo(GetCurrentThreadId(), &amp;threadInfo);
 while (threadInfo.QS_SENDMESSAGE == QS_SIGNALSET) {
 //从发送消息队列中获取消息
 dwReturnVal = GetMsgFromQueue(QUEUE_SEND, lpMsg, hWnd,wMsgFilterMin, wMsgFilterMax);
 //判断是否取到消息,有则调用窗口函数，无则复为QS_SENDMESSAGE标志
 If (dwReturnVal == GETMESSAGE_HASMESSAGE) {
 //调用指定窗口的窗口函数
 CallWindowProc(hWnd, &amp;threadInfo, lpMsg);
 else {
 QS_SENDMESSAGE = QS_SIGNALRESET;
 break;
 //在继续处理之前再次检查发送消息队列
 if (threadInfo.QS_SENDMESSAGE == QS_SIGNALSET) goto FLAG_SENDPROCLOOP;
 //检查发送消息队列, 如果有消息则取发送消息
 //判断是否还有发送消息，没有了则复位QS_POSTMESSAGE标志
 if (threadInfo.QS_POSTMESSAGE == QS_SIGNALSET) {
 dwReturnVal = GetMsgFromQueue(QUEUE_POST, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax);
 if (dwReturnVal == GETMESSAGE_LASTMESSAGE)
 threadInfo.QS_POSTMESSAGE = QS_SIGNALRESET;
 return TRUE;
 
 //如果退出标志被置位
 if (threadInfo.QS_QUIT == QS_SIGNALSET) {
 threadInfo.QS_QUIT = QS_SIGNALRESET;
 FillMessage(lpMsg, MESSAGE_QUIT);
 return FALSE;
 
 //检查输入消息队列
 if (threadInfo.QS_INPUT == QS_SIGNALSET) {
 DWORD dwRetVal = GetMessageFromQueue(QUEUE_INPUT, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax);
 //检查是否有键盘，鼠标消息
 if (Test(dwRetVal, QS_KEY) == QS_LASTMOUSEKEYMESSAGE)
 threadInfo.QS_KEY = QS_SIGNALRESET;
 if (Test(dwRetVal, QS_MOUSEBUTTON) == QS_LASTMOUSEMESSAGE)
 threadInfo.QS_MOUSEBUTTON = QS_SIGNALRESET;
 return TRUE;
 
 //测试QS_PAINT
 if (threadInfo.QS_PAINT == QS_SIGNALSET) {
 //填充MSG，如果没有窗口过程确认窗口，则复位QS_PAINT标志
 //返回TRUE
 threadInfo.QS_PAINT = QS_SIGNALRESET;
 return TRUE;
 
 if (threadInfo.QS_TIMER == QS_SIGNALSET) {
 //填充MSG，如果没有定时器报时，则复位QS_TIMER标志
 //返回TRUE
 return TRUE;
 
 //等待有消息到达
 dwRetVal = MsgWaitForMultipleObjectsEx(...);
 if (...)
 goto FLAG_SENDPROCLOOP;
 //等待失败
 return FALSE;