MFC消息映射的通俗演繹

面向?qū)ο笤O(shè)計(jì)中颊艳,消息的傳遞是整個(gè)系統(tǒng)中非常重要的一環(huán)。ObjectC有runtime運(yùn)行時(shí)來(lái)負(fù)責(zé)消息的傳遞和處理媚媒。不過(guò)這里不打算說(shuō)oc的runtime锄码,先來(lái)看看微軟的天才工程師們是如何解決這個(gè)問(wèn)題触徐。
當(dāng)消息發(fā)生時(shí)咪鲜,尋找到合適的對(duì)象的方法去處理狐赡,這個(gè)過(guò)程很容易就聯(lián)想到面向?qū)ο笾械亩鄳B(tài)和繼承撞鹉。于是不假思索就得出一個(gè)解決方案。在基類(lèi)中定義好虛函數(shù)颖侄,有子類(lèi)去負(fù)責(zé)具體實(shí)現(xiàn)鸟雏。那樣大約會(huì)產(chǎn)生如下的代碼:

class CCmdTarget
{
.....
public:
virtual void OnDraw();
virtual void OnClick();
virtual void OnKeyBoard();
.....
};

對(duì)于每一個(gè)能想到的消息,都在基類(lèi)中寫(xiě)一個(gè)對(duì)應(yīng)的虛函數(shù)览祖,留作子類(lèi)去重寫(xiě)孝鹊。這樣問(wèn)題貌似得到了解決,不過(guò)效率不言而喻展蒂,每個(gè)對(duì)象都要持有一份長(zhǎng)長(zhǎng)的虛函數(shù)表又活。另外,如果將來(lái)有新的消息類(lèi)型還不得不在基類(lèi)中添加一個(gè)虛函數(shù)锰悼。
換個(gè)思路柳骄。消息映射,消息路由箕般。這些字眼都在提醒著我們耐薯,我們需要一張表,這張表記錄了每一條消息我們?cè)摻挥烧l(shuí)處理丝里。于是表里的條目應(yīng)該是這樣的結(jié)構(gòu)體(精簡(jiǎn)了結(jié)構(gòu)體曲初,只取了幾個(gè)重要的字段,名稱(chēng)還是原封不動(dòng)的沿襲了MFC杯聚。):

class CCmdTarget;
typedef void (CCmdTarget:: * AFX_PMSG)(void);
struct AFX_MSGMAP_ENTRY
{
    int nMessage;
    int nCode;
    AFX_PMSG pfn;
};

pfn是一個(gè)函數(shù)指針臼婆,其余兩個(gè)是關(guān)于消息的。有了這樣一張表幌绍,當(dāng)有消息的時(shí)候目锭,我們只需要查找一遍表评汰,然后就可以知道該有哪個(gè)函數(shù)來(lái)處理。接下來(lái)我們?cè)倏紤]另一個(gè)問(wèn)題痢虹,當(dāng)某個(gè)消息當(dāng)前類(lèi)不打算處理被去,或者基類(lèi)中存在統(tǒng)一的處理辦法,我們想交由它來(lái)處理該怎么整奖唯?這里就涉及到自頂向下(由子類(lèi)到父類(lèi)的傳遞)的遞歸惨缆。大致是這樣的過(guò)程,在當(dāng)前類(lèi)中找處理函數(shù)丰捷,如果找不到坯墨,就到父類(lèi)的表中去找,還是找不到病往,繼續(xù),直到找到捣染,或者到達(dá)根類(lèi)依然找不到就拋棄或者其他處理。這就需要我們?cè)谧宇?lèi)中保存有到達(dá)父類(lèi)表的方法停巷。這就像鏈表耍攘,為了形成鏈表,我們需要在節(jié)點(diǎn)中保存后繼節(jié)點(diǎn)的指針畔勤。于是我們定義如下結(jié)構(gòu)體:

struct AFX_MSGMAP
{
    const AFX_MSGMAP       * (*pfnGetBaseMap)();
    const AFX_MSGMAP_ENTRY * lpEntries;
};

第一個(gè)是獲得父類(lèi)表的函數(shù)指針蕾各,第二個(gè)就是 表的入口地址。接下來(lái)我們定義一個(gè)函數(shù)用來(lái)取得這些數(shù)據(jù)庆揪。

virtual const AFX_MSGMAP * GetMessageMap()const;

定義成虛函數(shù)的原因大家想必都明白式曲,為了獲得具體對(duì)象的路由表,這就是多態(tài)的思想缸榛。

const AFX_MSGMAP * CCmdTarget::GetMessageMap()const
{
    return GetThisMessageMap();
}

函數(shù)直接返回了另一個(gè)函數(shù)的返回值吝羞,我們來(lái)看下這個(gè)函數(shù)又是如何定義的。

static const  AFX_MSGMAP * GetThisMessageMap();
const AFX_MSGMAP * CCmdTarget::GetThisMessageMap()
{
    static const AFX_MSGMAP_ENTRY _messageEntries[] =
    {
        {0,0,(AFX_PMSG)0}
    };
    static const AFX_MSGMAP messageMap = {
        &CCmdTarget::GetThisMessageMap,&_messageEntries[0]
    };
    return & messageMap;
}

定義了局部靜態(tài)變量内颗,然后將直接將地址返回钧排。這種設(shè)計(jì)也是性能考慮。靜態(tài)變量只初始化一次就在進(jìn)程的整個(gè)生命周期里存在于內(nèi)存中起暮。如果要問(wèn)為啥要大費(fèi)周章的設(shè)計(jì)兩個(gè)函數(shù)卖氨,一個(gè)函數(shù)的使命竟然只是為了調(diào)用另外一個(gè)函數(shù)。我的思路是负懦,GetMessageMap設(shè)計(jì)成虛函數(shù)的用意很明顯筒捺,為了實(shí)現(xiàn)多態(tài),每個(gè)對(duì)象都要獲得自己的路由表纸厉,這種根據(jù)不同的對(duì)象有不同的實(shí)現(xiàn)就是多態(tài)的思想系吭。另外AFX_MSGMAP結(jié)構(gòu)體的第一個(gè)字段就是一個(gè)函數(shù)指針,這個(gè)函數(shù)指針是普通的函數(shù)指針颗品,而不是一個(gè)指向類(lèi)成員函數(shù)的指針肯尺。沒(méi)有設(shè)計(jì)成指向類(lèi)成員函數(shù)指針的原因是沃缘,類(lèi)成員函數(shù)的指針在調(diào)用時(shí)必須要加上對(duì)象地址。這就使得我們必須在每個(gè)對(duì)象中保存一個(gè)父類(lèi)對(duì)象的地址则吟。這顯然是不切實(shí)際的槐臀,對(duì)象都還沒(méi)創(chuàng)建出來(lái),我們?cè)趺慈ケ4孢@個(gè)地址呢氓仲。于是我們需要一個(gè)編譯期就能確定的函數(shù)地址水慨。這也就是static const AFX_MSGMAP * GetThisMessageMap()被定義為靜態(tài)函數(shù)的原因。因?yàn)槲覀冊(cè)诰幾g期就需要它的地址啊敬扛。我們將父類(lèi)的函數(shù)地址存放AFX_MSGMAP結(jié)構(gòu)體中晰洒。就可以實(shí)現(xiàn) 運(yùn)行時(shí)通過(guò)多態(tài)的GetMessageMap函數(shù)獲得一個(gè)AFX_MSGMAP結(jié)構(gòu)體,而這個(gè)結(jié)構(gòu)體中又有一個(gè)父類(lèi)的獲得AFM_MSGMAP的函數(shù)的指針啥箭。這就形成了一個(gè)反向鏈表結(jié)構(gòu)谍珊。
至于如何路由,我隨意寫(xiě)了一個(gè)函數(shù)急侥,可供參考:

void HandleMessage(int nMessage,int nCode)
    {
        const AFX_MSGMAP * msg_map      = this->GetMessageMap();
        const AFX_MSGMAP * (* pfnGetBaseMap)();
        
        while(msg_map != NULL)
        {
            const AFX_MSGMAP_ENTRY * msg_entry = msg_map->lpEntries;
            while ( !(msg_entry->nCode == 0 && msg_entry->nMessage== 0) )
            {
                if(msg_entry->nMessage == nMessage && msg_entry->nCode == nCode)
                {
                    AFX_PMSG p2fn = msg_entry->pfn;
                    (this->*p2fn)();
                }
                msg_entry ++;
            }
            ///refresh msg_map , base_msg_map
            pfnGetBaseMap = msg_map->pfnGetBaseMap;
            msg_map = pfnGetBaseMap==NULL?NULL:(*pfnGetBaseMap)();
        }
    }

像偽代碼一樣直白砌滞,就是在本類(lèi)的路由表里搜索一下 找不到就繼續(xù)去父類(lèi)找。這就是MFC消息映射的一點(diǎn)個(gè)人理解缆巧。下面貼一下 樣例代碼布持。僅供參考豌拙。至于DECLARE_MESSAGE_MAP BEGIN_MESSAGE_MAP這些宏就不說(shuō)了陕悬,那都不是重點(diǎn)。

