1. 程式人生 > >MFC訊息機制---訊息對映

MFC訊息機制---訊息對映

Win32程式訊息流動


在講MFC訊息機制之前,先來介紹一下Win32程式的訊息處理。

Win32程式的編寫流程一般就分為:註冊視窗,建立視窗,顯示視窗,更新視窗,

訊息迴圈,訊息處理。

Win32程式執行起來後,會在如下程式碼進行訊息迴圈:

while(GetMessage(&msg...)) {//獲取訊息
TranslateMessage(...);//翻譯訊息
DispatchMessage(...);//派發訊息
}


GetMessage獲取的訊息來自兩種途徑:

1.      系統訊息佇列:滑鼠鍵盤等訊息進入系統訊息佇列。

2.      使用者訊息佇列:使用者程式觸發的事件訊息

DispatchMessage會把訊息派發到訊息處理函式WndProc由我們處理

其中,我們可以通過PostMessage函式將訊息投送至使用者訊息佇列,通過SendMessage將訊息直接投遞至WndProc函式處理。

MFC訊息對映

(下面我們用模擬的方式講解MFC的訊息對映,相關的巨集與函式與MFC中不完全一樣,我去掉了與訊息對映無關的東西,這裡我們只關注訊息對映)

Windows 程式靠訊息的流動而維護生命。我們已經在上面看到了訊息的一般處理方式,

也就是在視窗函式中藉著一個大大的switch/case 比對動作,判別訊息再呼叫對應的處理

例程。為了讓大大的switch/case 比對動作簡化,也讓程式程式碼更模組化一些,MFC

提供了一個訊息對映表作法,把訊息和其處理例程關聯起來。

當我們的類別庫成立,如果其中與訊息有關的類別(姑且叫作「訊息標的類別」好了,

在MFC 之中就是CCmdTarget)都是一條鞭式地繼承,我們應該為每一個「訊息標的類

別」準備一個訊息對映表,並且將基礎類別與衍生類別之訊息對映表串接起來。然後,

當視窗函式做訊息的比對時,我們就可以想辦法導引它沿著這條路走過去,

但是,MFC 之中用來處理訊息的C++ 類別,並不呈單鞭發展。作為application framework

的重要架構之一的document/view,也具有處理訊息的能力(你現在可能還不清楚什麼是

document/view,沒有關係)。因此,訊息藉以攀爬的路線應該有橫流的機會。

訊息如何流動,我們暫時先不管。是直線前進,或是中途換跑道,我們都暫時不管,我們先

把這個攀爬路線網講明白。這整個攀爬路線網就是所謂的訊息對映表(Message Map);說它是一張地圖,當然也沒有錯。將訊息與表格中的元素比對,然後呼叫對應的處理例程,這種動作我們也稱之為訊息對映(Message Mapping)。

訊息對映表

AFX_MSGMAP和AFX_MSGMAP_ENTRY

訊息對映表的建立用到了兩個重要的資料結構:

struct AFX_MSGMAP
{
AFX_MSGMAP* pBaseMessageMap;
AFX_MSGMAP_ENTRY* lpEntries;
};

AFX_MSGMAP可以理解為訊息對映表的一個節點。其中pBaseMessageMap成員變數儲存上一個節點的地址,lpEntries為訊息對映表節點的資料,它將指向一個數組首地址,陣列中每一個元素為AFX_MSGMAP_ENTRY,這時大家可能已經猜到了,AFX_MSGMAP_ENTRY這個結構體肯定有成員變數為訊息ID和訊息處理函式,這樣就實現了訊息和處理函式的對映。

struct AFX_MSGMAP_ENTRY // MFC 4.0 format
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFYcode
UINT nID; // control ID (or 0 for windowsmessages)
UINT nLastID; // used for entriesspecifying a range of control id's
UINT nSig; // signature type (action) orpointer to message #
AFX_PMSG pfn; // routine to call (orspecial value)
};

AFX_MSGMAP_ENTRY結構包含了

一個訊息的所有相關資訊,其中

nMessage為Windows訊息的ID號

nCode為控制訊息的通知碼

nID為Windows控制訊息的ID

nLastID表示如果是一個指定範圍的訊息被對映的話,nLastID用來表示它的範圍。

nSig表示訊息的動作標識

AFX_PMSG pfn 它實際上是一個指向和該訊息相應的執行函式的指標。

AFX_PMSG定義為:

typedef void (CCmdTarget::*AFX_PMSG)(void);

重要的巨集

有了上面兩個結構體,我們可以在每個類中宣告一個AFX_MSGMAP_ENTRY陣列,用來儲存該類要接收的訊息和該類中的處理函式的對映關係,然後再宣告一個AFX_MSGMAP成員變數,用來指向這個陣列和該類的父類的AFX_MSGMAP成員變數,這樣就把每個類串起來了。

