天天看點

Windows消息機制詳解

消息是指什麼?

     消息系統對于一個win32程式來說十分重要,它是一個程式運作的動力源泉。一個消息,是系統定義的一個32位的值,他唯一的定義了一個事件,向 Windows發出一個通知,告訴應用程式某個事情發生了。例如,單擊滑鼠、改變視窗尺寸、按下鍵盤上的一個鍵都會使Windows發送一個消息給應用程式。

    消息本身是作為一個記錄傳遞給應用程式的,這個記錄中包含了消息的類型以及其他資訊。例如,對于單擊滑鼠所産生的消息來說,這個記錄中包含了單擊滑鼠時的坐标。這個記錄類型叫做MSG,MSG含有來自windows應用程式消息隊列的消息資訊,它在Windows中聲明如下:

struct tagMsg

Windows消息機制詳解

{

Windows消息機制詳解

       HWND    hwnd;       //接受該消息的視窗句柄

Windows消息機制詳解

       UINT    message;    //消息常量辨別符,也就是我們通常所說的消息号

Windows消息機制詳解

       WPARAM  wParam;     //32位消息的特定附加資訊,确切含義依賴于消息值

Windows消息機制詳解

       LPARAM  lParam;     //32位消息的特定附加資訊,确切含義依賴于消息值

Windows消息機制詳解

       DWORD   time;       //消息建立時的時間

Windows消息機制詳解

       POINT   pt;         //消息建立時的滑鼠/光标在螢幕坐标系中的位置

Windows消息機制詳解

}MSG;

Windows消息機制詳解

    消息可以由系統或者應用程式産生。系統在發生輸入事件時産生消息。舉個例子, 當使用者敲鍵, 移動滑鼠或者單擊控件。系統也産生消息以響應由應用程式帶來的變化, 比如應用程式改變系統字型改變窗體大小。應用程式可以産生消息使窗體執行任務,或者與其他應用程式中的視窗通訊。

消息中有什麼?

   我們給出了上面的注釋,是不是會對消息結構有了一個比較清楚的認識?如果還沒有,那麼我們再試着給出下面的解釋:

     hwnd 32位的視窗句柄。視窗可以是任何類型的螢幕對象,因為Win32能夠維護大多數可視對象的句柄(視窗、對話框、按鈕、編輯框等)。

     message用于差別其他消息的常量值,這些常量可以是Windows單元中預定義的常量,也可以是自定義的常量。消息辨別符以常量命名的方式指出消息的含義。當視窗過程接收到消息之後,他就會使用消息辨別符來決定如何處理消息。例如、WM_PAINT告訴視窗過程窗體客戶區被改變了需要重繪。符号常量指定系統消息屬于的類别,其字首指明了處了解釋消息的窗體的類型。

     wParam 通常是一個與消息有關的常量值,也可能是視窗或控件的句柄。

     lParam 通常是一個指向記憶體中資料的指針。由于WParam、lParam和Pointer都是32位的,是以,它們之間可以互相轉換。

消息辨別符的值

     系統保留消息辨別符的值在0x0000在0x03ff(WM_USER-1)範圍。這些值被系統定義消息使用。應用程式不能使用這些值給自己的消息。應用程式消息從WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到 0X7FFF範圍的消息由應用程式自己使用;0XC000到0XFFFF範圍的消息用來和其他應用程式通信,我們順便說一下具有标志性的消息值:

     WM_NULL---0x0000    空消息。

     0x0001----0x0087    主要是視窗消息。

     0x00A0----0x00A9    非客戶區消息 

     0x0100----0x0108    鍵盤消息

     0x0111----0x0126    菜單消息

     0x0132----0x0138    顔色控制消息

     0x0200----0x020A    滑鼠消息

     0x0211----0x0213    菜單循環消息

     0x0220----0x0230    多文檔消息

     0x03E0----0x03E8    DDE消息

     0x0400              WM_USER

     0x8000              WM_APP

     0x0400----0x7FFF    應用程式自定義私有消息

消息有哪幾種?

   其實,windows中的消息雖然很多,但是種類并不繁雜,大體上有3種:視窗消息、指令消息和控件通知消息。

     指令消息,這是一種特殊的視窗消息,他用來處理從一個視窗發送到另一個視窗的使用者請求,例如按下一個按鈕,他就會向主視窗發送一個指令消息。

     控件通知消息,是指這樣一種消息,一個視窗内的子控件發生了一些事情,需要通知父視窗。通知消息隻适用于标準的視窗控件如按鈕、清單框、組合框、編輯框,以及Windows公共控件如樹狀視圖、清單視圖等。例如,單擊或輕按兩下一個控件、在控件中選擇部分文本、操作控件的滾動條都會産生通知消息。她類似于指令消息,當使用者與控件視窗互動時,那麼控件通知消息就會從控件視窗發送到它的主視窗。但是這種消息的存在并不是為了處理使用者指令,而是為了讓主視窗能夠改變控件,例如加載、顯示資料。例如按下一個按鈕,他向父視窗發送的消息也可以看作是一個控件通知消息;單擊滑鼠所産生的消息可以由主視窗直接處理,然後交給控件視窗處理。

    其中視窗消息及控件通知消息主要由視窗類即直接或間接由CWND類派生類處理。相對視窗消息及控件通知消息而言,指令消息的處理對象範圍就廣得多,它不僅可以由視窗類處理,還可以由文檔類,文檔模闆類及應用類所處理。

    由于控件通知消息很重要的,人們用的也比較多,但是具體的含義往往令初學者暈頭轉向,是以我決定把常見的幾個列出來供大家參考:

按扭控件

BN_CLICKED        使用者單擊了按鈕

 BN_DISABLE 按鈕被禁止

 BN_DOUBLECLICKED  使用者輕按兩下了按鈕

 BN_HILITE  用/戶加亮了按鈕

 BN_PAINT  按鈕應當重畫

 BN_UNHILITE 加亮應當去掉

組合框控件

 CBN_CLOSEUP 組合框的清單框被關閉

 CBN_DBLCLK 使用者輕按兩下了一個字元串

 CBN_DROPDOWN 組合框的清單框被拉出

 CBN_EDITCHANGE 使用者修改了編輯框中的文本

 CBN_EDITUPDATE 編輯框内的文本即将更新

 CBN_ERRSPACE 組合框記憶體不足

 CBN_KILLFOCUS 組合框失去輸入焦點

 CBN_SELCHANGE 在組合框中選擇了一項

 CBN_SELENDCANCEL 使用者的選擇應當被取消

 CBN_SELENDOK 使用者的選擇是合法的

 CBN_SETFOCUS 組合框獲得輸入焦點

編輯框控件

 EN_CHANGE 編輯框中的文本己更新

 EN_ERRSPACE 編輯框記憶體不足

 EN_HSCROLL 使用者點選了水準滾動條

 EN_KILLFOCUS 編輯框正在失去輸入焦點

 EN_MAXTEXT 插入的内容被截斷

 EN_SETFOCUS 編輯框獲得輸入焦點

 EN_UPDATE 編輯框中的文本将要更新

 EN_VSCROLL 使用者點選了垂直滾動條消息含義

清單框控件

 LBN_DBLCLK 使用者輕按兩下了一項

 LBN_ERRSPACE 清單框記憶體不夠

 LBN_KILLFOCUS 清單框正在失去輸入焦點

 LBN_SELCANCEL 選擇被取消

 LBN_SELCHANGE 選擇了另一項

 LBN_SETFOCUS 清單框獲得輸入焦點

隊列消息和非隊列消息

   從消息的發送途徑來看,消息可以分成2種:隊列消息和非隊列消息。消息隊列由可以分成系統消息隊列和線程消息隊列。系統消息隊列由Windows維護,線程消息隊列則由每個GUI線程自己進行維護,為避免給non-GUI現成建立消息隊列,所有線程産生時并沒有消息隊列,僅當線程第一次調用GDI函數時系統才給線程建立一個消息隊列。隊列消息送到系統消息隊列,然後到線程消息隊列;非隊列消息直接送給目的視窗過程。

     對于隊列消息,最常見的是滑鼠和鍵盤觸發的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,還有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。當滑鼠、鍵盤事件被觸發後,相應的滑鼠或鍵盤驅動程式就會把這些事件轉換成相應的消息,然後輸送到系統消息隊列,由 Windows系統去進行處理。Windows系統則在适當的時機,從系統消息隊列中取出一個消息,根據前面我們所說的MSG消息結構确定消息是要被送往那個視窗,然後把取出的消息送往建立視窗的線程的相應隊列,下面的事情就該由線程消息隊列操心了,Windows開始忙自己的事情去了。線程看到自己的消息隊列中有消息,就從隊列中取出來,通過作業系統發送到合适的視窗過程去處理。

     一般來講,系統總是将消息Post在消息隊列的末尾。這樣保證視窗以先進先出的順序接受消息。然而,WM_PAINT是一個例外,同一個視窗的多個 WM_PAINT被合并成一個 WM_PAINT 消息, 合并所有的無效區域到一個無效區域。合并WM_PAIN的目的是為了減少重新整理視窗的次數。

Windows消息機制詳解
Windows消息機制詳解

    非隊列消息将會繞過系統隊列和消息隊列,直接将消息發送到視窗過程,。系統發送非隊列消息通知視窗,系統發送消息通知視窗。例如,當使用者激活一個視窗系統發送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。這些消息通知視窗它被激活了。非隊列消息也可以由當應用程式調用系統函數産生。例如,當程式調用SetWindowPos系統發送WM_WINDOWPOSCHANGED消息。一些函數也發送非隊列消息,例如下面我們要談到的函數。

消息的發送

     了解了上面的這些基礎理論之後,我們就可以進行一下簡單的消息發送與接收。

     把一個消息發送到視窗有3種方式:發送、寄送和廣播。

     發送消息的函數有SendMessage、SendMessageCallback、SendNotifyMessage、 SendMessageTimeout;寄送消息的函數主要有PostMessage、PostThreadMessage、 PostQuitMessage;廣播消息的函數我知道的隻有BroadcastSystemMessage、 BroadcastSystemMessageEx。

     SendMessage的原型如下:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),這個函數主要是向一個或多個視窗發送一條消息,一直等到消息被處理之後才會傳回。不過需要注意的是,如果接收消息的視窗是同一個應用程式的一部分,那麼這個視窗的視窗函數就被作為一個子程式馬上被調用;如果接收消息的視窗是被另外的線程所建立的,那麼視窗系統就切換到相應的線程并且調用相應的視窗函數,這條消息不會被放進目标應用程式隊列中。函數的傳回值是由接收消息的視窗的視窗函數傳回,傳回的值取決于被發送的消息。

     PostMessage的原型如下:BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),該函數把一條消息放置到建立hWnd視窗的線程的消息隊列中,該函數不等消息被處理就馬上将控制傳回。需要注意的是,如果hWnd參數為 HWND_BROADCAST,那麼,消息将被寄送給系統中的所有的重疊視窗和彈出視窗,但是子視窗不會收到該消息;如果hWnd參數為NULL,則該函數類似于将dwThreadID參數設定成目前線程的标志來調用PostThreadMEssage函數。

  從上面的這2個具有代表性的函數,我們可以看出消息的發送方式和寄送方式的差別所在:被發送的消息是否會被立即處理,函數是否立即傳回。被發送的消息會被立即處理,處理完畢後函數才會傳回;被寄送的消息不會被立即處理,他被放到一個先進先出的隊列中,一直等到應用程式空線的時候才會被處理,不過函數放置消息後立即傳回。

Windows消息機制詳解

  實際上,發送消息到一個視窗處理過程和直接調用視窗處理過程之間并沒有太大的差別,他們直接的唯一差別就在于你可以要求作業系統截獲所有被發送的消息,但是不能夠截獲對視窗處理過程的直接調用。

  以寄送方式發送的消息通常是與使用者輸入事件相對應的,因為這些事件不是十分緊迫,可以進行緩慢的緩沖處理,例如滑鼠、鍵盤消息會被寄送,而按鈕等消息則會被發送。

  廣播消息用得比較少,BroadcastSystemMessage函數原型如下:

      long BroadcastSystemMessage(DWORD dwFlags,LPDWORD lpdwRecipients,UINT uiMessage,WPARAM wParam,LPARAM lParam);該函數可以向指定的接收者發送一條消息,這些接收者可以是應用程式、可安裝的驅動程式、網絡驅動程式、系統級别的裝置驅動消息和他們的任意組合。需要注意的是,如果dwFlags參數是BSF_QUERY并且至少一個接收者傳回了BROADCAST_QUERY_DENY,則傳回值為0,如果沒有指定BSF_QUERY,則函數将消息發送給所有接收者,并且忽略其傳回值。

消息的接收

消息的接收主要有3個函數:GetMessage、PeekMessage、WaitMessage。