class CCmdTarget;
typedef void (CCmdTarget:: * AFX_PMSG)(void);
struct AFX_MSGMAP_ENTRY
{
    int nMessage;
    int nCode;
    AFX_PMSG pfn;
};
struct AFX_MSGMAP
{
    const AFX_MSGMAP       * (*pfnGetBaseMap)();
    const AFX_MSGMAP_ENTRY * lpEntries;
};
class CCmdTarget
{
protected:
    static const  AFX_MSGMAP * GetThisMessageMap();
    virtual const AFX_MSGMAP * GetMessageMap()const;
public:
    void HandleMessage(int nMessage,int nCode)
    {
        const AFX_MSGMAP * msg_map      = this->GetMessageMap();
        const AFX_MSGMAP * (* pfnGetBaseMap)();
        
        while(msg_map != NULL)
        {
            const AFX_MSGMAP_ENTRY * msg_entry = msg_map->lpEntries;
            while ( !(msg_entry->nCode == 0 && msg_entry->nMessage== 0) )
            {
                if(msg_entry->nMessage == nMessage && msg_entry->nCode == nCode)
                {
                    AFX_PMSG p2fn = msg_entry->pfn;
                    (this->*p2fn)();
                }
                msg_entry ++;
            }
            ///refresh msg_map , base_msg_map
            pfnGetBaseMap = msg_map->pfnGetBaseMap;
            msg_map = pfnGetBaseMap==NULL?NULL:(*pfnGetBaseMap)();
        }
    }
private:
    void OnDraw()
    {
        cout << "this is CCmdTarget::OnDraw()" << endl;
    }
};
const AFX_MSGMAP * CCmdTarget::GetMessageMap()const
{
    return GetThisMessageMap();
}
const AFX_MSGMAP * CCmdTarget::GetThisMessageMap()
{
    static const AFX_MSGMAP_ENTRY _messageEntries[] =
    {
        {100,100,(AFX_PMSG)&CCmdTarget::OnDraw},
        {0,0,(AFX_PMSG)0}
    };
    static const AFX_MSGMAP messageMap = {
        /*&CCmdTarget::GetThisMessageMap*/NULL,&_messageEntries[0]
    };
    return & messageMap;
}
//////////////////////////////////////////////////////////////
class CWind:public CCmdTarget
{
protected:
    static const  AFX_MSGMAP * GetThisMessageMap();
    virtual const AFX_MSGMAP * GetMessageMap()const;
};
const AFX_MSGMAP * CWind::GetMessageMap()const
{
    return GetThisMessageMap();
}
const AFX_MSGMAP * CWind::GetThisMessageMap()
{
    static const AFX_MSGMAP_ENTRY _messageEntries[] =
    {
        {0,0,(AFX_PMSG)0}
    };
    static const AFX_MSGMAP messageMap = {
        &CCmdTarget::GetThisMessageMap,&_messageEntries[0]
    };
    return & messageMap;
}

int main(int argc, const char * argv[])
{
    CWind win7;
    win7.HandleMessage(100, 100);
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末按傅,一起剝皮案震驚了整個(gè)濱河市捉超,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唯绍,老刑警劉巖拼岳,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異况芒,居然都是意外死亡惜纸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)绝骚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)耐版,“玉大人,你說(shuō)我怎么就攤上這事压汪》嗌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵止剖,是天一觀(guān)的道長(zhǎng)腺阳。 經(jīng)常有香客問(wèn)我落君,道長(zhǎng),這世上最難降的妖魔是什么亭引? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任绎速,我火速辦了婚禮,結(jié)果婚禮上焙蚓,老公的妹妹穿的比我還像新娘朝氓。我一直安慰自己,他們只是感情好主届,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布赵哲。 她就那樣靜靜地躺著,像睡著了一般君丁。 火紅的嫁衣襯著肌膚如雪枫夺。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天绘闷,我揣著相機(jī)與錄音橡庞,去河邊找鬼。 笑死印蔗,一個(gè)胖子當(dāng)著我的面吹牛扒最,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播华嘹,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吧趣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了耙厚?” 一聲冷哼從身側(cè)響起强挫,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎薛躬,沒(méi)想到半個(gè)月后俯渤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡型宝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年八匠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趴酣。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梨树,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出价卤,到底是詐尸還是另有隱情劝萤,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布慎璧,位于F島的核電站床嫌,受9級(jí)特大地震影響跨释,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厌处,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一鳖谈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧阔涉,春花似錦缆娃、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至椭住,卻和暖如春崇渗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背京郑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工宅广, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人些举。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓跟狱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親户魏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驶臊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,139評(píng)論 30 470
  • 1.面向?qū)ο蟮某绦蛟O(shè)計(jì)思想是什么? 答:把數(shù)據(jù)結(jié)構(gòu)和對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作的方法封裝形成一個(gè)個(gè)的對(duì)象绪抛。 2.什么是類(lèi)资铡?...
    少帥yangjie閱讀 5,000評(píng)論 0 14
  • 從暑假開(kāi)始,鑒于自己已經(jīng)放了一段時(shí)間長(zhǎng)假尖飞,日漸拖延懶散症副,我便有意識(shí)的想接觸一些新東西,或者計(jì)劃把一些感興趣的事堅(jiān)持...
    zouzoula閱讀 403評(píng)論 0 0
  • 2015年7月26日政基,從方凌銳住的地方閔行虹橋坐地鐵到浦東楊高贞铣,5塊錢(qián);晚上吃了一碗炒面沮明,6面辕坝,極其難吃,以后再也...
    塵年C閱讀 292評(píng)論 0 1
  • 今天琳袄,我看了知乎的帖子:保險(xiǎn)的保額是否考慮通貨膨脹因素(https://www.zhihu.com/questio...
    章曉逸閱讀 679評(píng)論 0 0