從Windows訊息的角度看視窗應用程式的執行過程
一個典型的Win32視窗應用程式的框架是這樣的:
程式入口點(WinMain函式)-->註冊視窗類(呼叫RegisterClass函式或RegisterClassEx函式)-->建立主視窗(呼叫CreateWindow函式或CreateWindowEx函式)-->顯示主視窗(呼叫ShowWindow函式)-->更新主視窗(呼叫UpdateWindow函式)-->進入訊息迴圈(GetMessage、TranslateMessage、DispatchMessage)並處理各種Windows訊息(視窗過程函式)-->程式出口點(WinMain返回)。就像下面這個例子相同:
#include
#include
//視窗類名和視窗標題
TCHAR szWindowClass[]=_T("HELLOWINDOWS");
TCHAR szWindowTitle[]=_T("This is the MAIN window");
//視窗過程函式
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//LogMessage(logfile,msg,wParam,lParam);//
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd,msg,wParam,lParam);
}
}
int WINAPI _tWinMain( HINSTANCE hInstance,HINSTANCE,LPTSTR lpCmdLine,int nCmdShow)
{
//註冊視窗類
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = NULL;
RegisterClassEx(&wcex);
//建立主視窗
HWND hWnd = CreateWindowEx(0,szWindowClass, szWindowTitle, WS_OVERLAPPEDWINDOW,
128, 96, 512, 480, HWND_DESKTOP, NULL, hInstance, NULL);
if (!hWnd)
return FALSE;
//顯示並更新主視窗
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
// 進入訊息迴圈
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
//程式退出
}
這個例子很十分簡單,只是顯示一個視窗就完事了;但是麻雀雖小,五臟俱全,他基本上能夠作為一個Win32視窗程式的框架了。
下面來看他的視窗過程WndProc,他只處理了一個訊息:WM_DESTROY,其餘的訊息都交給了Windows去處理(呼叫DefWindowProc)。對於一個實際的Windows程式來說,要在視窗過程中處理的訊息會很多;然而Windows訊息成百上千,無論您處理多少訊息,剩下的您還是得呼叫DefWindowProc交給Windows系統去處理。這次我們就來看看,從程式啟動到退出,DefWindowProc到底要幫我們做多少的工作。
實驗的思想很簡單,把任何傳遞給視窗過程的訊息都記錄在一個Log文件中,我們就能夠察看在一個程式的生命過程中的任何訊息了。在上面的例子中,我們在視窗函式WndProc的最開始呼叫一個方法(形如註釋掉的那一行:LogMessage(logfile,msg,wParam,lParam)),把傳遞來的訊息型別,WPARAM引數,LPARAM引數順序都記錄下來,就會形成一個WIndows訊息Log文件了。
下面是兩次實驗的結果(假設上面的例子編譯後得到HelloWin.EXE):
實驗一:在文件管理其中選中HelloWin.EXE,按回車鍵啟動,顯示主視窗後馬上按下ALT+F4把他關閉,得到的LOG文件如下:
時間 訊息碼 引數 引數 描述
HH:MM:SS.MSS MSG WPARAM LPARAM DECRIPTION
03:21:39.187 0x0024 0x00000000 0x0012F910 WM_GETMINMAXINFO 獲取最大化最小化資訊
03:21:39.187 0x0081 0x00000000 0x0012F908 WM_NCCREATE 視窗非客戶區被建立
03:21:39.187 0x0083 0x00000000 0x0012F930 WM_NCCALCSIZE 計算非客戶區的大小
(WPARAM:FALSE->不必指出視窗客戶區的有效區域)
03:21:39.187 0x0001 0x00000000 0x0012F8D4 WM_CREATE 主視窗被建立
03:21:39.187 0x0018 0x00000001 0x00000000 WM_SHOWWINDOW
(WPARAM:TRUE->顯示視窗 LPARAM:0->指出這個訊息是呼叫ShowWindow函式發來的)
03:21:39.187 0x0046 0x00000000 0x0012FEB0 WM_WINDOWPOSCHANGING 視窗位置(包括大小)正在改變
03:21:39.203 0x001C 0x00000001 0x00000584 WM_ACTIVATEAPP 視窗程序啟用狀態改變
(WPARAM:TRUE->Activate啟用 LPARAM:執行緒ID=0x0584)
03:21:39.203 0x0086 0x00000001 0x00000000 WM_NCACTIVATE 非客戶區的啟用狀態需要改變
(WPARAM:TRUE->Activate啟用)
03:21:39.203 0x007F 0x00000002 0x00000000 WM_GETICON 獲取小圖示2
(WPARAM:2---->ICON_SMALL2)
03:21:39.203 0x007F 0x00000000 0x00000000 WM_GETICON 獲取小圖示
(WPARAM:0---->ICON_SMALL)
03:21:39.203 0x007F 0x00000001 0x00000000 WM_GETICON 獲取大圖示
(WPARAM:1---->ICON_BIG)
03:21:39.203 0x0006 0x00000001 0x00000000 WM_ACTIVATE 視窗啟用狀態改變
(WPARAM:1---->WA_ACTIVE啟用)
03:21:39.203 0x0281 0x00000001 0xC000000F WM_IME_SETCONTEXT 輸入法TRUE->Active
03:21:39.203 0x0282 0x00000002 0x00000000 WM_IME_NOTIFY 輸入法IMN_OPENSTATUSWINDOW
03:21:39.203 0x0007 0x00000000 0x00000000 WM_SETFOCUS 視窗獲得輸入焦點
03:21:39.218 0x0085 0x00000001 0x00000000 WM_NCPAINT 非客戶區需要重畫
(WPARAM:1---->整個視窗(Window Frame)都需要重畫
03:21:39.218 0x0014 0x0101005D 0x00000000 WM_ERASEBKGND 擦除背景
03:21:39.218 0x0047 0x00000000 0x0012FEB0 WM_WINDOWPOSCHANGED 視窗位置(包括大小)已改變
03:21:39.218 0x0083 0x00000001 0x0012FAEC WM_NCCALCSIZE 計算非客戶區的大小
(WPARAM:TRUE->NCCALCSIZE_PARAMS引數有效(根據該引數來計算重畫區域)
03:21:39.218 0x0085 0x00000001 0x00000000 WM_NCPAINT 非客戶區需要重畫
(WPARAM:1---->整個視窗(Window Frame)都需要重畫
03:21:39.218 0x0014 0x890109B8 0x00000000 WM_ERASEBKGND 擦除背景
03:21:39.218 0x0005 0x00000000 0x01BE01F8 WM_SIZE 視窗大小已改變
(WPARAM:0->SIZE_RESTORED 既不是最大化也不是最小化(MSDN如此說……))
03:21:39.218 0x0003 0x00000000 0x007E0084 WM_MOVE 視窗已被移動
(LPARAM:x=84[132] y=7e[126] 指出了視窗客戶區的新座標)
03:21:39.218 0x000F 0x00000000 0x00000000 WM_PAINT 視窗客戶區需要重畫
03:21:39.218 0x007F 0x00000002 0x00000000 WM_GETICON 2---->ICON_SMALL2
03:21:39.218 0x007F 0x00000000 0x00000000 WM_GETICON 0---->ICON_SMALL
03:21:39.218 0x007F 0x00000001 0x00000000 WM_GETICON 1---->ICON_BIG
===================================================================================
03:21:39.375 0x0101 0x0000000D 0xC01C0001 WM_KEYUP 鍵盤有按鍵被按下
(WPARAM:0D--->VK_RETURN(回車鍵) LPARAM:RECNT:1;SCCODE:1C;NON-EXKEY)
03:21:41.000 0x0104 0x00000012 0x20380001 WM_SYSKEYDOWN 系統鍵被按下
(WPARAM:12--->VK_MENU(ALT鍵) LPARAM:RECNT:1;SCCODE:38;ALT-DOWN)
03:21:41.609 0x0104 0x00000073 0x203E0001 WM_SYSKEYDOWN 系統鍵被按下
(WPARAM:73--->VK_F4(F4鍵) LPARAM:RECNT:1;SCCODE:3E;ALT-DOWN(ALT鍵同時被按下))
=======================================================================================
03:21:41.609 0x0112 0x0000F060 0x00000000 WM_SYSCOMMAND 系統命令
(WPARAM:F060->SC_CLOSE)
03:21:41.609 0x0010 0x00000000 0x00000000 WM_CLOSE 視窗需要被關閉
03:21:41.609 0x0046 0x00000000 0x0012F8A0 WM_WINDOWPOSCHANGING 視窗位置(包括大小)正在改變
03:21:41.609 0x0047 0x00000000 0x0012F8A0 WM_WINDOWPOSCHANGED 視窗位置(包括大小)已改變
03:21:41.609 0x0086 0x00000000 0x00000000 WM_NCACTIVATE 視窗非客戶區啟用狀態改變
(WPARAM:FALSE->InActivate非啟用狀態)
03:21:41.625 0x0006 0x00000000 0x00000000 WM_ACTIVATE 視窗啟用狀態改變
(WPARAM:0----->WA_INACTIVATE非啟用狀態)
03:21:41.625 0x001C 0x00000000 0x00000584 WM_ACTIVATEAPP 視窗程序啟用狀態改變
(WPARAM:FALSE->InActivate TID=0584)
03:21:41.625 0x0008 0x00000000 0x00000000 WM_KILLFOCUS 視窗失去輸入焦點
03:21:41.625 0x0281 0x00000000 0xC000000F WM_IME_SETCONTEXT 輸入法FALSE->InActivate
03:21:41.625 0x0282 0x00000001 0x00000000 WM_IME_NOTIFY 輸入法IMN_CLOSESTATUSWINDOW
03:21:41.625 0x0002 0x00000000 0x00000000 WM_DESTROY 視窗被銷燬
03:21:41.625 0x0082 0x00000000 0x00000000 WM_NCDESTROY 視窗非客戶區被銷燬
實驗二:在文件管理其中用滑鼠雙擊HelloWin.EXE啟動程式,顯示主視窗後馬上移動滑鼠到視窗右上角的關閉按鈕把他關閉,得到的LOG文件如下:
時間 訊息碼 引數 引數 描述
HH:MM:SS.MSS MSG WPARAM LPARAM DECRIPTION
04:29:01.421 0x0024 0x00000000 0x0012F910 WM_GETMINMAXINFO
04:29:01.421 0x0081 0x00000000 0x0012F908 WM_NCCREATE
04:29:01.421 0x0083 0x00000000 0x0012F930 WM_NCCALCSIZE
04:29:01.421 0x0001 0x00000000 0x0012F8D4 WM_CREATE
04:29:01.421 0x0018 0x00000001 0x00000000 WM_SHOWWINDOW
04:29:01.421 0x0046 0x00000000 0x0012FEB0 WM_WINDOWPOSCHANGING
04:29:01.437 0x001C 0x00000001 0x00000584 WM_ACTIVATEAPP
04:29:01.437 0x0086 0x00000001 0x00000000 WM_NCACTIVATE
04:29:01.437 0x007F 0x00000002 0x00000000 WM_GETICON
04:29:01.437 0x007F 0x00000000 0x00000000 WM_GETICON
04:29:01.437 0x007F 0x00000001 0x00000000 WM_GETICON
04:29:01.437 0x0006 0x00000001 0x00000000 WM_ACTIVATE
04:29:01.437 0x0281 0x00000001 0xC000000F WM_IME_SETCONTEXT
04:29:01.437 0x0282 0x00000002 0x00000000 WM_IME_NOTIFY
04:29:01.437 0x0007 0x00000000 0x00000000 WM_SETFOCUS
04:29:01.437 0x0085 0x00000001 0x00000000 WM_NCPAINT
04:29:01.437 0x0014 0x840108B2 0x00000000 WM_ERASEBKGND
04:29:01.437 0x0047 0x00000000 0x0012FEB0 WM_WINDOWPOSCHANGED
04:29:01.437 0x0083 0x00000001 0x0012FAEC WM_NCCALCSIZE
04:29:01.437 0x0085 0x00000001 0x00000000 WM_NCPAINT
04:29:01.437 0x0014 0x0F01093A 0x00000000 WM_ERASEBKGND
04:29:01.437 0x0005 0x00000000 0x01BE01F8 WM_SIZE
04:29:01.437 0x0003 0x00000000 0x007E0084 WM_MOVE
04:29:01.437 0x000F 0x00000000 0x00000000 WM_PAINT
04:29:01.437 0x007F 0x00000002 0x00000000 WM_GETICON
04:29:01.437 0x007F 0x00000000 0x00000000 WM_GETICON
04:29:01.437 0x007F 0x00000001 0x00000000 WM_GETICON
========================================================================================
04:29:01.828 0x0084 0x00000000 0x009E0266 WM_NCHITTEST 游標移動或滑鼠鍵被按下
(LPARAM:當前游標位置(相對於螢幕左上角)x=0266[614] y=009E[236])
04:29:01.828 0x0020 0x00A40268 0x02000001 WM_SETCURSOR 滑鼠導致的游標移動
(LPARAM:Hit-Test碼:01->HTCLIENT在客戶區 滑鼠訊息碼:0200->MOUSEMOVE滑鼠移動)
04:29:01.828 0x0200 0x00000000 0x002001E2 WM_MOUSEMOVE 滑鼠移動
(LPARAM:滑鼠當前位置 x=01E2[482] y=0020[32])
*************此處省略N個[WM_NCHITTEST-WM_SETCURSOR-WM_MOUSEMOVE]訊息***************
04:29:01.984 0x0084 0x00000000 0x007F0268 WM_NCHITTEST
(LPARAM:當前游標位置(相對於螢幕左上角)x=0268[616] y=007F[127])
04:29:01.984 0x0020 0x00A40268 0x02000001 WM_SETCURSOR
(LPARAM:Hit-Test碼:01->HTCLIENT在客戶區 滑鼠訊息碼:0200->MOUSEMOVE滑鼠移動)
04:29:01.984 0x0200 0x00000000 0x000101E4 WM_MOUSEMOVE
(LPARAM:滑鼠當前位置x=01E4[484] y=0001[1])
04:29:02.000 0x0084 0x00000000 0x007D0269 WM_NCHITTEST
(LPARAM:當前游標位置(相對於螢幕左上角)x=0269[617] y=007D[125])
04:29:02.000 0x0020 0x00A40268 0x02000002 WM_SETCURSOR 滑鼠導致的游標移動
(LPARAM:Hit-Test碼:02->HTCAPTION在標題區 滑鼠訊息碼:0200->MOUSEMOVE滑鼠移動)
04:29:02.000 0x00A0 0x00000002 0x007D0269 WM_NCMOUSEMOVE 非客戶區的滑鼠移動
(WPARAM:Hit-Test碼:02->HTCAPTION LPARAM:游標位置:x=0269[617] y=007D[125])
************此處省略N個[WM_NCHITTEST-WM_SETCURSOR-WM_MOUSEMOVE]訊息******************
04:29:02.234 0x0084 0x00000000 0x0075026D WM_NCHITTEST
(LPARAM:當前游標位置(相對於螢幕左上角)x=026D[621] y=0075[117])
04:29:02.234 0x0020 0x00A40268 0x02000014 WM_SETCURSOR
(LPARAM:Hit-Test碼:14->HTTOPRIGHT在右上角 滑鼠訊息碼:0200->MOUSEMOVE滑鼠移動)
04:29:02.234 0x00A0 0x00000014 0x0075026D WM_NCMOUSEMOVE
(WPARAM:Hit-Test碼:14->HTTOPRIGHT LPARAM:游標位置:x=026D[621] y=0075[117]
04:29:02.296 0x0084 0x00000000 0x0075026D WM_NCHITTEST
(LPARAM:當前游標位置(相對於螢幕左上角)x=026D[621] y=0075[117])
04:29:02.296 0x0020 0x00A40268 0x02010014 WM_SETCURSOR
(LPARAM:Hit-Test碼:14->HTTOPRIGHT在右上角 滑鼠訊息碼:0201->LBUTTONDOWN 滑鼠左鍵被按下
04:29:02.296 0x00A1 0x00000014 0x0075026D WM_NCLBUTTONDOWN 滑鼠左鍵在非客戶區按下
(LPARAM:當前游標位置(相對於螢幕左上角)x=026D[621] y=0075[117])
04:29:02.484 0x0215 0x00000000 0x00000000 WM_CAPTURECHANGED 正在失去滑鼠捕獲
(LPARAM:0---->HWND_DESKTOP 桌面將要獲得滑鼠)
===========================================================================================
04:29:02.484 0x0112 0x0000F060 0x0075026D WM_SYSCOMMAND F060-->SC_CLOSE
04:29:02.484 0x0010 0x00000000 0x00000000 WM_CLOSE
04:29:02.484 0x0046 0x00000000 0x0012F5E4 WM_WINDOWPOSCHANGING
04:29:02.484 0x0047 0x00000000 0x0012F5E4 WM_WINDOWPOSCHANGED
04:29:02.484 0x0086 0x00000000 0x00000000 WM_NCACTIVATE
04:29:02.484 0x0006 0x00000000 0x00000000 WM_ACTIVATE
04:29:02.484 0x001C 0x00000000 0x00000584 WM_ACTIVATEAPP
04:29:02.484 0x0008 0x00000000 0x00000000 WM_KILLFOCUS
04:29:02.484 0x0281 0x00000000 0xC000000F WM_IME_SETCONTEXT
04:29:02.484 0x0282 0x00000001 0x00000000 WM_IME_NOTIFY
04:29:02.484 0x0002 0x00000000 0x00000000 WM_DESTROY
04:29:02.484 0x0082 0x00000000 0x00000000 WM_NCDESTROY
在實驗二中,打星號(*)的部分省略了很多個[WM_NCHITTEST-WM_SETCURSOR-WM_MOUSEMOVE]訊息,滑鼠移動過程中他們一直產生,唯一不同的是指示的滑鼠位置不同。
由上兩個LOG文件記錄的Windows訊息能夠對比看出,從我們開始啟動程式(無論是從鍵盤還是滑鼠雙擊啟動)到主視窗最終顯示在我們面前,Windows已處理了從開始的WM_GETMINMAXINFO訊息到第一條雙劃線前的WM_GETICON訊息;而從我們關閉一個視窗(無論是按下ALT+F4還是滑鼠點選“關閉”按鈕)到程式最後退出,Windows要處理從第二條雙劃線後的WM_SYSCOMMAND到最後的WM_NCDESTROY訊息。而程式在執行過程中產生的訊息在兩條雙劃線之間(有上面兩個LOG文件能夠看出區分來,跟程式的行為相關的)。值得一提的是程式最後收到的一條訊息WM_QUIT,是由PostQuitMessage函式傳送的,在訊息迴圈中被GetMessage捕獲,GetMessage函式捕獲到WM_QUIT訊息之後返回FALSE,於是WinMain函式退出訊息迴圈,是不交給視窗過程去處理的,所以上面沒有記錄下來;之後程式便退出了。
在寫windows程式的時候,我們也能夠通過記錄windows訊息的方式去除錯程式的,至於怎麼來我就不羅嗦啦: