程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Win32消息循環機制等【轉載】http://blog.csdn.net/u013777351/article/details/49522219,u01377735149522219

Win32消息循環機制等【轉載】http://blog.csdn.net/u013777351/article/details/49522219,u01377735149522219

編輯:C++入門知識

Win32消息循環機制等【轉載】http://blog.csdn.net/u013777351/article/details/49522219,u01377735149522219


Dos的過程驅動與Windows的事件驅動

在講本程序的消息循環之前,我想先談一下Dos與Windows驅動機制的區別:

DOS程序主要使用順序的,過程驅動的程序設計方法。順序的,過程驅動的程序有一個明顯的開始,明顯的過程及一個明顯的結束,因此程序能直接控制程序事件或過程的順序。雖然在順序的過程驅動的程序中也有很多處理異常的方法,但這樣的異常處理也仍然是順序的,過程驅動的結構。

而Windows的驅動方式是事件驅動,就是不由事件的順序來控制,而是由事件的發生來控制,所有的事件是無序的,所為一個程序員,在你編寫程序時,你並不知道用戶先按哪個按紐,也不知道程序先觸發哪個消息。你的任務就是對正在開發的應用程序要發出或要接收的消息進行排序和管理。事件驅動程序設計是密切圍繞消息的產生與處理而展開的,一條消息是關於發生的事件的消息。

Windows編程的特點:

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完成指定操作,而承擔這項通信任務的API函數就是Windows的相應窗口函數WindowProc。在dos裡,程序能直接控制事件的發生順序,結果等。而在Windows裡,應用程序不直接調用任何窗口函數,而是等待Windows調用窗口函數,請求完成任務或返回信息。為保證Windows調用這個窗口函數,這個函數必須先向Windows登記,然後在Windows實施相應操作時回調,所以窗口函數又稱為回調函數。WindowProc是一個主回調函數,Windows至少有一個回調函數。

回調函數WindowProc在哪裡定義的呢,請看這個語句:wc.lpfnWndProc = WindowProc ;將在第七講裡詳談.

實例:在Windows中,能多次同時運行同一個應用程序,即運行多個副本,每個副本叫做一個“實例”。

現在讓我們把這個程序層層剝解開來,我把自己的理解慢慢地展示給你:

Win32編程步驟:

我把這個程序支解為四塊:(一)建立,注冊窗口類.(二)創建窗口.(三)顯示和更新窗口.(四)創建消息循環.(五)終止應用程序.(六)窗口過程.(七)處理消息.

(一)注冊窗口類:

(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.

關於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_DESTROY

這個消息要比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主消息循環的兩種處理方法

主要介紹了Win32 API主消息循環的兩種處理方法:使用GetMessage方法構造主消息循環、使用PeekMessage方法構造主消息循環。

使用GetMessage方法構造主消息循環

一般應用程序都使用用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
    {
        //循環處理的函數
    }
}

Win32消息循環是一個死循環嗎?如果是消息循環為什麼不會耗盡CPU?

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程序啟動時,會注冊一個窗口類,注冊的窗口類中包括當前窗口的風格、消息處理函數等等。然後,程序創建一個該注冊窗口類的主窗口,接著,顯示這個主窗口並進入到消息循環。在消息循環中,將不斷地從窗口自身的消息隊列中讀取消息,並調用注冊的窗口消息處理函數對不同的消息進行處理。

關於Windows中的系統消息循環占用CPU的疑問

GetMessage函數是一個阻塞型的函數,當消息隊列中沒有消息時,GetMessage會處於阻塞狀態。一旦有消息到達,進程會被喚醒,GetMessage馬上返回。實現時,使用了一個信號量, GetMessage函數在確定沒有消息可讀時,對這個信號量進行一個V操作,從而使線程阻塞。而PostMessage、SendNotifyMessage、SendSyncMessage等任何一個發送消息函數在發送完消息之後,都會讀取這個信號量的值,當發現這個值等於零時,即表示讀消息的線程當前已阻塞,這時就會作一次P操作,來喚醒睡眠的線程。

GetMessage與PeekMessage的區別

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消息的唯一方法是令窗口顯示區域的失效區域變得有效,這可以用ValidateRectValidateRgn或者BeginPaintEndPaint對來完成。如果您在使用PeekMessage從隊列中取出WM_PAINT消息後,同平常一樣處理它,那麼就不會有問題了。所不能作的是使用如下所示的程序代碼來清除消息隊列中的所有消息:

while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) ;

 

這行敘述從消息隊列中刪除WM_PAINT之外的所有消息。如果隊列中有一個WM_PAINT消息,程序就會永遠地陷在while循環中。

PeekMessage和GetMessage函數的主要區別有:

我們也可以說,PeekMessage是一個具有線程異步行為的函數,不管消息隊列中是否有消息,函數都會立即返回。而GetMessage則是一個具有線程同步行為的函數,如果消息隊列中沒有消息的話,函數就會一直等待,直到消息隊列中至少有一條消息時才返回。

如果消息隊列中沒有消息,PeekMessage總是能返回,這就相當於在執行一個循環,如果消息隊列一直為空, 它就進入了一個死循環。GetMessage則不可能因為消息隊列為空而進入死循環。

在Windows的內部,兩個函數執行著相同的代碼。 
具體情況具體分析,無法說明到底哪一個更好一些,這要根據實際的應用情況而定。

SendMessage、PostMessage原理

本文講解SendMessage、PostMessage兩個函數的實現原理,分為三個步驟進行講解,分別適合初級、中級、高級程序員進行理解,三個步驟分別為:

1、SendMessage、PostMessage的運行機制。

2、SendMessage、PostMessage的運行內幕。

3、SendMessage、PostMessage的內部實現。

注:理解這篇文章之前,必須先了解Windows的消息循環機制。

SendMessage、PostMessage原理

1、SendMessage、PostMessage的運行機制

我們先來看最簡單的。

SendMessage可以理解為,SendMessage函數發送消息,等待消息處理完成後,SendMessage才返回。稍微深入一點,是等待窗口處理函數返回後,SendMessage就返回了。

PostMessage可以理解為,PostMessage函數發送消息,不等待消息處理完成,立刻返回。稍微深入一點,PostMessage只管發送消息,消息有沒有被送到則並不關心,只要發送了消息,便立刻返回。

對於寫一般Windows程序的程序員來說,能夠這樣理解也就足夠了。但SendMessage、PostMessage真的是一個發送消息等待、一個發送消息不等待嗎?具體細節,下面第2點將會講到。

2、SendMessage、PostMessage的運行內幕

在寫一般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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved