本文轉自:http://www.cnblogs.com/sakiwer/archive/2009/12/15/1619282.html
-----------------------------------------------------------------------------------------------------------------------------------------------------
曾經在Win32平台下奮戰的程式員們想必記得,為了弄清楚“訊息迴圈”的概念,度過多少不眠之夜。儘管如今在應用程式代碼的編寫過程中,我們已經不再需要它,但是深刻理解Windows平台內部的訊息流程轉機制依然必要..
在早年直接用Win32/Win16 API寫程式的時代,訊息迴圈是我們必須搞懂的第一個觀念。現在,不管你用是Windows上面的哪一套Application Framework(MFC、VCL、VB、.NET Framework),甚至Unix、Linux、MacOSX上面的Application Framework,都不太容易看到訊息迴圈。事實上,訊息迴圈依然存在,只是被這些ApplicationFramework封裝起來,深深地埋藏在某 個角落。
本文章試圖喚起大家對於訊息迴圈的回憶,也試圖解釋訊息迴圈如何被封裝進.NET Framework的Windows Forms中。雖然Windows Forms將這一切都藏起來,但是也留下許多空間,讓我們可以自行處理Win32的訊息。
傳統的Windows 程式
傳統的Windows程式,只利用Win32 API撰寫,下面是一個程式範例,為了節省篇幅,我將其中許多程式碼省略:
| // 程式進入點 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow){ MSG msg; if (!InitInstance (hInstance, nCmdShow)){ return FALSE; } // 主訊息迴圈: while (GetMessage(&msg, NULL, 0, 0)){ TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } // 函數: WndProc(HWND, unsigned, WORD, LONG) // 用途: 處理主視窗的訊息。 LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message){ case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 剖析菜單選取項目: switch (wmId){ case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX,hWnd, (DLGPROC)About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message,wParam,lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: 在此加入任何繪圖程式碼... EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message,wParam, lParam); } return 0; } // [關於] 方塊的訊息處理常式。 LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){ switch (message){ case WM_INITDIALOG: return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){ EndDialog(hDlg, LOWORD(wParam)); return TRUE; } break; } return FALSE; } |
1、從_tWinMain內,程式進入主訊息迴圈;
2、訊息迴圈從訊息佇列(Message Queue)中取得一個訊息(透過調用GetMessage())。每個執行中的程式都有一個屬於自己的訊息佇列;
3、訊息迴圈根據訊息內容來決定訊息應該送給哪個Windows Procedure(WndProc),.. 這就稱為訊息分發(Message Dispatch)。通常“每一種”視窗或控制項(control)都有一個Windows Procedure,來處理該種視窗/控制項的行為;
4、Windows Procedure根據訊息內容來決定應該調用哪個函數(利用Switch/Case文法);..
5、Windows Procedure處理完,控制權回到訊息迴圈。繼續進行2、3、4、5的動作;
6、當訊息佇列為空白的時候,GetMessage()無法取得任何訊息,就會進入Idle(空閑)狀態,進入睡眠狀態(而不是Busy Waiting)。當訊息佇列不再為空白的時候,程式會自動醒過來,繼續進行2、3、4、5的動作;
7、當取得的訊息是WM_QUIT,GetMessage()就會得到0的傳回值,因而離開訊息迴圈,程式結束。程式會利用調用PostQuitMessage()來將WM_QUIT放置進訊息佇列中,來造成稍後結束,而不會直接貿然跳離開迴圈來結束。
雖名為隊列(queue),.. 但是訊息佇列中的訊息並非總是先進先出(First In First Out,FIFO),有一些特例:
. 只要訊息佇列中有WM_QUIT ,就會先取出WM_QUIT,導致程式結束。
. 只有在沒有其它訊息的時候,WM_PAINT 和WM_TIMER才會被取出。且多個WM_PAINT可能會被合并成一個,WM_TIMER也是如此。
. 利用TranslateMessage()來處理訊息,可能會造成新訊息的產生。例如:TranslateMessage()可以辨識出WM_KEYDOWN(按鍵按下)加上WM_KEYUP(按鍵放開)就產生WM_CHAR(字元輸入)。