GetMessage原型如下:BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax);該函數用來擷取與hWnd參數所指定的視窗相關的且wMsgFilterMin和wMsgFilterMax參數所給出的消息值範圍内的消息。需要注意的是,如果hWnd為NULL,則GetMessage擷取屬于調用該函數應用程式的任一視窗的消息,如果 wMsgFilterMin和wMsgFilterMax都是0,則GetMessage就傳回所有可得到的消息。函數擷取之後将删除消息隊列中的除 WM_PAINT消息之外的其他消息,至于WM_PAINT則隻有在其處理之後才被删除。

PeekMessage原型如下:BOOL PeekMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT wRemoveMsg);該函數用于檢視應用程式的消息隊列,如果其中有消息就将其放入lpMsg所指的結構中,不過,與GetMessage不同的是,PeekMessage函數不會等到有消息放入隊列時才傳回。同樣,如果hWnd為NULL,則PeekMessage擷取屬于調用該函數應用程式的任一視窗的消息,如果hWnd=-1,那麼函數隻傳回把hWnd參數為NULL的PostAppMessage函數送去的消息。如果 wMsgFilterMin和wMsgFilterMax都是0,則PeekMessage就傳回所有可得到的消息。函數擷取之後将視最後一個參數來決定是否删除消息隊列中的除 WM_PAINT消息之外的其他消息,至于WM_PAINT則隻有在其處理之後才被删除。

WaitMessage原型如下:BOOL WaitMessage();當一個應用程式無事可做時,該函數就将控制權交給另外的應用程式,同時将該應用程式挂起,直到一個新的消息被放入應用程式的隊列之中才傳回。

消息的處理

接下來我們談一下消息的處理,首先我們來看一下VC中的消息泵:

Windows消息機制詳解

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

Windows消息機制詳解
Windows消息機制詳解

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

Windows消息機制詳解

      { 

Windows消息機制詳解

            TranslateMessage(&msg);

Windows消息機制詳解

            DispatchMessage(&msg);

Windows消息機制詳解

       }

Windows消息機制詳解

}

   首先,GetMessage從程序的主線程的消息隊列中擷取一個消息并将它複制到MSG結構,如果隊列中沒有消息,則GetMessage函數将等待一個消息的到來以後才傳回。如果你将一個視窗句柄作為第二個參數傳入GetMessage,那麼隻有指定視窗的的消息可以從隊列中獲得。GetMessage也可以從消息隊列中過濾消息隻接受消息隊列中落在範圍内的消息。這時候就要利用GetMessage/PeekMessage指定一個消息過濾器。這個過濾器是一個消息辨別符的範圍或者是一個窗體句柄,或者兩者同時指定。當應用程式要查找一個後入消息隊列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的鍵盤消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的滑鼠消息。 

然後TranslateAccelerator判斷該消息是不是一個按鍵消息并且是一個加速鍵消息,如果是,則該函數将把幾個按鍵消息轉換成一個加速鍵消息傳遞給視窗的回調函數。處理了加速鍵之後,函數TranslateMessage将把兩個按鍵消息WM_KEYDOWN和WM_KEYUP轉換成一個 WM_CHAR,不過需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然将傳遞給視窗的回調函數。     

處理完之後,DispatchMessage函數将把此消息發送給該消息指定的視窗中已設定的回調函數。如果消息是WM_QUIT,則 GetMessage傳回0,進而退出循環體。應用程式可以使用PostQuitMessage來結束自己的消息循環。通常在主視窗的 WM_DESTROY消息中調用。

下面我們舉一個常見的小例子來說明這個消息泵的運用:

Windows消息機制詳解

if (::PeekMessage(&msg, m_hWnd, WM_KEYFIRST,WM_KEYLAST, PM_REMOVE))

Windows消息機制詳解
Windows消息機制詳解

          if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)...

Windows消息機制詳解
Windows消息機制詳解

  這裡我們接受所有的鍵盤消息,是以就用WM_KEYFIRST 和 WM_KEYLAST作為參數。最後一個參數可以是PM_NOREMOVE 或者 PM_REMOVE,表示消息資訊是否應該從消息隊列中删除。                 

   是以這段小代碼就是判斷是否按下了Esc鍵,如果是就進行處理。

視窗過程

