1. 程式人生 > >從Windows訊息的角度看視窗應用程式的執行過程

從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訊息的方式去除錯程式的,至於怎麼來我就不羅嗦啦: