MFC訊息對映
Windows程式和MFC程式是靠訊息驅動的,他們對於訊息的處理本質上是相同的。只是Windows程式對於訊息處理的過程十分清晰明瞭,MFC程式則掩蓋了訊息處理的過程,以訊息對映的方式呈現在開發者面前,使得開發訊息的處理十分簡單。用多了mfc就想對它的訊息對映機制有一個本質的瞭解,下面將對訊息對映做詳細的分析。當然,在分析MFC訊息對映之前首先對Windows程式的訊息處理過程進行一個簡單的描述。
1、Windows應用程式訊息處理
Windows程式都維護有自己的訊息佇列,儲存了佇列訊息(當然也有非佇列訊息,它們直接發給視窗),並用過訊息迴圈對訊息進行處理。訊息迴圈首先通過GetMessage取得訊息並從佇列中移走,對於加速鍵會呼叫TranslateAccelerator函式,對其進行翻譯和處理,如果處理成功就不在呼叫TranslateMessage。如果不是加速鍵,就進行訊息的轉換和派發,讓目的視窗的視窗過程來處理訊息。示例程式碼:
- // 主訊息迴圈:
- while (GetMessage(&msg, NULL, 0, 0))
- {
- if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
真正處理訊息的是所謂的視窗過程(LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)),這個函式的引數記錄了過程對應的視窗、訊息的ID以及引數,在其內部開發者可以實現自己需要的訊息處理功能。那訊息分發是如何傳送給視窗過程的呢?我們知道視窗建立過程中有一個註冊視窗類的步驟,如下:
- ATOM MyRegisterClass(HINSTANCE hInstance)
- {
- WNDCLASSEX wcex;
- wcex.cbSize = sizeof(WNDCLASSEX);
- wcex.style = CS_HREDRAW | CS_VREDRAW;
- wcex.lpfnWndProc = WndProc; // 啊!!!原來在這裡
- wcex.cbClsExtra = 0;
- wcex.cbWndExtra = 0;
- wcex.hInstance = hInstance;
- wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSP));
- wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
- wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
- wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WINDOWSP);
- wcex.lpszClassName = szWindowClass;
- wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
- return RegisterClassEx(&wcex);
- }
2、MFC訊息對映
MFC視窗使用同一視窗過程,通過訊息對映隱藏了訊息處理的過程,更加詳細點是隱藏了,那訊息對映如何實現的呢?
首先,我們先對MFC的訊息對映做一個簡單介紹。MFC為了實現訊息對映在響應訊息的類內部自動做了如下兩方面的處理:
a、訊息對映宣告和實現
在類的定義(標頭檔案)裡,新增宣告訊息對映的巨集DECLARE_MESSAGE_MAP,在類的實現(原始檔)裡,通過
BEGIN_MESSAGE_MAP和END_MESSAGE_MAP()實現訊息對映。
b、訊息響應函式的宣告和實現
當通過ClassWizard新增訊息響應函式時就會自動新增函式的宣告和實現,程式碼如下:
宣告:
- //{{AFX_MSG
- afx_msg void OnTimer(UINT nIDEvent);
- afx_msg void OnPaint();
- //}}AFX_MSG
- DECLARE_MESSAGE_MAP()
- BEGIN_MESSAGE_MAP(CTestDialog, CDialog)
- //{{AFX_MSG_MAP(CTestDialog)
- ON_WM_TIMER()
- ON_WM_PAINT()
- //}}AFX_MSG_MAP
- END_MESSAGE_MAP()
- void CTestDialog::OnPaint()
- {
- }
- void CTestDialog::OnTimer(UINT nIDEvent)
- {
- CDialog::OnTimer(nIDEvent);
- }
僅僅這些工作就能實現對訊息處理的簡化嗎?當然,我們需要對這幾步有更深入的探討!首先,需要了解的是訊息對映的宣告和實現。訊息對映宣告的程式碼如下:
- #define DECLARE_MESSAGE_MAP() /
- protected: /
- static const AFX_MSGMAP* PASCAL GetThisMessageMap(); / // 獲得當前類和基類的對映資訊
- virtual const AFX_MSGMAP* GetMessageMap() const; / // 實際上呼叫了上一個函式
- #define BEGIN_MESSAGE_MAP(theClass, baseClass) / // 訊息對映開始
- PTM_WARNING_DISABLE / // pragma巨集的處理,無關係
- const AFX_MSGMAP* theClass::GetMessageMap() const / // 獲得自身和基類的函式對映表
- { return GetThisMessageMap(); } / // 入口地址
- const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() / // 獲得自身函式對映表入口地址
- { /
- typedef theClass ThisClass; / // 當前類
- typedef baseClass TheBaseClass; / // 基類
- static const AFX_MSGMAP_ENTRY _messageEntries[] = / // 當前類資訊實體陣列,記錄了
- { // 該類所有的訊息實體
- // 該行之所以空出來,是因為所有的訊息都要寫在這裡
- #define END_MESSAGE_MAP() / // 對映訊息的結束,也是訊息實
- {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } / // 體的最後一個元素,標誌結束
- }; /
- static const AFX_MSGMAP messageMap = / // 訊息對映變數(包含基類)
- { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; /
- return &messageMap; / // 返回訊息變數
- }/
- PTM_WARNING_RESTORE // pragma巨集的處理,無關係
a、靜態變數:訊息對映實體陣列AFX_MSGMAP_ENTRY _messageEntries[] ——記錄了當前類的所有訊息對映。每一個訊息是一個陣列成員,AFX_MSGMAP_ENTRY 的定義如下:
- struct AFX_MSGMAP_ENTRY
- {
- UINT nMessage; // windows message
- UINT nCode; // control code or WM_NOTIFY code
- UINT nID; // control ID (or 0 for windows messages)
- UINT nLastID; // used for entries specifying a range of control id's
- UINT_PTR nSig; // signature type (action) or pointer to message #
- AFX_PMSG pfn; // routine to call (or special value)
- };
從上述結構可以看出,每條對映有兩部分的內容:第一部分是關於訊息ID的,包括前四個域;第二部分是關於訊息對應的執行函式,包括後兩個域,pfn是一個指向CCmdTarger成員函式的指標。函式指標的型別定義如下:
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
當使用一條或者多條訊息對映條目初始化訊息對映陣列時,各種不同型別的訊息函式都被轉換成這樣的型別:不接收引數,也不返回引數的型別。因為所有可以有訊息對映的類都是從CCmdTarge派生的,所以可以實現這樣的轉換。nSig是一個標識變數,用來標識不同原型的訊息處理函式,每一個不同原型的訊息處理函式對應一個不同的nSig。在訊息分發時,MFC內部根據nSig把訊息派發給對應的成員函式處理,實際上,就是根據nSig的值把pfn還原成相應型別的訊息處理函式並執行它。
b、靜態變數:訊息對映資訊變數AFX_MSGMAP messageMap——記錄了當前類和基類的訊息對映實體陣列的入口地址。AFX_MSGMAP結構的定義如下:
- struct AFX_MSGMAP
- {
- const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); // 基類訊息對映入口地址
- const AFX_MSGMAP_ENTRY* lpEntries; // 當前類訊息對映入口地址
- };
c、訊息對映實體陣列:從BEGIN_MESSAGE_MAP和END_MESSAGE_MAP巨集定義來看,使用者新增的訊息對映實體會自動加入到_messageEntries陣列中,在這裡實現了對訊息對映實體陣列的初始化。
d、虛擬函式GetMessageMap:之所以設定成虛擬函式,就是為了實現多型,使當前類和基類能夠呼叫正確的訊息對映實體陣列。
通過上面對訊息對映巨集的解析,我可以清晰的瞭解到三個巨集通過兩個靜態變數把類和基類、把訊息和對應的訊息處理函式關聯起來,這種關聯保證了訊息處理的順序(當前類->基類),保證了訊息能夠正確的找到對應的函式。
講到這裡,總感覺少點什麼。仔細想想這種對映是有啦,但是什麼激發了這樣對映(就像windows程式的視窗過程是有啦,是誰呼叫了這個過程使得每一個訊息都能夠實現自己的功能、達到想要的目的呢?)?——訊息迴圈。通過訊息迴圈派發訊息到視窗,視窗類的會呼叫有關函式查詢訊息對映實體陣列,首先查詢當前類的再查詢基類的,查詢到後就會呼叫相應的訊息處理函式,如果沒有查到相應的處理函式程式會自動呼叫預設處理函式!
至於MFC具體是怎樣實現訊息的派送的,請參考:http://blog.csdn.net/linzhengqun/archive/2007/11/28/1905671.aspx。這篇文章清晰的闡釋了MFC訊息分發的原理和訊息的部分傳遞過程!
MFC的完整傳遞過程(從訊息迴圈開始到訊息處理函式)有待以後熟悉,畢竟MFC封裝的太好以至於不那麼好理解,同時給予文件的應用程式和基於對話方塊的應用程式也是不一樣。
相關文章
- MFC六大核心機制之五、六:訊息對映和命令傳遞
- MFC DLL如何響應PreTranslateMessage訊息
- MFC學習(四) 訊息機制
- OCX 控制元件主動傳送訊息給 MFC 視窗訊息控制元件
- 對映
- 多重對映
- MyBatis(四) 對映器配置(自動對映、resultMap手動對映、引數傳遞)MyBatis
- 如何應對 RocketMQ 訊息堆積MQ
- .NET對接極光訊息推送
- 訊息對話方塊 confirm() prompt()
- [非專業翻譯] Mapster - 對映前&對映後
- Cache對映方式
- WSL 埠對映
- 09 對映(mappings)APP
- .NET快速對接極光訊息推送
- RabbitMQ,RocketMQ,Kafka 訊息模型對比分析MQKafka模型
- Linux埠對映是什麼?如何進行埠對映?Linux
- WSL docker打通容器間通訊和追加埠對映Docker
- 好訊息 OR 壞訊息
- JPA關係對映系列四:many-to-many 關聯對映
- 訊息佇列之JMS和AMQP對比佇列MQ
- KafkaConsumer對於事務訊息的處理Kafka
- MapStruct屬性對映Struct
- 對映本地圖片地圖
- MapStruct實體對映Struct
- VMware Fusion 埠對映
- Mybatis結果對映MyBatis
- Docker-埠對映Docker
- ElasticSearch中的對映Elasticsearch
- Nginx埠對映配置Nginx
- python 關係對映Python
- TypeScript 對映型別TypeScript型別
- mmap共享儲存對映(儲存I/O對映)系列詳解
- Cache與主存之間的直接對映,全相聯對映和組項聯對映以及其地址變換
- RocketMQ 訊息整合:多型別業務訊息-普通訊息MQ多型型別
- 訊息機制篇——初識訊息與訊息佇列佇列
- Three.js進階篇之9 - 紋理對映和UV對映JS
- 驅動開發:透過MDL對映實現多次通訊