視窗過程是一個用于處理所有發送到這個視窗的消息的函數。任何一個視窗類都有一個視窗過程。同一個類的視窗使用同樣的視窗過程來響應消息。系統發送消息給視窗過程将消息資料作為參數傳遞給他,消息到來之後,按照消息類型排序進行處理,其中的參數則用來區分不同的消息,視窗過程使用參數産生合适行為。

一個視窗過程不經常忽略消息,如果他不處理,它會将消息傳回到執行預設的處理。視窗過程通過調用DefWindowProc來做這個處理。視窗過程必須 return一個值作為它的消息處理結果。大多數視窗隻處理小部分消息和将其他的通過DefWindowProc傳遞給系統做預設的處理。視窗過程被所有屬于同一個類的視窗共享,能為不同的視窗處理消息。下面我們來看一下具體的執行個體:

Windows消息機制詳解

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

Windows消息機制詳解
Windows消息機制詳解

 int wmId, wmEvent;

Windows消息機制詳解

 PAINTSTRUCT ps;

Windows消息機制詳解

 HDC hdc;

Windows消息機制詳解

 TCHAR szHello[MAX_LOADSTRING];

Windows消息機制詳解

 LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);

Windows消息機制詳解
Windows消息機制詳解

 switch (message) 

Windows消息機制詳解

 {

Windows消息機制詳解

  case WM_COMMAND:

Windows消息機制詳解

         wmId    = LOWORD(wParam); 

Windows消息機制詳解

         wmEvent = HIWORD(wParam); 

Windows消息機制詳解

         // Parse the menu selections:

Windows消息機制詳解

         switch (wmId)

Windows消息機制詳解

         {

Windows消息機制詳解

          case IDM_ABOUT:

Windows消息機制詳解

             DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);

Windows消息機制詳解

             break;

Windows消息機制詳解

          case IDM_EXIT:

Windows消息機制詳解

             DestroyWindow(hWnd);

Windows消息機制詳解
Windows消息機制詳解

          default:

Windows消息機制詳解

             return DefWindowProc(hWnd, message, wParam, lParam);

Windows消息機制詳解

         }

Windows消息機制詳解

   break;

Windows消息機制詳解
Windows消息機制詳解

  case WM_PAINT:

Windows消息機制詳解

         hdc = BeginPaint(hWnd, &ps);

Windows消息機制詳解

         // TODO: Add any drawing code here

Windows消息機制詳解
Windows消息機制詳解

         RECT rt;

Windows消息機制詳解

         GetClientRect(hWnd, &rt);

Windows消息機制詳解

         DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);

Windows消息機制詳解

         EndPaint(hWnd, &ps);

Windows消息機制詳解

         break;

Windows消息機制詳解
Windows消息機制詳解

  case WM_DESTROY:

Windows消息機制詳解

         PostQuitMessage(0);

Windows消息機制詳解
Windows消息機制詳解

  default:

Windows消息機制詳解

         return DefWindowProc(hWnd, message, wParam, lParam);

Windows消息機制詳解

  }

Windows消息機制詳解

  return 0;

Windows消息機制詳解
Windows消息機制詳解

消息分流器

通常的視窗過程是通過一個switch語句來實作的,這個事情很煩,有沒有更簡便的方法呢?有,那就是消息分流器,利用消息分流器,我們可以把switch語句分成更小的函數,每一個消息都對應一個小函數,這樣做的好處就是對消息更容易管理。

之是以被稱為消息分流器,就是因為它可以對任何消息進行分流。下面我們做一個函數就很清楚了:

int id,HWND hWndCtl,UINT codeNotify)

Windows消息機制詳解
Windows消息機制詳解

      switch(id)

Windows消息機制詳解

      {

Windows消息機制詳解

     case ID_A:

Windows消息機制詳解

                  if(codeNotify==EN_CHANGE)

Windows消息機制詳解
Windows消息機制詳解

                  break;

Windows消息機制詳解

     case ID_B:

Windows消息機制詳解

                  if(codeNotify==BN_CLICKED)

Windows消息機制詳解
Windows消息機制詳解
Windows消息機制詳解
Windows消息機制詳解

.

Windows消息機制詳解
Windows消息機制詳解
Windows消息機制詳解

然後我們修改一下視窗過程:

Windows消息機制詳解
Windows消息機制詳解
Windows消息機制詳解

       switch(message)

Windows消息機制詳解
Windows消息機制詳解

             HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);

Windows消息機制詳解

             HANDLE_MSG(hWnd,WM_DESTROY,MsgCracker);

