1. 程式人生 > >VC程式設計Windows訊息處理機制、阻塞試驗、SetTimer、MessageBox、小心訊息響應處理函式

VC程式設計Windows訊息處理機制、阻塞試驗、SetTimer、MessageBox、小心訊息響應處理函式

VC6標準WIN32程式,Windows訊息處理機制:

1.在註冊視窗類時,指定了訊息處理函式WndProc()。

2.WinMain()裡有訊息迴圈:

    while (GetMessage(&msg, NULL, 0, 0))

    {

       if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

       {

           TranslateMessage(&msg);

           DispatchMessage(&msg);

       }

    }

3.在WndProc()裡,對各個訊息進行處理!!

  • 訊息佇列是執行緒所有,並非視窗所有和程序所有。
  • 一個執行緒的所有視窗,比如父視窗(主視窗)、對話方塊、訊息框等都是使用這個執行緒的同一個訊息佇列!!!!
  • 視窗有訊息迴圈。窗口裡的控制元件應該就沒有訊息迴圈了吧。

試驗:

一、MFC中,用TIMER,發現,在TIMER響應函式中,用MessageBox(),則會不斷地出現MessageBox。

而且當前響應函式會停止在MessageBox上,直到MessageBox返回才繼續執行後面的語句!

用SendMessage或PostMessage來試訊息響應,也是一樣:響應函式中有MessageBox(),則會出現多個MessageBox。

說明MessageBox阻塞了函式,卻沒有阻塞訊息分發。

二、VC建WIN32程式(標準HELLO WORLD),試驗發現,如果WndProc()裡有MessageBox,當前的WndProc()會暫時停止在MessageBox,直到MessageBox返回才繼續執行後面的語句!!

如果MessageBox出現,這時一個WM_PAINT出現了,則會執行一個新的WndProc()。即WndProc()並行出現了!!執行緒只有一個,但WndProc()可以出現多個!

用ABOUT對話方塊代替MessageBox,情況也是一樣!ABOUT對話框出現時,父視窗(即主程式)點不出來,但父視窗WM_PAINT能正常執行!!!

即像別的文章說的,對話方塊和訊息框會有自己的訊息迴圈,但執行緒是同一個執行緒,所以執行緒的所有訊息會被分發處理。

三、再試下面程式,

void CUnicodewcharDlg::OnButton4()

{

    SendMessage(UM_SOCK);      // UM_SOCK為自定義訊息,已經和OnRecv相關聯

}

BOOL running=FALSE;

void CUnicodewcharDlg::OnRecv()

{

    if (running==TRUE) {::MessageBox(NULL,"ERROR","",MB_OK); return;}

    running=TRUE;

    ::MessageBox(NULL,"OK","",MB_OK);

::MessageBox(NULL,"返回","",MB_OK);

    running=FALSE;

}

發現,按OnButton4後,先出現一個"OK"訊息框,再點OnButton4,就出現"ERROR"訊息框。

如果"OK"訊息框關閉了,則不會出現"返回"訊息框,再點OnButton4,仍是出現"ERROR"訊息框!!!!!!!

一定要"ERROR"訊息框和"OK"訊息框都關閉了,才會出現"返回"訊息框,再點OnButton4,才會出現"OK"訊息框!!

說明是:

父窗訊息迴圈,呼叫訊息響應函式生成了第一個訊息框,同時父窗呼叫的訊息響應函式阻塞,父窗訊息迴圈阻塞;第一個訊息框開始接手負責訊息迴圈(是自己有訊息迴圈還是把父窗的訊息迴圈重入了繼續使用?)。

第一個訊息框訊息迴圈,呼叫訊息響應函式生成了第二個訊息框,同時第一個訊息框呼叫的訊息響應函式阻塞,第一個訊息框訊息迴圈阻塞;第二個訊息框開始接手負責訊息迴圈(是自己有訊息迴圈還是把父窗的訊息迴圈重入了繼續使用?)。

第二個訊息框訊息迴圈。。。

即:父窗阻塞->第一個訊息框阻塞->第二個訊息框

如果第一個訊息框關閉,但第二個訊息框未關閉,則第一個訊息框呼叫的訊息響應函式仍在阻塞中,故父窗呼叫的訊息響應函式也在阻塞中,所以繼續執行不下去,而第二個訊息框仍在進行訊息迴圈。只有第二個訊息框關閉了,第一個訊息框呼叫的訊息響應函式才會繼續執行並返回,然後父窗呼叫的訊息響應函式才會繼續執行並返回!!!

第二個訊息框訊息迴圈,收到父窗WM_PAINT,呼叫父窗訊息響應函式,WM_PAINT這一部分如果沒有阻塞,則訊息響應函式可以馬上返回。再進行下一個訊息處理。所以父窗不會因為沒有重新整理而失去顯示,否則就顯示不正常了。

訊息響應函式可以同時多次進入,比如父窗呼叫了阻塞了,第一個訊息框又呼叫了阻塞了,第二個訊息框又呼叫了。。。

三、試驗子視窗是自己有訊息迴圈還是把父窗的訊息迴圈重入了繼續使用?

用WIN32(標準HELLO WORLD)程式來試:

在WinMain()中:

    while (GetMessage(&msg, NULL, 0, 0))

    {

       if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

       {

           TranslateMessage(&msg);

           if(msg.message ==WM_COMMAND) WinExec("cmd /k echo TranslateMessage()ok",SW_SHOW);      //加上這句

           DispatchMessage(&msg);

           if(msg.message ==WM_COMMAND) WinExec("cmd /k echo DispatchMessage()ok",SW_SHOW);       //加上這句

       }

    }

在WndProc()中:

case IDM_ABOUT:

       //DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);  //這句遮蔽掉

       MessageBox(NULL,"in about","",MB_OK);  //加上這句,這樣父窗就可以點出來了

發現,點選單的ABOUT..., WinExec(顯示TranslateMessage()ok)執行,顯示出MessageBox;MessageBox關閉後,WinExec(顯示DispatchMessage()ok)才會執行。    OK,符合正常的訊息處理原則,即DispatchMessage()要等到WndProc()執行完才會返回。

重新開始試:

1. 點選單的ABOUT..., WinExec(顯示TranslateMessage()ok)執行,顯示出第一個MessageBox。

2. 再到主窗,點選單的ABOUT..., 顯示出第二個MessageBox,但WinExec(顯示TranslateMessage()ok)沒有執行!!

3. 關閉第一個MessageBox,WinExec(顯示DispatchMessage()ok)仍沒有執行。  

4. 最後,關閉第二個MessageBox,DispatchMessage()ok)才執行。

結論:

對話方塊、訊息框等子視窗是用自已預設的訊息迴圈,而不是重入到父窗的訊息迴圈!!

主窗阻塞了,第1個子窗負責訊息迴圈,呼叫執行緒的訊息響應函式(包括主窗、第1個子窗的訊息響應函式);

第1個子窗阻塞了,第2個子窗負責訊息迴圈,呼叫執行緒的訊息響應函式(包括主窗、第1個子窗、第2個字窗的訊息響應函式);。。。

子窗是一級一級阻塞下去的,後一個子窗關閉了,才能使前一個子窗的訊息響應函式解除阻塞。

如果處在中間的子窗關閉了,它的訊息響應函式仍還在阻塞中!只有它後面的子窗都關閉了,它的訊息響應函式才會返回。

所以,如果是基於訊息來處理事件,比如,網路SOCKET非同步程式設計,訊息處理函式中,有用到訊息框MessageBox或對話方塊,則要小心,以免多個事件訊息同時發生。因為訊息框出現後,當前訊息處理函式阻塞了,但訊息迴圈仍在進行,如果有新的訊息事件產生,則會執行新的訊息處理函式,則有可能會產生不希望的後果。

四、後來,試驗網路SOCKET非同步程式設計,確實,彈出MessageBox後,訊息迴圈仍在繼續,如果有收到FD_READ、FD_ACCEPT等,都會執行訊息處理函式。

如果訊息處理函式中彈出MessageBox,這時下一個訊息來了,則會再執行一個新的訊息處理函式!而不是阻塞。即訊息處理函式同時被多次呼叫,多個並存!

===================================

下面引用網路的文章:

===================================

DispatchMessage

2008年10月18日 星期六 14:33

windows訊息處理機制是這樣的:

首先系統(也就是windows)把來自硬體(滑鼠,鍵盤等訊息)和來自應用程式的訊息 放到一個系統訊息佇列中去。

而應用程式需要有自己的訊息佇列,也就是執行緒訊息佇列,每一個執行緒有自己的訊息佇列,對於多執行緒的應用程式就有和執行緒數目相等的執行緒訊息佇列。

