在講本程序的消息循環以前,我想先談一下Dos與Windows驅動機制的區別:php
DOS程序主要使用順序的,過程驅動的程序設計方法。順序的,過程驅動的程序有一個明顯的開始,明顯的過程及一個明顯的結束,所以程序能直接控制程序事件或過程的順序。雖然在順序的過程驅動的程序中也有不少處理異常的方法,但這樣的異常處理也仍然是順序的,過程驅動的結構。css
而Windows的驅動方式是事件驅動,就是不禁事件的順序來控制,而是由事件的發生來控制,全部的事件是無序的,所爲一個程序員,在你編寫程序時,你並不知道用戶先按哪一個按紐,也不知道程序先觸發哪一個消息。你的任務就是對正在開發的應用程序要發出或要接收的消息進行排序和管理。事件驅動程序設計是密切圍繞消息的產生與處理而展開的,一條消息是關於發生的事件的消息。html
C語言編程至少有一個主程序,其名字是main()。Windows程序則至少兩個主程序,一個是WinMain(),程序員
int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window );
另外一個是窗口過程函數WindowProc,它的函數原型爲:編程
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );
Windows應用程序的編程就圍繞這兩個部份進行的。其中WinMain函數爲應用程序的入口點,它的名字必定要是WinMain。windows
在Windows中,應用程序經過要求Windows完成指定操做,而承擔這項通訊任務的API函數就是Windows的相應窗口函數WindowProc。在dos裏,程序能直接控制事件的發生順序,結果等。而在Windows裏,應用程序不直接調用任何窗口函數,而是等待Windows調用窗口函數,請求完成任務或返回信息。爲保證Windows調用這個窗口函數,這個函數必須先向Windows登記,而後在Windows實施相應操做時回調,因此窗口函數又稱爲回調函數。WindowProc是一個主回調函數,Windows至少有一個回調函數。api
回調函數WindowProc在哪裏定義的呢,請看這個語句:wc.lpfnWndProc = WindowProc ;將在第七講裏詳談.app
實例:在Windows中,能屢次同時運行同一個應用程序,即運行多個副本,每一個副本叫作一個「實例」。異步
如今讓咱們把這個程序層層剝解開來,我把本身的理解慢慢地展現給你:ide
我把這個程序支解爲四塊:(一)創建,註冊窗口類.(二)建立窗口.(三)顯示和更新窗口.(四)建立消息循環.(五)終止應用程序.(六)窗口過程.(七)處理消息.
(1)創建窗口類
WinMain()是程序的入口,它至關於一箇中介人的角色,把應用程序(指小窗口)介紹給windows.首要的一步是登記應用程序的窗口類.
窗口種類是定義窗口屬性的模板,這些屬性包括窗口式樣,鼠標形狀,菜單等等,窗口種類也指定處理該類中全部窗口消息的窗口函數.只有先創建窗口種類,才能根據窗口種類來建立Windows應用程序的一個或多個窗口.建立窗口時,還能夠指定窗口獨有的附加特性.窗口種類簡稱窗口類,窗口類不能重名.在創建窗口類後,必須向Windows登記.
創建窗口類就是用WNDCLASS結構定義一個結構變量,在這個程序中就是指 WNDCLASS wc ;而後用本身設計的窗口屬性的信息填充結構變量wc的域.
要WinMain登記窗口類,首先要填寫一個WNDCLASS結構,其定義以下所示:
typedef struct _WNDCLASSA { UINT style ; //窗口類風格 WNDPROC lpfnWndProc ; //指向窗口過程函數的指針 int cbClsExtra ; //窗口類附加數據 int cbWndExtra ; //窗口附加數據 HINSTANCE hInstance ; //擁有窗口類的實例句柄 HICON hIcon ; //最小窗口圖標 HCURSOR hCursor ; //窗口內使用的光標 HBRUSH hbrBackground ; //用來着色窗口背景的刷子 LPCSTR lpszMenuName ; //指向菜單資源名的指針 LPCSTR lpszClassName ; // 指向窗口類名的指針 }WNDCLASS; // 增強版 typedef struct _WNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX;
在VC6.0裏面,把光標定位在WNDCLASS上,按F1,便可啓動MSDN,在MSDN裏你可看到這個結構原形.在下節講解這些參數在本程序中的具體用法.
(2)註冊窗口類
(1)第一個參數:成員style控制窗口的某些重要特性,在WINDOWS.H中定義了一些前綴爲CS的常量,在程序中可組合使用這些常量.也可把sytle設爲0.本程序中爲wc.style = CS_HREDRAW | CS_VREDRAW
,它表示當窗口的縱橫座標發生變化時要重畫整個窗口。你看:不管你怎樣拉動窗口的大小,那行字都會停留在窗口的正中部,而假如把這個參數設爲0的話,當改動窗口的大小時,那行字則不必定處於中部了。
(2)第二個參數:lpfnWndProc包括一個指向該窗口類的消息處理函數的指針,此函數稱爲窗口過程函數。它將接收Windows發送給窗口的消息,並執行相應的任務。其原型爲:
long FAR PASCAL WndProc(HWND ,unsigned,WORD,LONG);而且必須在模快定義中回調它。WndProc是一個回調函數(見第五節),若是暫時沒法理解這個模糊的概念意味着什麼,可先放過,等到講消息循環時再詳談。
(3)第三,四個參數:cbWndExtra域指定用本窗口類創建的全部窗口結構分配的額外字節數。當有兩個以上的窗口屬於同一窗口類時,若是想將不一樣的數據和每一個窗口分別相對應。則使用該域頗有用。這般來說,你只要把它們設爲0就好了,沒必要過多考慮。
(4)第五個參數:hInstance域標識應用程序的實例hInstance,固然,實例名是能夠改變的。wc.hInstance = hInstance ;這一成員可以使Windows鏈接到正確的程序。
(5)第六個參數:成員hIcon被設置成應用程序所使用圖標的句柄,圖標是將應用程序最小化時出如今任務欄裏的的圖標,用以表示程序仍駐留在內存中。Windows提供了一些默認圖標,咱們也可定義本身的圖標,VC裏面專有一個製做圖標的工具。
(6)第七個參數: hCursor域定義該窗口產生的光標形狀。LoadCursor可返回固有光標句柄或者應用程序定義的光標句柄。IDC_ARROW表示箭頭光標.
(7)第八個參數:wc.hbrBackground域決定Windows用於着色窗口背景的刷子顏色,函數GetStockObject返回窗口的顏色,本程序中返回的是白色,你也能夠把它改變爲紅色等其餘顏色.試試看
(8)第九個參數:lpszMenuName用來指定菜單名,本程序中沒有定義菜單,因此爲NULL。
(9)第十個參數:lpszClassName指定了本窗口的類名。
當對WNDCLASS結構域一一賦值後,就可註冊窗口類了,在建立窗口以前,是必需要註冊窗口類的,註冊窗口類用的API函數是RegisterClass,註冊失敗的話,就會出現一個對話框如程序所示,函數RegisterClass返回0值,也只能返回0值,由於註冊不成功,程序已經不能再進行下去了。
在本程序中註冊窗口類以下:
if (!RegisterClass (&wc)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName,MB_IConERROR) ; return 0 ; }
註冊窗口類後,就能夠建立窗口了,本程序中建立窗口的有關語句以下:
HWND CreateWindow(
LPCTSTR lpClassName, // pointer to registered class name LPCTSTR lpWindowName, // pointer to window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent or owner window HMENU hMenu, // handle to menu or child-window identifier HANDLE hInstance, // handle to application instance LPVOID lpParam // pointer to window-creation data );
參數1:登記的窗口類名,這個類名剛纔我們在註冊窗口時已經定義過了。
參數2:用來代表窗口的標題。
參數3: 用來代表窗口的風格,若有無最大化,最小化按紐啊什麼的。
參數4,5: 用來代表程序運行後窗口在屏幕中的座標值。
參數6,7: 用來代表窗口初始化時(即程序初運行時)窗口的大小,即長度與寬度。
參數8: 在建立窗口時能夠指定其父窗口,這裏沒有父窗口則參數值爲0。
參數9: 用以指明窗口的菜單,菜單之後會講,這裏暫時爲0。
最後一個參數是附加數據,通常都是0。
CreateWindow()的返回值是已經建立的窗口的句柄,應用程序使用這個句柄來引用該窗口。若是返回值爲0,就應該終止該程序,由於可能某個地方出錯了。若是一個程序建立了多個窗口,則每一個窗口都有各自不一樣的句柄.
API函數CreateWindow建立完窗口後,要想把它顯示出現,還必須調用另外一個API函數ShowWindows.形式爲:
ShowWindow (hwnd, iCmdShow);
其第一個參數是窗口句柄,告訴ShowWindow()顯示哪個窗口,而第二個參數則告訴它如何顯示這個窗口:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),仍是最大化(SW_SHOWMAXIMIZED)。WinMain在建立完窗口後就調用ShowWindow函數,並把iCmdShow參數傳送給這個窗口。你可把iCmdShow改變爲這些參數試試。
WinMain()調用完ShowWindow後,還須要調用函數UpdateWindow,最終把窗口顯示了出來。調用函數UpdateWindow將產生一個WM_PAINT消息,這個消息將使窗口重畫,即便窗口獲得更新.
主窗口顯示出來了,WinMain就開始處理消息了,怎麼作的呢?
Windows爲每一個正在運行的應用程序都保持一個消息隊列。當你按下鼠標或者鍵盤時,Windows並非把這個輸入事件直接送給應用程序,而是將輸入的事件先翻譯成一個消息,而後把這個消息放入到這個應用程序的消息隊列中去。應用程序又是怎麼來接收這個消息呢?這就講講消息循環了。
應用程序的WinMain函數經過執行一段代碼從她的隊列中來檢索Windows送往她的消息。而後WinMain就把這些消息分配給相應的窗口函數以便處理它們,這段代碼是一段循環代碼,故稱爲」消息循環」。這段循環代碼是什麼呢?好,往下看:
在我們的第二隻小板凳中,這段代碼就是:
……
MSG msg; //定義消息名 while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; //翻譯消息 DispatchMessage (&msg) ; //撤去消息 } return msg.wParam ;
MSG結構在頭文件中定義以下:
typedef struct tagMSG { // msg HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG;
MSG數據成員意義以下:
參數1:hwnd是消息要發送到的那個窗口的句柄,這個窗口就是我們用CreateWindows函數建立的那一個。若是是在一個有多個窗口的應用程序中,用這個參數就可決定讓哪一個窗口接收消息。
參數2:message是一個數字,它惟一標識了一種消息類型。每種消息類型都在Windows文件中定義了,這些常量都以WM_開始後面帶一些描述了消息特性的名稱。好比說當應用程序退出時,Windows就嚮應用程序發送一條WM_QUIT消息。
參數3:一個32位的消息參數,這個值的確切意義取決於消息自己。
參數4:同上。
參數5:消息放入消息隊列中的時間,在這個域中寫入的並非日期,而是從Windows啓動後所測量的時間值。Windows用這個域來使用消息保持正確的順序。
參數6:消息放入消息隊列時的鼠標座標.
消息循環以GetMessage調用開始,它從消息隊列中取出一個消息:
GetMessage(&msg,NULL,0,0)
,第一個參數是要接收消息的MSG結構的地址,第二個參數表示窗口句柄,NULL則表示要獲取該應用程序建立的全部窗口的消息;第三,四參數指定消息範圍。後面三個參數被設置爲默認值,這就是說你打算接收發送到屬於這個應用程序的任何一個窗口的全部消息。在接收到除WM_QUIT以外的任何一個消息後,GetMessage()都返回TRUE。若是GetMessage收到一個WM_QUIT消息,則返回FALSE,如收到其餘消息,則返回TRUE。所以,在接收到WM_QUIT以前,帶有GetMessage()的消息循環能夠一直循環下去。只有當收到的消息是WM_QUIT時,GetMessage才返回FALSE,結束消息循環,從而終止應用程序。 均爲NULL時就表示獲取全部消息。
消息用GetMessage讀入後(注意這個消息可不是WM_QUIT消息),它首先要通過函數TranslateMessage()進行翻譯,這個函數會轉換成一些鍵盤消息,它檢索匹配的WM_KEYDOWN和WM_KEYUP消息,併爲窗口產生相應的ASCII字符消息(WM_CHAR),它包含指定鍵的ANSI字符.但對大多數消息來講它並不起什麼做用,因此如今沒有必要考慮它。
下一個函數調用DispatchMessage()要求Windows將消息傳送給在MSG結構中爲窗口所指定的窗口過程。咱們在講到登記窗口類時曾提到過,登記窗口類時,咱們曾指定Windows把函數WindosProc做爲我們這個窗口的窗口過程(就是指處理這個消息的東東)。就是說,Windows會調用函數WindowsProc()來處理這個消息。在WindowProc()處理完消息後,代碼又循環到開始去接收另外一個消息,這樣就完成了一個消息循環。
下一個出場的東東就是窗口過程了,先歇一下子再說吧??
Windows是一種非剝奪式多任務操做系統。只有的應用程序交出CPU控制權後,Windows才能把控制權交給其餘應用程序。當GetMessage函數找不到等待應用程序處理的消息時,自動交出控制權,Windows把CPU的控制權交給其餘等待控制權的應用程序。因爲每一個應用程序都有一個消息循環,這種隱式交出控制權的方式保證合併各個應用程序共享控制權。一旦發往該應用程序的消息到達應用程序隊列,即開始執行GetMessage語句的下一條語句。
當WinMain函數把控制返回到Windows時,應用程序就終止了。應用程序的啓動消息循環前要檢查引導出消息循環的每一步,以確保每一個窗口已註冊,每一個窗口都已建立。如存在一個錯誤,應用程序應返回控制權,並顯示一條消息。
可是,一旦WinMain函數進入消息循環,終止應用程序的惟一辦法就是使用PostQuitMessage把消息WM_QUIT發送到應用程序隊列。當GetMessage函數檢索到WM_QUIT消息,它就返回NULL,並退出消息外循環。一般,當主窗口正在刪除時(即窗口已接收到一條WM_DESTROY消息),應用程序主窗口的窗口函數就發送一條WM_QUIT消息。
雖然WinMain指定了返回值的數據類型,但Windows並不使用返回值。不過,在調試一應用程序時,返回值地有用的。一般,可以使用與標準C程序相同的返回值約定:0表示成功,非0表示出錯。PostQuitMessage函數容許窗口函數指定返回值,這個值複製到WM_QUIT消息的wParam參數中。爲了的結束消息循環以後返回這個值,咱們的第二隻小板凳中使用瞭如下語句:
return msg.wParam ; //表示從PostQuitMessage返回的值
例如:當Windows自身終止時,它會撤消每一個窗口,但不把控制返回給應用程序的消息循環,這意味着消息循環將永遠不會檢索到WM_QUIT消息,而且的循環以後的語句也不能再執行。Windows的終止前的確發送一消息給每一個應用程序,於是標準C程序一般會的結束前清理現場並釋放資源,但Windows應用程序必須隨每一個窗口的撤消而被清除,不然會丟失一些數據。
如前所述,函數GetMessage負責從應用程序的消息隊列中取出消息,而函數DispatchMessage()要求Windows將消息傳送給在MSG結構中爲窗口所指定的窗口過程。而後出臺的就是這個窗口過程了,這個窗口過程的任務是幹什麼呢?就是最終用來處理消息的,就是消息的處理器而已,那麼這個函數就是WindowProc,在Visual C++6.0中按F1啓動MSDN,按下面這個路徑走下來:
PlatForm SDK–>User Interface services–>Windows user Interface–>Windowing–>Window Procedures–>Window Procedure Reference–>Windows Procedure Functions–>WindowProc
啊,太累了,不過咱們終於的MSDN中找到了這個函數,前幾回我講解這些API函數的時候,都是的知道的狀況下搜索出來的,因此沒有詳細給出每一個函數的具體位置,而此次我倒是一點點去找的,還好,沒被累死,體會到MSDN的龐大了吧,不過我用的是MSDN2000,是D版的,三張光盤裝。你用的MSDN若是按這個路徑走下去的話,可能會找不到,不過我想大體也是在這個位置了,找找看!!!
LRESULT CALLBACK WindowProc
(
HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );
這個函數咱們的第二隻小板凳裏被咱們稱爲WndProc.
不知你注意到了沒有,這個函數的參數與剛剛提到的GetMessage調用把返回的MSG結構的前四個成員相同。若是消息處理成功,WindowProc的返回值爲0.
Windows的啓動應用程序時,先調用WinMain函數,而後調用窗口過程,注意:在咱們的這個程序中,只有一個窗口過程,實際上,也許有不止一個的窗口過程。例如,每個不一樣的窗口類都 有一個與之相對應的窗口過程。不管Windows什麼時候想傳遞一個消息到一窗口,都將調用相應的窗口過程。當Windows從環境,或從另外一個應用程序,或從用戶的應用程序中獲得消息時,它將調用窗口過程並將信息傳給此函數。總之,窗口過程函數處理全部傳送到由此窗口類建立的窗口所獲得的消息。而且窗口過程有義務處理Windows扔給它的任何消息。咱們在學習Windows程序設計的時候,最主要的就是學習這些消息是什麼以及是什麼意思,它們是怎麼工做的。
令咱們不解的是,在程序中咱們看不出來是哪個函數在調用窗口過程。它實際上是一個回調函數.前面已經提到,Windows把發生的輸入事件轉換成輸入消息放到消息隊列中,而消息循環將它們發送到相應的窗口過程函數,真正的處理是在窗口過程函數中執行的,在Windows中就使用了回調函數來進行這種通訊。
回調函數是輸出函數中特殊的一種,它是指那些在Windows環境下直接調用的函數。一個應用程序至少有一個回調函數,由於在應用程序處理消息時,Windows調用回調函數。這種回調函數就是咱們前面提到的窗口過程,它對對應於一個活動的窗口,回調函數必須向Windows註冊,Windows實施相應操做即行回調。
每一個窗口必須有一個窗口過程與之對應,且Windows直接調用本函數,所以,窗口函數必須採用FAR PASCAL調用約定。在咱們的第二隻小板凳中,咱們的窗口函數爲WndProc,必須注意這裏的函數名必須是前面註冊的窗口類時,向域wc.lpfnWndProc所賦的WndProc。函數WndProc就是前面定義的窗口類所生成的全部窗口的窗口函數。
在咱們的這個窗口函數中,WndProc處理了共有兩條消息:WM_PAINT和WM_DESTROY.
窗口函數從Windows中接收消息,這些消息或者是由WinMain函數發送的輸入消息,或者是直接來自Windows的窗口管理消息。窗口過程檢查一條消息,而後根據這些消息執行特定的動做未被處理的消息經過DefWindowProc函數傳回給Windows做缺海上處理。
能夠發送窗口函數的消息約有220種,全部窗口消息都以WM_開頭,這些消息在頭文件中被定義爲常量。引發Windows調用窗口函數的緣由有不少,,如改變窗口大小啊,改變窗口在屏幕上的位置啊什麼的。
Windows已經把任務扔給窗口過程了,窗口過程是怎麼處理消息的呢?稍息一下,讓咱們進行下一節:處理消息……
注:可能你看這些東西的時候有些亂,不過不要緊,這很正常,多看幾下MSDN就慢慢明白了,有我寫這個專題的時候,不少概念也太不清楚,不過等我查資料寫下來後,感受漸漸有些東西也有了點眉目,由於這自己也是個進步的過程。 —小朱
(七)處理消息
窗口過程處理消息一般以switch語句開始,對於它要處理的每一條消息ID都跟有一條case語句。大多數windows proc都有具備下面形式的內部結構:
switch(uMsgId) { case WM_(something): //這裏此消息的處理過程 return 0; case WM_(something else): //這裏是此消息的處理過程 ruturn 0; default: //其餘消息由這個默認處理函數來處理 return DefWindowProc(hwnd,uMsgId,wParam,lParam); }
在處理完消息後,要返回0,這很重要—–它會告訴Windows沒必要再重試了。對於那些在程序中不許備處理的消息,窗口過程會把它們都扔給DefWindowProc進行缺省處理,並且還要返回那個函數的返回值。在消息傳遞層次中,能夠認爲DefWindowProc函數是最頂層的函數。這個函數發出WM_SYSCOMMAND消息,由系統執行Windows環境中多數窗口所公用的各類通用操做,例如,畫窗口的非用戶區,更新窗口的正文標題等等等等。
再提示一下,以WM_的消息在Windows頭文件中都被定義成了常量,如WM_QUIT=XXXXXXXXXXX,但咱們沒有必要記住這個數值,也不可能記得住,咱們只要知道WM_QUIT就OK了。
在第二隻小板凳中咱們只讓窗口過程處理了兩個消息:一個是WM_PAINT,另外一個是WM_DESTROY,先說說第一個消息—WM_PAINT.
不管什麼時候Windows要求重畫當前窗口時,都會發該消息。也能夠這樣說:不管什麼時候窗口非法,都必須進行重畫。 哎呀,什麼又是」非法窗口」?什麼又是重畫啊?你這人有沒有完,嗯?
稍安勿燥,我比你還煩呢?我午餐到如今還沒吃呢!你有點耐心,來點專業精神好很差???我開始在MSDN裏面找有關這個方面的內容了,別急,我找找看:
Platform SDK–>Graphics and Multimedia Services–>Windows GDI–>Painting and Drawing–>Using the WM_PAINT Message—–終於找到了。
讓咱們把Windows的屏幕想像成一個桌面,把一個窗口想像成一張紙。當咱們把一張紙放到桌面上時,它會蓋住其餘的紙,這樣被蓋住的其餘紙上的內容都看不到了。但咱們只要把這張紙移開,被蓋住的其餘紙上的內容就會顯示出來了—這是一個很簡單的道理,誰都明白。
對於咱們的屏幕來講,當一個窗口被另外一窗口蓋住時,被蓋住的窗口的某些部分就看不到了,咱們要想看到被蓋住的窗口的所有面貌,就要把另外一個窗口移開,可是當咱們移開後,事情卻起了變化—–極可能這個被蓋住的窗口上的信息被擦除了或是丟失了。當窗口中的數據丟失或過時時,窗口就變成非法的了—或者稱爲」無效」。因而咱們的任務就來了,咱們必須考慮怎樣在窗口的信息丟失時」重畫窗口」–使窗口恢復成之前的那個樣子。這也就是咱們在這第二隻小板凳中調用UpdateWindow的緣由。
你忘記了嗎?剛纔咱們在(三)顯示和更新窗口中有下面的一些文字:
WinMain()調用完ShowWindow後,還須要調用函數UpdateWindow,最終把窗口顯示了出來。調用函數UpdateWindow將產生一個WM_PAINT消息,這個消息將使窗口重畫,即便窗口獲得更新.—這是程序第一次調用了這條消息。
爲從新顯示非法區域,Windows就發送WM_PAINT消息實現。要求Windows發送WM_PAINT的狀況有改變窗口大小,對話框關閉,使用了UpdateWindows和ScrollWindow函數等。這裏注意,Windows並不是是消息WM_PAINT的惟一來源,使用InvalidateRect或InvalidateRgn函數也能夠產生繪圖窗口的WM_PAINT消息……
一般狀況下用BeginPaint()來響應WM_PAINT消息。若是要在沒有WM_PAINT的狀況下重畫窗口,必須使用GetDC函數獲得顯示緩衝區的句柄。這裏面再也不擴展。詳細見MDSN。
這個BeginPaint函數會執行準備繪畫所需的全部步驟,包括返回你用於輸入的句柄。結束則是以EndPaint();
在調用完BeginPaint以後,WndProc接着調用GetClientRect:
GetClientRect(hwnd,&rect);
第一個參數是程序窗口的句柄。第二個參數是一個指針,指向一個RECT類型的結構。查MSDN,可看到這個結構有四個成員。
WndProc作了一件事,他把這個RECT結構的指針傳送給了DrawText的第四個參數。函數DrawText的目的就是在窗口上顯示一行字—-「你好,歡迎你來到VC之路!」,有關這個函數的具體用法這裏也不必說了吧。
這個消息要比WM_PAINT消息容易處理得多:只要用戶關閉窗口,就會發送WM_DESTROY消息(在窗口從屏幕上移去後)。
程序經過調用PostQuitMessage以標準方式響應WM_DESTROY消息:
PostQuitMessage (0) ;
這個函數在程序的消息隊列中插入一個WM_QUIT消息。在(四)建立消息循環中咱們曾有這麼一段話:
消息循環以GetMessage調用開始,它從消息隊列中取出一個消息:
在接收到除WM_QUIT以外的任何一個消息後,GetMessage()都返回TRUE。若是GetMessage收到一個WM_QUIT消息,則返回FALSE,如收到其餘消息,則返回TRUE。所以,在接收到WM_QUIT以前,帶有GetMessage()的消息循環能夠一直循環下去。只有當收到的消息是WM_QUIT時,GetMessage才返回FALSE,結束消息循環,從而終止應用程序。
主要介紹了Win32 API主消息循環的兩種處理方法:使用GetMessage方法構造主消息循環、使用PeekMessage方法構造主消息循環。
通常應用程序都使用用GetMessage方法構造主消息循環,該方法是得到一條線程 的消息。對於VS2005自動生成的Win32 Windows程序上面有些不足。
由於VS2005生成的主消息循環以下;
// Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
簡單看看的確沒有問題,可是當咱們去查閱MSDN文檔看到GetMessage消息時候能夠看到這樣一段
If there is an error, the return value is -1.
因此咱們應該把上面這個主循環修改成下面這樣的形式,增長一個臨死變量。
// Main message loop: BOOL bRet;//臨時變量,存儲GetMessage方法返回值 // Main message loop: while ((bRet = GetMessage(&msg, NULL, 0, 0))!=0) { if(bRet==-1) { //表示GetMessage得到的信息有錯誤 } else { TranslateMessage(&msg); DispatchMessage(&msg); } }
(2)使用PeekMessage方法構造主消息循環
PeekMessage經常用於Windows開發遊戲中,PeekMessage在處理得到消息時候和GetMessage同樣,關鍵不一樣的是PeekMessage在沒有消息處理的時候還會繼續保持循環激活狀態,而且繼續佔用資源。
// Main message loop: while (true) { if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { if(msg.message == WM_QUIT) { break; } else //表示GetMessage得到的信息有錯誤 { TranslateMessage(&msg); DispatchMessage(&msg); } } else { //循環處理的函數 } }
1:Win32是個多任務搶佔式操做系統,每運行一個程序(可執行文件),操做系統就建立一個進程和主線程,把程序的代碼和數據映射到該進程地址空間,併爲每一個線程分配了一個時間片,一個線程放棄CPU的處理權有、能夠是時間片完了,I/O請求,還有就是程序本身要求放棄處理權,而GetMessage函數是一個阻塞函數,也就是你調用他就至關於主動放棄了CPU,引發線程上下文切換,從而其餘線程能夠獲得CPU,但該函數會在有消息的時間激活而繼續執行。若是你是獲取消息用PeekMessage函數,那麼你打開任務管理器,才知道什麼叫作真正的浪費資源;
2:
while(1) { id=getMessage(...); if(id == quit) break; translateMessage(...); }
當該程序沒有消息通知時getMessage就不會返回,也就不會佔用系統的CPU時間。
在16位的系統中系統中只有一個消息隊列,因此係統必須等待當前任務處理消息後才能夠發送下一消息到相應程序,若是一個程序陷如死循環或是耗時操做時系統就會得不到控制權。這種多任務系統也就稱爲協同式的多任務系統。Windows3.X就是這種系統。
而32位的系統中每一運行的程序都會有一個消息隊列,因此係統能夠在多個消息隊列中轉換而沒必要等待當前程序完成消息處理就能夠獲得控制權。這種多任務系統就稱爲搶先式的多任務系統。Windows95/Windows98/NT就是這種系統。
3:曾有這樣的疑問,爲何不少資料中都有關於windows中的While(getmessage(&msg,Null,0,0)){..}
消息循環不佔用CPU的說法?今天特有關此事查了一下資料,原來是這樣子啊!
說,其實這裏的while(){}循環是佔用cpu的,只是getmessage()是一個阻塞型的函數,當消息隊列中沒有消息時,它會檢查確認,當確認消息隊列爲空時,則進行V操做,從而使線程外於阻塞狀態,不被激發,另外咱們知道外於sleep狀態的線程是不佔cpu的,是故當getmessage無返回值時,while()也不執行。整個線程被阻塞,從而不佔用CPU資源。
當Winows程序啓動時,會註冊一個窗口類,註冊的窗口類中包括當前窗口的風格、消息處理函數等等。而後,程序建立一個該註冊窗口類的主窗口,接着,顯示這個主窗口並進入到消息循環。在消息循環中,將不斷地從窗口自身的消息隊列中讀取消息,並調用註冊的窗口消息處理函數對不一樣的消息進行處理。
GetMessage函數是一個阻塞型的函數,當消息隊列中沒有消息時,GetMessage會處於阻塞狀態。一旦有消息到達,進程會被喚醒,GetMessage立刻返回。實現時,使用了一個信號量, GetMessage函數在肯定沒有消息可讀時,對這個信號量進行一個V操做,從而使線程阻塞。而PostMessage、SendNotifyMessage、SendSyncMessage等任何一個發送消息函數在發送完消息以後,都會讀取這個信號量的值,當發現這個值等於零時,即表示讀消息的線程當前已阻塞,這時就會做一次P操做,來喚醒睡眠的線程。
PeekMessage 返回 TRUE 的條件是有消息,若是沒有消息返回 FALSE
GetMessage 返回 TRUE 的條件是有消息且該消息不爲 WM_QUIT
返回 FALSE 的條件是有消息且該消息 爲 WM_QUIT
GetMessage不將控制傳回給程序,直到從程序的消息隊列中取得消息,可是PeekMessage老是馬上傳回,而不論一個消息是否出現。當消息隊列中有一個消息時,PeekMessage的傳回值爲TRUE(非0),而且將按一般方式處理消息。當隊列中沒有消息時,PeekMessage傳回FALSE(0)。
這使得咱們能夠改寫普通的消息循環。咱們能夠將以下所示的循環:
while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ;
替換爲下面的循環:
while (TRUE) { if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break ; TranslateMessage (&msg) ; DispatchMessage (&msg) ; } else { // 完成某些工做的其它行程序 } } return msg.wParam ;
注意,WM_QUIT
消息被另外挑出來檢查。在普通的消息循環中您沒必要這麼做,由於若是GetMessage
接收到一個WM_QUIT
消息,它將傳回0,可是PeekMessage
用它的傳回值來指示是否獲得一個消息,因此須要對WM_QUIT
進行檢查。
若是PeekMessage
的傳回值爲TRUE
,則消息按一般方式進行處理。若是傳回值爲FALSE
,則在將控制傳回給Windows以前,還能夠做一點工做(如顯示另外一個隨機矩形)。
(儘管Windows文件上說,您不能用PeekMessage從消息隊列中刪除WM_PAINT
消息,可是這並非什麼大不了的問題。畢竟,GetMessage
並不從消息隊列中刪除WM_PAINT
消息。從隊列中刪除WM_PAINT
消息的惟一方法是令窗口顯示區域的失效區域變得有效,這能夠用ValidateRect
和ValidateRgn
或者BeginPaint
和EndPaint
對來完成。若是您在使用PeekMessage
從隊列中取出WM_PAINT
消息後,同日常同樣處理它,那麼就不會有問題了。所不能做的是使用以下所示的程序代碼來清除消息隊列中的全部消息:
while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) ;
這行敘述從消息隊列中刪除WM_PAINT
以外的全部消息。若是隊列中有一個WM_PAINT
消息,程序就會永遠地陷在while循環中。
咱們也能夠說,PeekMessage是一個具備線程異步行爲的函數,無論消息隊列中是否有消息,函數都會當即返回。而GetMessage則是一個具備線程同步行爲的函數,若是消息隊列中沒有消息的話,函數就會一直等待,直到消息隊列中至少有一條消息時才返回。
若是消息隊列中沒有消息,PeekMessage老是能返回,這就至關於在執行一個循環,若是消息隊列一直爲空, 它就進入了一個死循環。GetMessage則不可能由於消息隊列爲空而進入死循環。
在Windows的內部,兩個函數執行着相同的代碼。
具體狀況具體分析,沒法說明到底哪個更好一些,這要根據實際的應用狀況而定。
SendMessage、PostMessage原理
本文講解SendMessage、PostMessage兩個函數的實現原理,分爲三個步驟進行講解,分別適合初級、中級、高級程序員進行理解,三個步驟分別爲:
一、SendMessage、PostMessage的運行機制。
二、SendMessage、PostMessage的運行內幕。
三、SendMessage、PostMessage的內部實現。
注:理解這篇文章以前,必須先了解Windows的消息循環機制。
咱們先來看最簡單的。
SendMessage能夠理解爲,SendMessage函數發送消息,等待消息處理完成後,SendMessage才返回。稍微深刻一點,是等待窗口處理函數返回後,SendMessage就返回了。
PostMessage能夠理解爲,PostMessage函數發送消息,不等待消息處理完成,馬上返回。稍微深刻一點,PostMessage只管發送消息,消息有沒有被送到則並不關心,只要發送了消息,便馬上返回。
對於寫通常Windows程序的程序員來講,可以這樣理解也就足夠了。但SendMessage、PostMessage真的是一個發送消息等待、一個發送消息不等待嗎?具體細節,下面第2點將會講到。
在寫通常Windows程序時,如上第1點講到的足以應付,其實咱們能夠看看MSDN來肯定SendMessage、PostMessage的運行內幕。
在MSDN中,SendMessage解釋如爲:The SendMessage function sends the specified message to a window or windows. It calls the window procedure for the specified window and does not return until the window procedure has processed the message.
翻譯成中文爲:SendMessage函數將指定的消息發到窗口。它調用特定窗口的窗口處理函數,而且不會當即返回,直到窗口處理函數處理了這個消息。
再看看PostMessage的解釋:The PostMessage function places (posts) a message in the message queue associated with the thread that created the specified window and returns without waiting for the thread to process the message.
翻譯成中文爲:PostMessage函數將一個消息放入與建立這個窗口的消息隊列相關的線程中,並馬上返回不等待線程處理消息。
仔細看完MSDN解釋,咱們瞭解到,SendMessage的確是發送消息,而後等待處理完成返回,但發送消息的方法爲直接調用消息處理函數(即WndProc函數),按照函數調用規則,確定會等消息處理函數返回以後,SendMessage才返回。而PostMessage卻沒有發送消息,PostMessage是將消息放入消息隊列中,而後馬上返回,至於消息什麼時候被處理,PostMessage徹底不知道,此時只有消息循環知道被PostMessage的消息什麼時候被處理了。
至此咱們撥開了一層疑雲,原來SendMessage只是調用咱們的消息處理函數,PostMessage只是將消息放到消息隊列中。
[1]關於如何設置,讓VS2005載入Symbol,能夠查看我寫的另一篇文章:「讓Visual Studio載入Symbol(pdb)文件」,地址:http://blog.csdn.net/xt_xiaotian/archive/2010/03/16/5384111.aspx
原創博客:
http://blog.chinaunix.net/uid-20496675-id-1664090.html
http://blog.itpub.net/7668308/viewspace-853757/
http://zhidao.baidu.com/link?url=fQT7pgXRKoFnpQlzYRjhdqJtPe8E1Lp1G8t1M0Sg8lm-mfNC5zF2D83FFxwWtJA5UL_E81lHT9uxuITlMIlLg_
http://blog.sina.com.cn/s/blog_4ba53587010007lb.html
http://www.cnblogs.com/scope/archive/2009/06/14/1503088.html
http://blog.csdn.net/gencheng/article/details/9376881
http://blog.csdn.net/xt_xiaotian/article/details/5384137