Windows消息機制詳解

           default:

Windows消息機制詳解

                    return DefWindowProc(hWnd, message, wParam, lParam);

Windows消息機制詳解

   }

Windows消息機制詳解
Windows消息機制詳解

在WindowsX.h中定義了如下的HANDLE_MSG宏:

Windows消息機制詳解

   #define HANDLE_MSG(hwnd,msg,fn) \

Windows消息機制詳解

             switch(msg): return HANDLE_##msg((hwnd),(wParam),(lParam),(fn));

實際上,HANDLE_WM_XXXX都是宏,例如:HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);将被轉換成如下定義:

Windows消息機制詳解

   #define HANDLE_WM_COMMAND(hwnd,wParam,lParam,fn)\ 

Windows消息機制詳解

             ((fn)((hwnd),(int)(LOWORD(wParam)),(HWND)(lParam),(UINT)HIWORD(wParam)),0L);

好了,事情到了這一步,應該一切都明朗了。

不過,我們發現在windowsx.h裡面還有一個宏:FORWARD_WM_XXXX,我們還是那WM_COMMAND為例,進行分析:

Windows消息機制詳解

   #define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \

Windows消息機制詳解

     (void)(fn)((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl))

是以實際上,FORWARD_WM_XXXX将消息參數進行了重新構造,生成了wParam && lParam,然後調用了我們定義的函數。

前面,我們分析了消息的基本理論和基本的函數及用法,接下來,我們将進一步讨論消息傳遞在MFC中的實作。

MFC消息的處理實作方式

初看MFC中的各種消息,以及在頭腦中根深蒂固的C++的影響,我們可能很自然的就會想到利用C++的三大特性之一:虛拟機制來實作消息的傳遞,但是經過分析,我們看到事情并不是想我們想象的那樣,在MFC中消息是通過一種所謂的消息映射機制來處理的。

為什麼呢?在潘愛民老師翻譯的《Visual C++技術内幕》(第4版)中給出了詳細的原因說明,我再簡要的說一遍。在CWnd類中大約有110個消息,還有其它的MFC的類呢,算起來消息太多了,在C++中對程式中用到的每一個派生類都要有一個vtable,每一個虛函數在vtable中都要占用一個4位元組大小的入口位址,這樣一來,對于每個特定類型的視窗或控件,應用程式都需要一個440KB大小的表來支援虛拟消息控件函數。

如果說上面的視窗或控件可以勉強實作的話,那麼對于菜單指令消息及按鈕指令消息呢?因為不同的應用程式有不同的菜單和按鈕,我們怎麼處理呢?在MFC 庫的這種消息映射系統就避免了使用大的vtable,并且能夠在處理正常Windows消息的同時處理各種各樣的應用程式的指令消息。

說白了,MFC中的消息機制其實質是一張巨大的消息及其處理函數的一一對應表,然後加上分析處理這張表的應用架構内部的一些程式代碼.這樣就可以避免在SDK程式設計中用到的繁瑣的CASE語句。

MFC的消息映射的基類CCmdTarget

如果你想讓你的控件能夠進行消息映射,就必須從CCmdTarget類中派生。CCmdTarget類是MFC處理指令消息的基礎、核心。MFC為該類設計了許多成員函數和一些成員資料,基本上是為了解決消息映射問題的,所有響應消息或事件的類都從它派生,例如:應用程式類、架構類、文檔類、視圖類和各種各樣的控件類等等,還有很多。

不過這個類裡面有2個函數對消息映射非常重要,一個是靜态成員函數DispatchCmdMsg,另一個是虛函數OnCmdMsg。

DispatchCmdMsg專門供MFC内部使用,用來分發Windows消息。OnCmdMsg用來傳遞和發送消息、更新使用者界面對象的狀态。

CCmdTarget對OnCmdMsg的預設實作:在目前指令目标(this所指)的類和基類的消息映射數組裡搜尋指定指令消息的消息處理函數。

這裡使用虛拟函數GetMessageMap得到指令目标類的消息映射入口數組_messageEntries,然後在數組裡比對指令消息ID相同、控制通知代碼也相同的消息映射條目。其中GetMessageMap是虛拟函數,是以可以确認目前指令目标的确切類。

如果找到了一個比對的消息映射條目,則使用DispachCmdMsg調用這個處理函數;