winsows訊息佇列把得到的訊息傳送到執行緒訊息佇列,執行緒訊息佇列每次取出一條訊息傳送到指定視窗,不斷迴圈直到程式退出。這個迴圈就是靠訊息環(while(GetMessage()) TranslateMessage();DispatchMessage(); 實現的。

GetMessage()只是從執行緒訊息中取出一條訊息,而DispatchMessage 則把取出的訊息傳送到目的視窗。如果收到WM_CLOSE訊息則結束迴圈,傳送postqiutmessage(0),處理WM_DESTROY銷燬視窗! 

其實問題的關鍵在於:DispatchMessage到底幹了什麼?

如果只是去呼叫相應的視窗,那自己寫個switch不就可以了 ?

DispatchMessage與switch不同之處在於DispatchMessage會 先呼叫windows,進入管態(大概是range 0),然後再由windows呼叫 視窗的函式。

為什麼這麼麻煩?

因為這樣windows就可以知道你的程式執行到什麼情況了, windows來呼叫你的視窗,這樣你的視窗返回的時候,windows就知道你已經處理過一個訊息了,如果沒有新的訊息進入訊息佇列,windows就不再會給你的程序分配時間片。

如果是你自己寫switch的話,windows就不可能這樣靈活的分配時間,資源利用率就會降低。

那麼還要訊息迴圈幹什麼,windows直接把訊息發給視窗不就可以了嗎?

因為你要在訊息迴圈裡把KEY_DOWN和KEY_UP組合成WM_CHAR,還可以直接遮蔽掉許多對你來說無用的訊息,加快速度。

GetMessage:      從執行緒的訊息佇列取出一個訊息 。GetMessage是從系統為每個應用程式自動分配的訊息對列的頭部得到一個訊息。  

TranslateMessage:     將msg結構傳給Windows,進行一些轉換,比如A鍵按下,轉換成WM_CHAR訊息等 。TranslateMessage是對一些鍵盤事件做預處理。TranslateMessage是翻譯需要翻譯的訊息。  翻譯訊息不是簡單的轉換,一個訊息被翻譯後,可能會產生幾個訊息。

DispatchMessage():      再將msg結構傳給Windows,Windows將該訊息發給視窗過程,由視窗過程處理。  DispatchMessage()則會把翻譯好的訊息傳送到系統的訊息處理函式中,而這個函式又會把這個訊息傳遞到註冊窗體時使用者指定的訊息處理函式中

前面已經介紹從系統佇列裡獲取一條訊息,然後經過快捷鍵的函式檢查,又通過字元訊息函式的轉換,最後要做的事情就是呼叫DispatchMessage函式,它的意思就是說要把這條訊息傳送到窗口裡的訊息處理函式WindowProc。

===================================

訊息的迴圈過程大致為(關於訊息的具體情況不再說明)

 1. 訊息迴圈呼叫GetMessage()從訊息佇列中查詢訊息進行處理,如果訊息佇列為空,程式將停止執行並等待(程式阻塞)。

 2. 事件發生時導致一個訊息加入到訊息佇列(例如系統註冊了一個滑鼠點選事件),GetMessage()將返回一個正值,這表明有訊息需要被處理,並且訊息已經填充到傳入的MSG引數中;當傳入WM_QUIT訊息時返回0;如果返回值為負表明發生了錯誤。

    3. 取出訊息(在Msg變數中)並將其傳遞給TranslateMessage()函式,這個函式做一些額外的處理:將虛擬鍵值資訊轉換為字元資訊。這一步實際上是可選的,但有些地方需要用到這一步。

    4. 上面的步驟執行完後,將訊息傳遞給DispatchMessage()函式。DispatchMessage()函式將訊息分發到訊息的目標視窗,並且查詢目標視窗過程函式,給視窗過程函式傳遞視窗控制代碼、訊息、wParam、lParam等引數然後呼叫該函式。

   5. 在視窗過程函式中,檢查訊息和其他引數,你可以用它來實現你想要的操作。如果不想處理某些特殊的訊息,你應該總是呼叫DefWindowProc()函式,系統將按按預設的方式處理這些訊息(通常認為是不做任何操作)。

   6. 一旦一個訊息處理完成,視窗過程函式返回,DispatchMessage()函式返回,繼續迴圈處理下一個訊息。

================================

DispatchMessage將訊息分發到視窗函式中,請問:DispatchMessage是直接返回還是等待WndProc處理完畢再返回?

用::DiapatchMessage派送訊息,在視窗處理過程(WinProc,視窗函式)返回之前,他是阻塞的,不會立即返回,也就是訊息迴圈此時不能再從訊息佇列中讀取訊息,直到::DispatchMessage返回。如果你在視窗函式中執行一個死迴圈操作,就算你用   PostQuitMessage函式退出,程式也會down掉。

while(1)

{

        PostQuitMessage(0);   //程式照樣down.

}

    所以,當視窗函式處理沒有返回的時候,訊息迴圈是不會從訊息佇列中讀取訊息的。這也是為什麼在模式對話方塊中要自己用無限迴圈來繼續訊息迴圈,因為這個無限迴圈阻塞了原來的訊息迴圈,所以,在這個無限迴圈中要用GetMessage,PeekMessage,DispatchMessage來從訊息佇列中讀取訊息並派送訊息了。要不然程式就不會響應了,這不是我們所希望的。

模式對話方塊是卡住了啊,只不過它內部死迴圈中又有PeekMessage,GetMessage什麼的了,所以不會對介面操作產生影響

我記得IsDialogMessage只適用於模式對話方塊吧,非模式對話方塊不需要的。

還有,如果是模式對話方塊,下面的視窗還是可以處理WM_PAINT的,你移動上面的模式對話方塊,下面的視窗可以自己重畫

剛才試了一下,DispatchMessage果然是會卡住等待WndProc返回才返回的。模式對話方塊的確是有內嵌訊息迴圈的,包括MessagaBox也是一樣。

WM_PAINT和WM_TIMER都是優先順序不同的,WM_TIMER優先順序最低,只有佇列裡沒有其他訊息才會執行,WM_PAINT則較高

從視窗建立開始說:

CreateWindowEx,這個會向視窗例程直接投遞WM_CREATE,而不進入訊息佇列。其他訊息則是比較正常的經過訊息佇列,然後通過GetMessage和DispatchMessage取訊息和分發到相應例程,直到取到WM_QUITE就結束訊息迴圈。

對於按鍵和滑鼠訊息則是先進入系統訊息佇列,然後系統佇列會分發到相應的執行緒訊息佇列。

記住,訊息佇列是執行緒所有,並非視窗所有和程序所有,當然,系統佇列例外。

===================================================================

剛才學了用 c 和 windows API 建立一個簡單的windows 視窗程式,步驟如下:

1.設計一個視窗類

2.建立視窗

3.顯示視窗

4.定義訊息結構體,開始訊息迴圈,如:

   while(GetMessage(&msg,NULL,0,0)){

       略...        

   }

5.編寫視窗過程函式,如:

  LRESULT CALLBACK WinSunProc(引數略){

      switch(uMsg)

      {

         case 某一事件;

              執行某函式;

              break;

         case 某一事件;

              執行某函式;

              break;

         default;

               略

      }

  }

我的問題是, 當發生了一個訊息時, 會執行switch 語句 中的某條case語句, 就是在此時此刻,while語句還在監聽訊息嗎??????    

如何你在視窗函式case不返回的話這個視窗就會沒響應的,你可以試試看在一個case裡寫Sleep(5000)

有些人已經說對了,GetMessage只負責從訊息佇列裡面取出一條訊息,TranslateMessage將鍵盤敲鍵的訊息轉換成WM_CHAR訊息,DispatchMessage就負責呼叫你的視窗函式,其實相當於

... DispatchMessage(...)

{

    ....

     WinSunProc(...):  //事實上這裡是通過你註冊視窗類時候給Windows的函式指標來實現的,但是效果和直接呼叫一樣。

}

現在整個流程就很清楚了,GetMessage -> DispatchMessage -> WinSunProc 然後再返回到主迴圈進行下一條訊息的操作,如果你在WinSunProc裡面一直不返回,那麼程式是無法處理下條訊息的。

處理訊息的時候如果又有其他訊息過來是沒關係的,Windows的GetMessage是從訊息“佇列”裡面去訊息的,沒來得處理的訊息是會排隊在訊息佇列裡面的,微軟說了Windows的訊息佇列足夠長,一般不會出現訊息丟失的情況,具體沒說多長,可能根據作業系統版本不同有不同的長度限制。

另外GetMessage還有個特性,如果程式的訊息佇列是空的,也就是沒有訊息了,那麼GetMessage就不會返回,直到等到下一條訊息來再返回,Windows會將處於等待的程式轉入Idle模式,所以那個while迴圈是不會出現CPU100%的佔用率的。

如果你希望在程式沒有訊息的時候在後臺做點什麼事情,那麼就可以利用PeekMessage,典型的MFC就是利用了PeekMessage來運作訊息迴圈的,PeekMessage在佇列中有訊息的時候則把訊息取回,沒訊息的時候也會立刻返回,這樣你就可以在沒訊息的時候做點別的事情。

MFC的CWinApp類在Run這個函式中包含了訊息迴圈,在沒有訊息的時候,Run會去呼叫CWinApp::OnIdle,預設的OnIdle會負責釋放不需要再使用的動態連線庫檔案。

如果Run裡面的PeekMessage取到訊息,他則呼叫CWinApp::PumpMessage函式,PumpMessage就負責呼叫DispatchMessage把訊息轉交給視窗函式。

windows在處理訊息時,會使用一個佇列來存放所有訊息,即訊息佇列。在執行case語句時,雖然視窗沒有關心訊息佇列,但是如果此時又來了一個訊息,windows會自動將該訊息放入佇列之中,保證訊息不會丟失。當case語句處理完畢,視窗再一次執行到GetMessage時,會檢視佇列中有沒有未處理的訊息存在,通過這種方式獲取之前發生但還沒有處理的訊息,因此不會出現對訊息視而不見的情況

windows採用訊息佇列來處理訊息,沒偵聽到一個事件,系統會將此訊息加到訊息佇列,再按順序或者按權重來處理訊息。

       所有建立了視窗的Windows程式,都需要執行一個訊息迴圈 :

       while (GetMessage(&msg, hWnd, 0, 0) > 0)

         {

                  TranslateMessage(&msg);

                  DispatchMessage(&msg);

         }

         這裡的hWnd就是建立的視窗控制代碼,上述迴圈會不斷的把該視窗(hWnd)相關的訊息取出來,並分發到訊息處理函式當中。

         GetMessage函式不是用來監聽的,是用來獲取當前執行緒訊息隊列當中的訊息的,其中的第二個引數如果傳遞一個視窗控制代碼,那麼就會獲取該視窗相關的訊息,如果傳NULL,那麼會將執行緒訊息佇列中所有的訊息都取出來。如果建立了多個視窗,而只對其中一個視窗控制代碼呼叫GetMessage形成訊息迴圈,那麼別的視窗都會毫無響應。

         補充說明:訊息佇列是作業系統為每個需要處理訊息的執行緒建立的,任何執行緒只要呼叫過與訊息有關的函式(如GetMessage,PeekMessage),作業系統就會為該執行緒建立訊息佇列

GetMessage在訊息列隊中沒有訊息時是阻塞的,也就是沒有訊息的情況下他會通知系統把時間片交給其他程序或執行緒

====================================

1 :訊息處理中第一條訊息是不是 WM_CREATE

2:當接收到WM_CREATE訊息時,他的附加引數中有沒有 視窗的例項控制代碼 在那個引數中或者 有沒有其他辦法在訊息處理中獲得視窗的例項控制代碼 hInstance

3;如果將WM_CREATE交給系統處理的話。。系統做了什麼

重複的說下過程吧,首先是定義一個視窗類,然後註冊視窗類,然後依照你前邊定義好的視窗類來建立一個視窗。以上就是CreateWindow函式呼叫結束前所做的事情,然後會呼叫ShowWindow將這個建立好的視窗顯示出來。

接下來遇到訊息迴圈了,程式開始從訊息佇列中取訊息。

首先遇到的訊息就是WM_CREATE(正如你所問到的),這條訊息是由系統向你的應用程式投遞的。然後系統會呼叫你寫的那個“回撥函式”,它依照你寫的WM_CREATE內容來建立一些控制元件或子窗體。(注意在這個訊息中並不建立主視窗,因為建立主視窗的工作早在ShowWindow前就建立好了) 在建立都完成以後就會把整個視窗的樣子繪製出來,然後程式就可以處理別的訊息啦。

至於第二個問題,它是得到不應用程式的例項控制代碼的。它的兩個賦加引數中,wParam不會被使用,而lParam則是一個指向 CREATESTRUCT 結構體的指標,用於建立子窗體(控制元件)的一些必要資訊。

你要想得到例項控制代碼要呼叫函式,最典型的就是GetWindowLong函式。利用該函式取應用程式例項控制代碼的調和方式如下:

HINSTANCE hAppInstance = (HINSTACE)GetWindowLong(hwnd,GWL_HINSTANCE);

其引數有兩個,第一個引數是主視窗的控制代碼,第二個引數是一個標記,代表你要取例項控制代碼,該函式還有別的功能,樓主可以自行查閱MSDN

還有其它的函式可以得到例項控制代碼,比如GetModuleHandle(NULL)的返回值也是例項控制代碼。

第三個問題已經在前邊講過了,注意你問的不對,這個訊息本來就是由系統處理的,並不是你的程式處理的。