上面說的這些工作,都由這幾個巨集來完成:

DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP、END_MESSAGE_MAP

我們來這樣定義巨集:

#defineDECLARE_MESSAGE_MAP() \
staticAFX_MSGMAP_ENTRY _messageEntries[]; \
static AFX_MSGMAPmessageMap; \
virtual AFX_MSGMAP*GetMessageMap() const;


於是,DECLARE_MESSAGE_MAP就相當於聲明瞭這樣一個數據結構:

這個資料結構的內容填塞工作由三個巨集完成:

#define BEGIN_MESSAGE_MAP(theClass,baseClass) \
AFX_MSGMAP* theClass::GetMessageMap() const\
{ return &theClass::messageMap; } \
AFX_MSGMAP theClass::messageMap = \
{ &(baseClass::messageMap), \
(AFX_MSGMAP_ENTRY*)&(theClass::_messageEntries) }; \
AFX_MSGMAP_ENTRYtheClass::_messageEntries[] = \
{
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, 0, (WORD)id, (WORD)id,AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() \
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
};

其中的AfxSig_end 定義為:

enum AfxSig
{
AfxSig_end = 0, // [marks end of messagemap]
AfxSig_vv,
};

注:AfxSig_xx 用來描述訊息處理例程memberFxn 的型別(引數與回返值)。

END_MESSAGE_MAP中有一個{ 0, 0,0, 0, AfxSig_end, (AFX_PMSG)0 },它的作用是在訊息進行匹配的時候,作為匹配結束標識。

我們以CView 為例來演示一下這幾個巨集的用法:

// in header file
class CView : public CWnd
{
public:
...
DECLARE_MESSAGE_MAP()
};
// in implementation file
#define CViewid 122
...
BEGIN_MESSAGE_MAP(CView, CWnd)
ON_COMMAND(CViewid, 0)
END_MESSAGE_MAP()<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

將巨集展開我們就清晰了:

// in header file
class CView : public CWnd
{
public:
...
static AFX_MSGMAP_ENTRY _messageEntries[];
static AFX_MSGMAP messageMap;
virtual AFX_MSGMAP* GetMessageMap() const;
};
// in implementation file
AFX_MSGMAP* CView::GetMessageMap() const
{ return &CView::messageMap; }
AFX_MSGMAP CView::messageMap =
{ &(CWnd::messageMap),
(AFX_MSGMAP_ENTRY*)&(CView::_messageEntries) };
AFX_MSGMAP_ENTRY CView::_messageEntries[] =
{
{ WM_COMMAND, 0, (WORD)122, (WORD)122, 1,(AFX_PMSG)0 },
{ 0, 0, 0, 0, 0, (AFX_PMSG)0 }
};

用圖表示為:

 

只要在每個我們想處理訊息的類中增加這幾個巨集,我們的訊息就可以很方便的順著基類向上傳遞了。

普通訊息(COMMAND訊息會有橫向傳遞)的傳遞的過程就是從子類找尋匹配的處理函式,沒有則向上傳遞去父類找尋,我們可以寫一個這樣的函式(實際這個函式在CCmdTarget類中的OnCmdMsg函式中有,用來找尋匹配COMMAND訊息;在CWnd::WindowProc中也有,用來找尋匹配非普通訊息)

//傳遞一個AFX_MSGMAP,它會縱向傳遞
void MsgMapPrinting(AFX_MSGMAP*pMessageMap)
{
//縱向傳遞,每到達一層,呼叫printlpEntries去找尋匹配
for(; pMessageMap != NULL; pMessageMap =pMessageMap->pBaseMessageMap)
{
AFX_MSGMAP_ENTRY* lpEntry =pMessageMap->lpEntries;
printlpEntries(lpEntry);//這裡我們不模擬找尋匹配,只是輸出一個值,表示我們來過
}
}
void printlpEntries(AFX_MSGMAP_ENTRY*lpEntry)
{
struct {
int classid;
char* classname;
} classinfo[] = {
CCmdTargetid, "CCmdTarget",
CWinThreadid, "CWinThread",
CWinAppid, "CWinApp",
CMyWinAppid, "CMyWinApp",
CWndid, "CWnd",
CFrameWndid, "CFrameWnd",
CMyFrameWndid, "CMyFrameWnd",
CViewid, "CView",
CMyViewid, "CMyView",
CDocumentid, "CDocument",
CMyDocid, "CMyDoc",
0, " "
};
for (int i=0; classinfo[i].classid != 0;i++)
{
if (classinfo[i].classid ==lpEntry->nID)
{
cout << lpEntry->nID <<" ";
cout << classinfo[i].classname<< endl;
break;
}
}
}