如果沒有找到,則使用_GetBaseMessageMap得到基類的消息映射數組,查找,直到找到或搜尋了所有的基類(到CCmdTarget)為止;

如果最後沒有找到,則傳回FASLE。

每個從CCmdTarget派生的指令目标類都可以覆寫OnCmdMsg,利用它來确定是否可以處理某條指令,如果不能,就通過調用下一指令目标的 OnCmdMsg,把該指令送給下一個指令目标處理。通常,派生類覆寫OnCmdMsg時,要調用基類的被覆寫的OnCmdMsg。

在MFC架構中,一些MFC指令目标類覆寫了OnCmdMsg,如架構視窗類覆寫了該函數,實作了MFC的标準指令消息發送路徑。必要的話,應用程式也可以覆寫OnCmdMsg,改變一個或多個類中的發送規定,實作與标準架構發送規定不同的發送路徑。例如,在以下情況可以作這樣的處理:在要打斷發送順序的類中把指令傳給一個非MFC預設對象;在新的非預設對象中或在可能要傳出指令的指令目标中。

消息映射的内容

    通過ClassWizard為我們生成的代碼,我們可以看到,消息映射基本上分為2大部分:

    在頭檔案(.h)中有一個宏DECLARE_MESSAGE_MAP(),他被放在了類的末尾,是一個public屬性的;與之對應的是在實作部分(.cpp)增加了一章消息映射表,内容如下:

    BEGIN_MESSAGE_MAP(目前類, 目前類的基類)

       //{{AFX_MSG_MAP(CMainFrame)

         消息的入口項

       //}}AFX_MSG_MAP

   END_MESSAGE_MAP()

   但是僅是這兩項還遠不足以完成一條消息,要是一個消息工作,必須有以下3個部分去協作:

1.在類的定義中加入相應的函數聲明;

2.在類的消息映射表中加入相應的消息映射入口項;

3.在類的實作中加入相應的函數體;

消息的添加

   有了上面的這些隻是作為基礎,我們接下來就做我們最熟悉、最常用的工作:添加消息。MFC消息的添加主要有2種方法:自動/手動,我們就以這2種方法為例,說一下如何添加消息。

   1、利用Class Wizard實作自動添加

      在菜單中選擇View-->Class Wizard,也可以用單擊滑鼠右鍵,選擇Class Wizard,同樣可以激活Class Wizard。選擇Message Map标簽,從Class name組合框中選取我們想要添加消息的類。在Object IDs清單框中,選取類的名稱。此時, Messages清單框顯示該類的大多數(若不是全部的話)可重載成員函數和視窗消息。類重載顯示在清單的上部,以實際虛構成員函數的大小寫字母來表示。其他為視窗消息,以大寫字母出現,描述了實際視窗所能響應的消息ID。選中我們向添加的消息,單擊Add Function按鈕,Class Wizard自動将該消息添加進來。

      有時候,我們想要添加的消息本應該出現在Message清單中,可是就是找不到,怎麼辦?不要着急,我們可以利用Class Wizard上Class Info标簽以擴充消息清單。在該頁中,找到Message Filter組合框,通過它可以改變首頁中Messages清單框中的選項。這裡,我們選擇Window,進而顯示所有的視窗消息,一把情況下,你想要添加的消息就可以在Message清單框中出現了,如果還沒有,那就接着往下看:)

   2、手動地添加消息處理函數

    如果在Messages清單框中仍然看不到我們想要的消息,那麼該消息可能是被系統忽略掉或者是你自己建立的,在這種情況下,就必須自己手工添加。根據我們前面所說的消息工作的3個部件,我們一一進行處理:

      1) 在類的. h檔案中添加處理函數的聲明,緊接在//}}AFX_MSG行之後加入聲明,注意:一定要以afx_msg開頭。

     通常,添加處理函數聲明的最好的地方是源代碼中Class Wizard維護的表下面,但是在它标記其領域的{{}}括弧外面。這些括弧中的任何東西都将會被Class Wizard銷毀。

      2) 接着,在使用者類的.cpp檔案中找到//}}AFX_MSG_MAP行,緊接在它之後加入消息入口項。同樣,也是放在{ {} }的外面

      3) 最後,在該檔案中添加消息處理函數的實體。

本文轉自莫水千流部落格園部落格,原文連結:http://www.cnblogs.com/zhoug2020/p/6239018.html,如需轉載請自行聯系原作者