青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

cexer

cexer
posts - 12, comments - 334, trackbacks - 0, articles - 0
  C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

GUI框架:消息檢查者

Posted on 2009-11-22 02:36 cexer 閱讀(3756) 評論(40)  編輯 收藏 引用 所屬分類: GUI

轉(zhuǎn)帖請注明出處 http://m.shnenglu.com/cexer/archive/2009/11/22/101591.html

1 胸口碎大石

  緊接上話:GUI框架:談?wù)効蚣?,寫寫代碼 。廢話是肯定首先要說的,既為了承前啟后點(diǎn)明主題,也為了拉攏人心騙取回復(fù)。本來我想像自己上篇博文寫出來勢必像胸口碎大石一樣威猛有力,在街邊拉開陣勢,大吼一聲舉起錘子正要往下砸的時候,卻看到幾位神仙手提醬油瓶優(yōu)雅地踏著凌波微步路過,聽他們開口閉口說的都是六脈神劍啊,九陰真級啊這些高級東西,我的威猛感一下消失于無形,取而代之的是小孩子玩水槍的渺小。但是不管怎么樣攤子都鋪開了,這一錘子不砸下去,對不起那涼了半天石頭的胸肌。

  在此之前首先得感謝一下各位醬油眾。無論你們是看熱鬧的還是砸場子的,你們的圍觀都令我的博文增光不少。特別要感謝那幾位打架的神仙,你們使上篇博文真正變得有思想交鋒的精彩。我覺得你們的那些想法和爭論都非常有價(jià)值,建議你們不要只讓它們在這個角落里藏著,都寫到自己的博客上去讓更多的人看到吧。

  走過路過不要錯過,有錢的捧個錢場,沒錢的繼續(xù)揮舞你的醬油瓶加油吶喊,我這一錘要砸下去了!

2 實(shí)現(xiàn)消息檢查者

  上文將消息框架分為幾個部分,這篇博文實(shí)現(xiàn)其中的消息檢查者。經(jīng)典的用 API 編寫 GUI 程序的方式當(dāng)中,消息檢查都是用 if 或者 switch 語句進(jìn)行的:

   1: // 經(jīng)典的 API 方式
   2: switch( message )
   3: {
   4:     case WM_CREATE:
   5:         // ......
   6:         break;
   7:     case WM_PAINT:
   8:         // ......
   9:         break;
  10:     default:
  11:         // ......
  12: }
  13:
  14: // MFC 映射宏展開
  15: if ( message == WM_CREATE )
  16: {
  17:     // ......
  18: }
  19: if ( message == WM_PAINT )
  20: {
  21:     // ......
  22: }

  見過的很多的 GUI 框架并沒有在這原始的方式上進(jìn)步多少,"只是將黑換成暗"。比如 MFC 和 WTL 的消息映射宏,就像是披在 if 語句上的皇帝的新衣。這種消息檢查方式的好處是速度快,不用額外的空間消耗,但壞處更明顯:不容易擴(kuò)充。我覺得在好處和壞處之間的取舍很容易,有必要單獨(dú)給消息檢查的過程實(shí)現(xiàn)一個更具 OO 含義的執(zhí)行者:消息檢查者(MessageChecker )。

2.1 其實(shí)很簡單

  要有消息檢查者,首先得有個消息(Message)。上篇博文中的消息定義雖然非常簡單,卻完全可以勝任目前需要的工作,因此我們直接復(fù)制過來。

   1: typedef LRESULT    MessageResult;
   2: typedef UINT       MessageId;
   3: typedef WPARAM     MessageWparam;
   4: typedef LPARAM     MessageLparam;
   5:
   6: // 簡單的消息定義 
   7: class Message
   8: {
   9: public:
  10:     Message( MessageId id_=0,MessageWparam wp_=0,MessageLparam lp_=0 )
  11:         :id( id_ )
  12:         ,wparam( wp_ )
  13:         ,lparam( lp_ )
  14:         ,result( 0 )
  15:     {}
  16:
  17: public:
  18:     MessageResult    result;
  19:     MessageId        id;
  20:     MessageWparam    wparam;
  21:     MessageLparam    lparam;
  22: };

  有了消息,現(xiàn)在開始定義消息檢查者。消息檢查者的職責(zé):根據(jù)某種條件檢查消息并返回(是,否)的檢查結(jié)果,所以它既應(yīng)該有用于檢查的數(shù)據(jù),也應(yīng)該有用于檢查的動作函數(shù),并且該函數(shù)檢查返回布爾值。這不是很容易就出來了嗎:

   1: // 領(lǐng)銜的消息檢查者
   2: class MessageChecker
   3: {
   4: public:
   5:     MessageChecker( MessageId id_=0,MessageWparam wp_=0,MessageLparam lp_=0 )
   6:         :id( id_ )
   7:         ,wparam( wp_ )
   8:         ,lparam( lp_ )
   9:     {}
  10:
  11: public:
  12:     // 用于檢查消息的函數(shù)
  13:     virtual bool    isOk( const Message& message ) const;
  14:
  15: public:
  16:     // 用于檢查消息的數(shù)據(jù)
  17:     MessageId        id;
  18:     MessageWparam    wparam;
  19:     MessageLparam    lparam;
  20: };

  其中 MessageChecker::isOk 很明顯應(yīng)該可以被后繼者重寫以實(shí)現(xiàn)不同的檢查方式,所以它應(yīng)該是虛函數(shù),這樣后繼者可以這樣的形式擴(kuò)充檢查者隊(duì)伍:

   1: // 命令消息檢查者
   2: class CommandChecker:public MessageChecker
   3: {
   4: public:
   5:     virtual bool isOk( const Message& message );
   6: };
   7:
   8: // 通知消息檢查者
   9: class NotifyChecker:public MessageChecker
  10: {
  11: public:
  12:     virtual bool isOk( const Message& message );
  13: };

  看著 MessageChecker,CommandChecker,NotifyChecker 這些和諧的名字,感覺消息檢查者就這樣實(shí)現(xiàn)完成了,我們的 GUI 框架似乎已經(jīng)成功邁出了重要的第一步。但是面對函數(shù) isOk ,有經(jīng)驗(yàn)的程序員肯定會有疑問:真的這樣簡單就 ok 了?當(dāng)然是 no。要是真有那么簡單,我何苦還在后面寫那么長的篇符呢,cppblog 又不能多寫字騙稿費(fèi)的。

2.2 堆上生成 & 對象切割

  看著虛函數(shù) isOk 會想聯(lián)到兩個關(guān)鍵詞:多態(tài),指針。多態(tài)意味著要保存為指針,指針意味著要在堆上生成。消息檢查者保存為指針的映射像下面這樣子(假設(shè)消息處理者叫做 MessageHandler ):

   1: // 允許一個消息對應(yīng)多個處理者
   2: typedef vector<MessageHandler>                _HandlerVector;
   3:
   4: // 為了多態(tài)必須保存 MessageChecker 的指針
   5: typedef pair<MessageChecker*,_HandlerVector>   _HandlerPair;
   6:
   7: // 消息映射
   8: typedef vector<_HandlerPair>                 _HandlerMap;

  堆上生成是萬惡之源。誰來負(fù)責(zé)銷毀?何時銷毀?效率問題怎么辦?有的人此時可能想到了引用計(jì)數(shù),小對象分配技術(shù),內(nèi)存池。。。只是一個消息檢查的動作就用那么昂貴的實(shí)現(xiàn),就像花兩萬塊買張鼠標(biāo)墊一樣讓人難以接受。所以我們想保存消息映射者的對象 MessageChecker 而不是它的指針,就像這個樣子:

   1: // 允許一個消息對應(yīng)多個處理者
   2: typedef vector<MessageHandler>                _HandlerVector;
   3:
   4: // 保存 MessageChecker 對象而不是指針
   5: typedef pair<MessageChecker,_HandlerVector>   _HandlerPair;
   6:
   7: // 消息映射
   8: typedef vector<_HandlerPair>                 _HandlerMap;

  但這樣的保存方式帶來了一個新的問題:對象切割。如果往映射中放入派生類的對象比如 CommandChecker 或者 NotifyChecker,編譯器會鐵手無情地對它們進(jìn)行切割,切割得它們體無完膚搖搖欲墜,只剩下 MessageChecker 子對象為止 。砍頭不要緊,只要主義真,但是要是切割過程中切掉了虛函數(shù)表這個命根子就完蛋了,在手執(zhí)電鋸的編譯器面前玩耍虛函數(shù),很難說會發(fā)生什么可怕的事情。

  有一種解決方案是我們不使用真正的虛函數(shù),而是自己模擬虛函數(shù)的功能。具體辦法是在 MessageChecker 當(dāng)中保存一個函數(shù)指針,由子類去把它指向自己實(shí)現(xiàn)的函數(shù),非虛的 MessageChecker::isOk 函數(shù)去調(diào)用這個指針。修改 MessageChecker 的定義:

   1: // 領(lǐng)銜的消息檢查者,用函數(shù)指針模擬虛函數(shù)
   2: class MessageChecker
   3: {
   4:     // 模擬虛函數(shù)的函數(shù)指針類型
   5:     typedef bool (*_VirtualIsOk)( const MessageChecker* pthis,const Message& message );
   6:
   7: public:
   8:     MessageChecker( MessageId id_=0,MessageWparam wp_=0,MessageLparam lp_=0,_VirtualIsOk is_=&MessageChecker::virtualIsOk )
   9:         :id( id_ )
  10:         ,wparam( wp_ )
  11:         ,lparam( lp_ )
  12:         ,m_visok( is_ )
  13:     {}
  14:
  15: public:
  16:     // 非虛函數(shù)調(diào)用函數(shù)指針
  17:     bool isOk( const Message& message ) const
  18:     {
  19:         return m_visok( this,message );
  20:     }
  21:
  22: protected:
  23:     // 不騙你,我真的是虛函數(shù)
  24:     static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
  25:     {
  26:         return pthis->id == message.id;
  27:     }
  28:
  29: public:
  30:     // 用于檢查消息的數(shù)據(jù)
  31:     MessageId        id;
  32:     MessageWparam    wparam;
  33:     MessageLparam    lparam;
  34:
  35: protected:
  36:     // 模擬虛函數(shù)的函數(shù)指針
  37:     _VirtualIsOk    m_visok;
  38: };

  如代碼所示的,MessageChecker::virtualIsOk 的默認(rèn)實(shí)現(xiàn)是只檢查消息id,這也是 MFC 的映射宏 MESSAGE_HANDLER 干的事情。現(xiàn)在解決了不要堆生成與要求多態(tài)的矛盾關(guān)系,真正完成了消息檢查者的定義。

2.3 擴(kuò)充隊(duì)伍

  要擴(kuò)展消息檢查者隊(duì)伍,可以從 MessageChecker 派生新類,定義自己的檢查函數(shù)(virtualIsOk 或者其它名字都可以)并將 MessageChecker::m_visok 指向這個函數(shù)。要注意的是因?yàn)榇嬖趯ο笄懈顔栴},所以派生類不應(yīng)該定義新的數(shù)據(jù)成員,畢竟切掉花花草草也是非常不好的。舉例說明派生方法。

  比如從消息檢查者派生一個命令消息(Command)的檢查者 CommandChecker:它定義了一個函數(shù) virtualIsOk ,此函數(shù)檢查消息id是否為 WM_COMMAND ,并進(jìn)一步檢查控件id和命令code,然后將指針 Message::m_visok 指向這個函數(shù) CommandChecker::virualIsOk,這樣 MessageChecker::isOk 實(shí)際上就是調(diào)用的 CommandChecker::virtualIsOk 了。CommandChecker 的功能類似于 MFC 的宏 COMMAND_HANDLER:

   1: // 命令消息檢查者
   2: class CommandChecker:public MessageChecker
   3: {
   4: public:
   5:     // 將函數(shù)指針指向自己的函數(shù) CommandChecker;:virtualIsOk
   6:     CommandChecker( WORD id,WORD code )
   7:         :MessageChecker( WM_COMMAND,MAKEWPARAM(id,code),0,&CommandChecker::virtualIsOk )
   8:     {}
   9:
  10: protected:
  11:     // 檢查消息id是否為 WM_COMMAND,并進(jìn)一步檢查控件id和命令code
  12:     static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
  13:     {
  14:         WORD idToCheck   = LOWORD( message.wparam );
  15:         WORD codeToCheck = HIWORD( message.wparam );
  16:
  17:         WORD id   = LOWORD( pthis->wparam );
  18:         WORD code = HIWORD( pthis->wparam );
  19:
  20:         return message.id==WM_COMMAND && idToCheck==id && codeToCheck==code;
  21:     }
  22: };

  同理定義一個通知消息(Notification)的檢查者 NotifyChecker,其功能類似于 MFC 的宏 NOTIFY_HANDLER :

   1: // 通知消息檢查者
   2: class NotifyChecker:public MessageChecker
   3: {
   4: public:
   5:     // 將函數(shù)指針指向自己的函數(shù) NotifyChecker;:virtualIsOk
   6:     NotifyChecker( WORD id,WORD code )
   7:         :MessageChecker( WM_NOTIFY,MAKEWPARAM(id,code),0,&NotifyChecker::virtualIsOk )
   8:     {}
   9:
  10: public:
  11:     // 檢查消息的 id 是否為 WM_NOTIFY ,并進(jìn)一步檢查控件 id 和命令 code
  12:     static bool virtualIsOk(  const MessageChecker* pthis,const Message& message )
  13:     {
  14:         WORD idToCheck   = reinterpret_cast<NMHDR*>(message.lparam)->idFrom;
  15:         WORD codeToCheck = reinterpret_cast<NMHDR*>(message.lparam)->code;
  16:
  17:         WORD id   = LOWORD( pthis->wparam );
  18:         WORD code = HIWORD( pthis->wparam );
  19:
  20:         return message.id==WM_NOTIFY && idToCheck==id && codeToCheck==code;
  21:     }
  22: };

  發(fā)揮想像進(jìn)行擴(kuò)展,這個消息檢查者可以做很多事情。比如定義一個范圍id內(nèi)命令消息的檢查者 RangeIdCommandChecker,其功能類似于 MFC 的 ON_COMMAND_RANGE 宏:

   1: // 范圍 id 命令消息檢查者
   2: class RangeIdCommandChecker:public MessageChecker
   3: {
   4: public:
   5:     // 將函數(shù)指針指向自己的函數(shù) RangeIdCommandChecker;:virtualIsOk
   6:     RangeIdCommandChecker( WORD idMin,WORD idMax,WORD code )
   7:         :MessageChecker( WM_COMMAND,MAKEWPARAM(idMin,idMax),MAKELPARAM(code,0),&RangeIdCommandChecker::virtualIsOk )
   8:     {}
   9:
  10: protected:
  11:     // 檢查消息 id 是否為 WM_COMMAND,并進(jìn)一步檢查控件 id 范圍和命令 code
  12:     static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
  13:     {
  14:         WORD idToCheck   = LOWORD( message.wparam );
  15:         WORD codeToCheck = HIWORD( message.wparam );
  16:
  17:         WORD idMin = LOWORD( pthis->wparam );
  18:         WORD idMax = HIWORD( pthis->wparam );
  19:         WORD code  = LOWORD( pthis->lparam );
  20:
  21:         return message.id==WM_COMMAND && codeToCheck==code && idToCheck>=idMin && idToCheck<=idMax;
  22:     }
  23: };

  定義一個按鈕點(diǎn)擊消息的消息檢查者:

   1: // 按鈕點(diǎn)擊的命令消息檢查者
   2: class ButtonClickingChecker:public MessageChecker
   3: {
   4: public:
   5:     // 將函數(shù)指針指向自己的函數(shù) ButtonClickChecker;:virtualIsOk
   6:     ButtonClickingChecker( WORD id )
   7:         :MessageChecker( WM_COMMAND,MAKEWPARAM(id,BN_CLICKED),0,&ButtonClickingChecker::virtualIsOk )
   8:     {}
   9:
  10: protected:
  11:     // 檢查消息id是否為 WM_COMMAND,并進(jìn)一步檢查命令code是否為 BN_CLICKED 以及按鈕id
  12:     static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
  13:     {
  14:         WORD idToCheck   = LOWORD( message.wparam );
  15:         WORD codeToCheck = HIWORD( message.wparam );
  16:
  17:         WORD  id   = LOWORD( pthis->wparam );
  18:         WORD  code = BN_CLICKED;
  19:
  20:         return message.id==WM_COMMAND && idToCheck==id && codeToCheck==code;
  21:     }
  22: };

2.4 數(shù)據(jù)不夠用

  這一節(jié)是新加的。因?yàn)橛腥颂岢鰯?shù)據(jù)容納不下的問題:MessageChecker 目前的定義只能容納 WPAWAM,LPARAM 兩個參數(shù)大小的數(shù)據(jù),而因?yàn)樵诖嬖趯ο笄懈顔栴},子類又不能定義新的數(shù)據(jù),那如果 WPARAM,LPARAM 裝不下需要的數(shù)據(jù)了怎么辦?難道就只能把數(shù)據(jù)在子類當(dāng)中定義,然后每次都在堆上生成 MessageChecker 嗎?

  我在這里的解決方案是,在 MessageChecker 里面定義一個多態(tài)的數(shù)據(jù)成員,這個成員大多數(shù)時候是空的,當(dāng)需要的時候從堆上生成它,用它來裝下數(shù)據(jù)。如代碼所示:

  先定義一個多態(tài)的數(shù)據(jù)類 MessageData:

   1: // 消息數(shù)據(jù)
   2: class MessageData
   3: {
   4: public:
   5:     virtual ~MessageData(){}
   6:     virtual MessageData* clone() const = 0;
   7:     virtual void release(){ delete this; }
   8: };

  在 MessageChecker 當(dāng)中加入這個數(shù)據(jù)成員:

   1: // 加入多態(tài)的數(shù)據(jù)成員
   2: class MessageChecker
   3: {
   4: //......
   5: public:
   6:     MessageData* data;
   7: }
   8:  
   9: // 拷貝構(gòu)造時同時深拷貝數(shù)據(jù)
  10: MessageChecker::MessageChecker( const MessageChecker& other )
  11:     :id( other.id )
  12:     ,wparam( other.wparam )
  13:     ,lparam( other.lparam )
  14:     ,m_visok( other.m_visok )
  15:     ,data( other.data?other.data->clone():NULL )
  16: {}
  17:  
  18: // 拷貝時同時深拷貝數(shù)據(jù)
  19: MessageChecker& MessageChecker::operator=( const MessageChecker& other )
  20: {
  21:     if ( this != &other )
  22:     {
  23:         id        = other.id;
  24:         wparam  = other.wparam;
  25:         lparam  = other.lparam;
  26:         m_visok = other.m_visok;
  27:         
  28:         if ( data )
  29:         {
  30:             data->release();
  31:             data = NULL;
  32:         }
  33:  
  34:         if ( other.data )
  35:         {
  36:             data = other.data->clone();
  37:         }
  38:     }
  39:     return *this;
  40: }
  41:  
  42: // 析構(gòu)時刪除
  43: MessageChecker::~MessageChecker()
  44: {
  45:     if ( data )
  46:     {
  47:         data->release();
  48:         data = NULL;
  49:     }
  50: }

  舉例說明怎么樣使用 data 成員。例如要在一個消息檢查者需要比較字符串,則在其中必須要保存供比較的字符串。先從 MessageData 派生一個保存字符串的數(shù)據(jù)類 MessageString:

   1: // 裝字符串多態(tài)數(shù)據(jù)類
   2: class MessageString:public MessageData
   3: {
   4: public:
   5:     MessageString( const String& string )
   6:         :content( string )
   7:     {}
   8:  
   9:     virtual MessageData* clone() const
  10:     {
  11:         return new MessageString( content );
  12:     }
  13:  
  14: public:
  15:     String    content;
  16: };

  然后在這個消息檢查者中可以使用這個類了:

   1: // 檢查字符串的消息檢查者
   2: class StringMessageChecker:public MessageChecker
   3: {
   4: public:
   5:     StringMessageChecker( const String& string )
   6:         :MessageChecker( WM_SETTEXT,0,0,&StringMessageChecker::virtualIsOk )
   7:     {
   8:         data = new MessageString( string );
   9:     }
  10:  
  11: public:
  12:     static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
  13:     {
  14:         if ( message.id != pthis->id )
  15:         {
  16:             return false;
  17:         }
  18:  
  19:         MessageString* data = (MessageString*)pthis->data;
  20:         if ( !data )
  21:         {
  22:             return false;
  23:         }
  24:  
  25:         std::string stirngToCheck = (const Char*)( message.lparam );
  26:         return stirngToCheck == data->content;
  27:     }
  28: };

  為了使用方便可以定義一個數(shù)據(jù)類的模板:

   1: // 數(shù)據(jù)類的模板
   2: template <typename TContent>
   3: class MessageDataT:public MessageData
   4: {
   5: public:
   6:     MessageDataT( const TContent& content_ )
   7:         :content( content_ )
   8:     {}
   9:  
  10:     virtual MessageData* clone() const
  11:     {
  12:         return new MessageDataT( content );
  13:     }
  14:  
  15: public:
  16:     TContent content;
  17: };

  然后可以將字符串?dāng)?shù)據(jù)類的定義簡化為:

   1: // 利用模板生成數(shù)據(jù)類
   2: typedef MessageDataT<String>    MessageString;

2.5 龍?zhí)籽輪T

  接下來可以進(jìn)行消息檢查者的測試了。但因?yàn)橄z查者的工作牽到其它兩個角色,所以測試之前必須要先簡單模擬出這兩個龍?zhí)捉巧合⑻幚碚吆拖⒈O(jiān)聽者。

  消息處理者(MessageHandler )的作用顧名思義不用多作解釋,它在 GUI 框架當(dāng)中的作用十分重要,實(shí)現(xiàn)起來也最復(fù)雜,但在這本文中它不是主角,所以可以先隨便拉個路人甲來跑跑龍?zhí)?,路人甲長得像這個樣子:

   1: // 路人甲表演的消息處理者
   2: typedef void    (*MessageHandler)( const Message& message );

  消息監(jiān)聽者(MessageListener )保存有消息檢查者與消息處理者的映射,并提供接口操作這些映射,當(dāng)監(jiān)聽到消息的時候,消息監(jiān)聽者調(diào)用映射中的檢查者進(jìn)行檢查,如果通過檢查則調(diào)用消息處理者來進(jìn)行處理。消息監(jiān)聽者干的都是添加/刪除/查找這樣的體力活,看似實(shí)現(xiàn)起來用不著大腦,可是當(dāng)涉及到真正的消息處理時情況會變得復(fù)雜,會遇到消息重入之類的問題。所以真正的實(shí)現(xiàn)留待日后再說,這里也先給出一個簡單的模擬定義含混過去:

   1: // 消息監(jiān)聽者
   2: class MessageListener
   3: {
   4:     typedef vector<MessageHandler>                 _HandlerVector;
   5:     typedef pair<MessageChecker,_HandlerVector>    _HandlerPair;
   6:     typedef vector<_HandlerPair>                   _HandlerMap;
   7:     typedef _HandlerVector::iterator               _HandlerVectorIter;
   8:     typedef _HandlerMap::iterator                  _HandlerMapIter;
   9:
  10: public:
  11:     virtual ~MessageListener(){}
  12:
  13:     // 消息從這里來了
  14:     virtual void    onMessage( const Message& message );
  15:
  16: public:
  17:     // 操作映射的接口
  18:     bool    addHandler( const MessageChecker& checker,const MessageHandler& handler );
  19:     bool    removeHandler( const MessageChecker& checker,const MessageHandler& handler );
  20:     bool    clearHandlers( const MessageChecker& checker );
  21:
  22: protected:
  23:     // 消息檢查者與消息處理者的映射
  24:     _HandlerMap        m_map;
  25: };

  其中調(diào)用消息檢查者的是關(guān)鍵函數(shù)是 MessageListener::onMessage( const Message& mesage ) ,實(shí)現(xiàn)很簡單,查找出消息處理者列表然后逐個調(diào)用其中處理者:

   1: // 調(diào)用檢查者檢查,通過則調(diào)用處理者處理
   2: void MessageListener::onMessage( const Message& message )
   3: {
   4:     for ( _HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it )
   5:     {
   6:         if ( (*it).first.isOk(message) )
   7:         {
   8:             _HandlerVector* handers = &(*it).second;
   9:             if ( handers && !handers->empty() )
  10:             {
  11:                 for ( _HandlerVectorIter it=handers->begin(); it!=handers->end(); ++it )
  12:                 {
  13:                     (*it)( message );
  14:                 }
  15:             }
  16:         }
  17:     }
  18: }

2.6 進(jìn)行測試

  龍?zhí)锥家丫臀唬瑢?dǎo)演喊:"action!",現(xiàn)在可以寫點(diǎn)測試代碼測試一下了:

   1: void handleCreated( const Message& message )
   2: {
   3:     cout<<"::handleCreated";
   4:     cout<<"\n"<<endl;
   5: }
   6:  
   7: void handleCommand( const Message& message )
   8: {
   9:     cout<<"::handleCommand\t";
  10:     cout<<"id:"<<LOWORD(message.wparam);
  11:     cout<<"\t";
  12:     cout<<"code:"<<HIWORD(message.wparam);
  13:     cout<<"\n"<<endl;
  14: }
  15:  
  16: void handleClicked( const Message& message )
  17: {
  18:     cout<<"::handleClicked\t";
  19:     cout<<"id:"<<LOWORD(message.wparam);
  20:     cout<<"\n"<<endl;
  21: }
  22:  
  23: void handleRangeCommand( const Message& message )
  24: {
  25:     cout<<"::handleRangeCommand\t";
  26:     cout<<"id:"<<LOWORD(message.wparam);
  27:     cout<<"\t";
  28:     cout<<"code:"<<HIWORD(message.wparam);
  29:     cout<<"\n"<<endl;
  30: }
  31:  
  32: void handleString( const Message& message )
  33: {
  34:     cout<<"::handleString\t";
  35:     cout<<"string:"<<(const char*)message.lparam;
  36:     cout<<"\n"<<endl;
  37: }
  38:  
  39:  
  40: #define ID_BUTTON_1    1
  41: #define ID_BUTTON_2    2
  42: #define ID_BUTTON_3    3
  43: #define ID_BUTTON_4    4
  44:  
  45: int main( int argc,char** argv )
  46: {
  47:     MessageListener listener;
  48:     listener.addHandler( MessageChecker(WM_CREATE),&handleCreated );
  49:     listener.addHandler( CommandChecker(ID_BUTTON_1,BN_CLICKED),&handleCommand );
  50:     listener.addHandler( RangeIdCommandChecker(ID_BUTTON_1,ID_BUTTON_3,BN_CLICKED),&handleRangeCommand );
  51:     listener.addHandler( StringMessageChecker( "I love this game" ),&handleString );
  52:  
  53:     Message message( WM_CREATE );
  54:     listener.onMessage( message );
  55:  
  56:     message.id = WM_COMMAND;
  57:     message.wparam = MAKEWPARAM( ID_BUTTON_2,BN_CLICKED );
  58:     listener.onMessage( message );
  59:  
  60:     message.id = WM_COMMAND;
  61:     message.wparam = MAKEWPARAM( ID_BUTTON_1,BN_CLICKED );
  62:     listener.onMessage( message );
  63:  
  64:     message.id = WM_COMMAND;
  65:     message.wparam = MAKEWPARAM( ID_BUTTON_3,BN_CLICKED );
  66:     listener.onMessage( message );
  67:  
  68:     const char* string = "I love this game";
  69:     message.id = WM_SETTEXT;
  70:     message.lparam = (LPARAM)string;
  71:     listener.onMessage( message );
  72:  
  73:     return 0;
  74: }

3 收場的話

  這第一錘終于砸完了,石頭一裂為二,胸口完好無損。其實(shí)砸的時候心想,這一錘的分量砸下去不轟動神州也要震驚天府吧。但是回頭看看上面所有的文字,覺得這個東西怎么這么簡單,甚至連模板參數(shù)都沒有用到一個,更沒有談到效率,優(yōu)化什么的,肯定是不足以誘惑技術(shù)流的 cpper 們的。

  想起自己曾經(jīng)寫過的幾個消息框架,可以算是把 C++ 的編譯期技術(shù)發(fā)揮得淋漓盡致了,但是出來的東西卻并不理想,后來慢慢領(lǐng)悟到一個道理:高尖的技術(shù)雖然炫酷,并不是處處都合適用。我的版本的消息檢查者就止于這個程度了,肯定有比這個更好的實(shí)現(xiàn),希望走過路過的高手們不要吝嗇自己的好想法,提出來與廣大醬油眾分享。

  消息檢查總算寫完了。沒選上好季節(jié),電腦前坐了大半天手腳都冰涼的。上床睡覺去了,養(yǎng)足精神希望能看到新一輪的神仙打架。文章涉及的所有代碼項(xiàng)目下載:MessageChecker_200911251055.rar

Feedback

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 05:14 by OwnWaterloo
貌似沙發(fā)又得被我占領(lǐng)……
作為一個對oo不屑一顧的c(pp)er,看到這樣的代碼相當(dāng)不適……




比如MessageChecker和它的子類……
不適啊……

1. 切割與多態(tài)
從msvc和gcc的實(shí)現(xiàn)來看,虛指針是切割不掉的,它在父類中。
虛表就更不存在切割一說……
不能多態(tài)的原因不是因?yàn)榍懈睿且驗(yàn)?—— 沒有使用引用或指針類型進(jìn)行調(diào)用……
所以啊,對這種情況 …… 可以惡心點(diǎn)……
按值保存(會被切割),同時對每個值也保存一個對應(yīng)的指針,指向該值。
通過指針而非值進(jìn)行調(diào)用,誘使編譯器使用虛指針。

為什么感到惡心呢……
是因?yàn)檫@樣可以多態(tài)調(diào)用,但不能保證子類的不變式……
調(diào)用的是子類的函數(shù),使用的是父類的數(shù)據(jù)(被切割了)……


2. class vs struct
當(dāng)然,對這個問題蠻適合的。
因?yàn)闃侵鞯姆桨冈谧宇愔型瑯硬荒苋碌臄?shù)據(jù),否則一樣會被切割。
然后,調(diào)用那個函數(shù)指針時,就會訪問到無效內(nèi)容。


為什么感到不適呢……
上面說了,只要是按值存儲,子類就不可能添加數(shù)據(jù)成員 —— 否則就會被切割。
也就是說,無論怎樣繼承,數(shù)據(jù)成員就那么幾個……
這里需要的其實(shí)不是class, 而是struct ……
強(qiáng)行使用class, 會使得需要定制的時候,就需要定義一個類 —— 其實(shí)僅僅需要定制一個函數(shù)就可以了。

struct message_checker
{

id;
wp;
lp;
int (*is_ok) (const message_checker&,const message& );

};

message_checker ButtonClickingChecker(WORD id);
message_checker xxxChecker( ... );

所以,怎么看怎么別扭……
當(dāng)然,這是口味問題…… 不值得爭個你死我活……


3. 其他
有些不需要id,wp,lp的checker該怎么辦呢……
需要比id,wp,lp更多參數(shù)的checker又該怎么辦呢……


for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it)
這個效率是很低的……
_HandlerMapIter的命名也是不符合c/c++規(guī)范的…… 都被Gof帶壞了……

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 12:33 by cexer
@OwnWaterloo
【強(qiáng)行使用class, 會使得需要定制的時候,就需要定義一個類 —— 其實(shí)僅僅需要定制一個函數(shù)就可以了。】
這樣的設(shè)計(jì)也是跟框架的整體設(shè)計(jì)有關(guān)系,以后寫了消息映射者你再看看。倒是沒考慮過使用函數(shù)來實(shí)現(xiàn)消息檢查者,我更喜歡它有對象的感覺一點(diǎn),OOP語言嘛,而且類是肯定比函數(shù)有大得多的靈活性的。

【有些不需要id,wp,lp的checker該怎么辦呢……需要比id,wp,lp更多參數(shù)的checker又該怎么辦呢……】
之所以不需要再需要新的成員數(shù)據(jù),是因?yàn)橄⒈旧砭椭挥心菐讉€數(shù)據(jù)。如果需要檢查更復(fù)雜的情況,比如說字符串,這樣的模式也是可以勝任的,以前實(shí)現(xiàn)過。在 MessageChecker 當(dāng)中加一個多態(tài)的 Data 成員,它的多態(tài)復(fù)雜度要比 MessageChecker 本身在堆上分配的小得多,目前沒遇到id,wparam,lparam 三個參數(shù)解決不了問題的情況,暫時不會增加這個 Data 成員。

【從msvc和gcc的實(shí)現(xiàn)來看,虛指針是切割不掉的,它在父類中。虛表就更不存在切割一說……】
這樣走旁門左道是因?yàn)橐郧按_實(shí)遇到過虛函數(shù)失效的問題,應(yīng)該不會真是我忘了用指針調(diào)用了吧?更可能是編譯器bug,因?yàn)橹羔樥{(diào)用虛函數(shù)從小就記得。VC71對于C++標(biāo)準(zhǔn)的支持不是很理想,遇到過很多編譯器報(bào)“cl.exe 內(nèi)部錯誤”,特別是涉及模板的時候。

【是因?yàn)檫@樣可以多態(tài)調(diào)用,但不能保證子類的不變式……調(diào)用的是子類的函數(shù),使用的是父類的數(shù)據(jù)(被切割了)……】
什么不變式要變式哦,聽起來好討厭,能抓貓的就是牛老鼠。寫出來的程序編譯通過鏈接成功,客戶能運(yùn)行它的時候覺得心里爽就行了,所有的規(guī)則應(yīng)該是為這個最終的目的服務(wù)的,而不應(yīng)該為規(guī)則而規(guī)則。

【“for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it)這個效率是很低的……HandlerMapIter的命名也是不符合c/c++規(guī)范的…… 都被Gof帶壞了……】
我倒是并不覺得它的效率很低,因?yàn)橹皇鞘啻蔚难h(huán),不過也希望看看你有更有效率的方法。命令風(fēng)格是被翻炒了千億次的問題了,使程序員從編碼到設(shè)計(jì)都能保持最大的個性,我覺得正是C++的一大特色。就算有人腰上圍一圈炸彈手執(zhí)打火機(jī)來逼我改風(fēng)格,我依然視死如歸地覺得自己的命名方式美妙絕倫無以倫比的,堅(jiān)持風(fēng)格得有血可流有頭可斷發(fā)型不能亂的勇氣啊。所以你就將就著看了。

“規(guī)則,風(fēng)格,效率”,OwnWaterloo兄弟啊,你就是典型的傳說中的學(xué)院派 cpper 。很高興又來占我沙發(fā),同時很是抱歉,這篇博文可能讓你失望了。但我會再接再勵,希望后續(xù)的文章能引起你的興趣,不負(fù)你兩占沙發(fā)的厚望。

# re: GUI框架:消息檢查者[未登錄]  回復(fù)  更多評論   

2009-11-22 12:57 by Loaden
下午還有課!雖然是星期天?。?
唉。
這么好的文章,竟然只能晚上才能拜讀,遺憾?。?
多謝cexer分享,期待下文...

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 13:06 by cexer
@Loaden
謝謝老鄧!也希望你的框架再進(jìn)化!

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 13:53 by 空明流轉(zhuǎn)
對GUI我早就煩了。。。
歸根結(jié)底,GUI是個適合于自動生成的玩意兒。
實(shí)在不行,還是學(xué)Qt吧,MOC解決所有問題。。。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 14:04 by OwnWaterloo
@cexer
剛醒…… 一邊吃飯一邊先把不涉及風(fēng)格的東西解釋一下……

不說不變式了。我也覺得這詞太學(xué)究,所以在后面補(bǔ)了一句:
【調(diào)用的是子類的函數(shù),使用的是父類的數(shù)據(jù)(被切割了)……】

而且,我說錯了……
編譯器會在復(fù)制(以及切割)的同時修正虛指針 —— 忘了……
要memcpy( &b, &d, sizeof(b) ); 才行……


關(guān)于【for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it)】
我指的不是for each。而是遍歷。
不明白為什么只需要遍歷,卻用了一個map。
然后…… 我再看了看這行代碼之上的代碼,這是一個vector……
我又看錯了…… 代碼看得不仔細(xì)……



關(guān)于coding-style。其實(shí)我是最不講究這個的了……
但有些東西不屬于coding-style,而是屬于底線 —— 使用c/c++語言必須遵守的 —— 不能使用語言保留的標(biāo)識符。
語言保留的標(biāo)識符還分了幾種, 統(tǒng)一起來就是以下劃線開始的程序員全都不能使用。
這在很多書里面都有說,又在很多書(比如Gof,不知道算不算經(jīng)典)中犯錯。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 14:16 by 個性藝術(shù)簽名
黃金時代何健飛道護(hù)膚

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 14:38 by OwnWaterloo
@cexer
說說我對gui框架的需求吧……
以用戶的身份…… 用戶的需求總得聽聽吧……


從這里說起:
【目前沒遇到id,wparam,lparam 三個參數(shù)解決不了問題的情況】
我編寫gui的經(jīng)驗(yàn)不多。所以對"這3個參數(shù)是否能解決所有問題"是一點(diǎn)概念都沒有。

不過我想了一個例子,比如我要在xxx-yyy時間段內(nèi),檢查這個消息,返回真,這個時間段外,就返回假。
這個checker就做不到了。 肯定要放到其他地方做,比如hanlder中。


對,這就是我對"框架"感到反感的地方之一 —— 它"限制"你做事的方法。
它會將可能本來是一件完整的工作,拆散,然后分散到框架的一些地方去。

為了完成這個工作,你要按框架所拆分的那樣 —— 需要順著框架的思路,而非我自己的思路 ——只需要id,wp,lp就能check的,放到checker中。其他即使也是check,也得放到handler中。
如果框架拆分得恰當(dāng),就很好,比如 CommandChecker等。
如果框架拆分得不恰當(dāng),就很糟……
所以我對mfc一點(diǎn)好感都沒有。


作為一個了解且不排斥win32api的用戶(當(dāng)然,gui框架大多著眼的都不是這種用戶囧……),我需要的僅僅是彌補(bǔ)一下WndProc,使得它可以找到this —— 這個工作幾乎是必須做的,即使是用C編寫gui的家伙。
然后,就可以從hwnd,msg,wp,lp開始干活了 —— 不需要再學(xué)任何關(guān)于框架的知識,比如要將check放在哪,hanlder放在哪。
我希望框架提供一個,在window上add(掛)多個listener的機(jī)制就差不多了…… 如何分配checker和handler還有cracker的工作,還是將它們?nèi)嘣谝黄?,用戶可以自行決定。

所以我對lambda表達(dá)式特別渴望……
比如上面的代碼,對應(yīng)的偽代碼可能是這樣:

window w;
w.add_listener( void (const msg& m) { if (m.msg==WM_CREATE) cout<<"create\n"<<endl; } );


然后,在每天編程閑暇的時候,發(fā)現(xiàn)庫(而非框架)里面有command_check,我就可以這樣寫代碼了:
w.add_listener( void (const msg&m) { if (is_command(m,id) cout<<command_crack(m).id<<endl; } );

如果我沒發(fā)現(xiàn)command_check,我也可以開始我的工作,只是代碼更繁瑣一些。

這就是庫和框架的一個很大的區(qū)別。
你可以使用熟悉的方式工作,然后慢慢熟悉這個庫,慢慢用它干善自己的工作,并提高效率。
而框架,不對它熟悉到一定程度,就沒法工作……



作為程序員……
我也了解用戶的需求是很惡心的…… 就像你有一篇blog里寫的那樣……
所以,推己及人……
對我提出的這個惡心的需求,聽過之后就當(dāng)是廢話好了~_~

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 14:48 by cexer
@OwnWaterloo
【剛醒…… 一邊吃飯一邊先把不涉及風(fēng)格的東西解釋一下……】
你們那里沒有出太陽?這么大好時光不要拿來睡覺浪費(fèi)了。回復(fù)完這個,我就出去曬太陽了。

【調(diào)用的是子類的函數(shù),使用的是父類的數(shù)據(jù)(被切割了)……】
函數(shù)指針和虛函數(shù)的唯一區(qū)別就是函數(shù)指針少個讀表的動作。所以如果說這樣破壞了“不變式”,那么虛函數(shù)也是一樣的。使用子類函數(shù)讀父類數(shù)據(jù),這種手法是經(jīng)常被用到各種模式中的,這里也不存在切割問題,因?yàn)橐呀?jīng)沒有什么可以切割的。

【語言保留的標(biāo)識符還分了幾種, 統(tǒng)一起來就是以下劃線開始的程序員全都不能使用?!?br>這只專家們一個很學(xué)究氣的建議,跟“謹(jǐn)慎使用內(nèi)聯(lián)函數(shù)”一樣的警示級別。因?yàn)闃?biāo)準(zhǔn)庫的作者確實(shí)使用了一些下劃線的變量,boost 也有很多下劃線開始的東西,像占位符 _1,_2,..._n。為了避免名字沖突確實(shí)少用為妙,但用了也不應(yīng)該看作錯誤。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 15:01 by OwnWaterloo
@cexer
好像沒有,我是宅男……

boost有自己的苦衷。它需要一個簡短的東西來做占位符。
這樣寫就太繁瑣了:
bind( f, first, second , ... ) ();
而且first,second估計(jì)很容易和程序員沖突。
1、2又不是合法的標(biāo)識符。 所以干脆搞成_1, _2,至少不會和程序員沖突了。
bind( f, _1, _2 ... )( ... ); 直觀

而_HandlerMapIter改為HandlerMapIter或者HandlerMapIter_并不會影響什么,而且也符合與C/C++定下的契約。


boost將_1,_2改為1_,2_是不合法的,不能以數(shù)字開頭。
改為其他,不太簡潔……



也不能算完全的學(xué)究。它們不會永遠(yuǎn)被保留。
C99已經(jīng)開始動用這些保留的標(biāo)識符了。
_LongLong, _Complex,_Bool等。
C++也會跟進(jìn)。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 15:06 by OwnWaterloo
將_HandlerMapIter改為HandlerMapIter或者HandlerMapIter_真的沒有壞處。
本來就是一個private,庫用戶不可見的名字。
屬于Handler,也不會和其他重名。
還避免了(可能微乎其微)與_HandlerMapIter宏沖突。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 20:07 by cexer
@OwnWaterloo
【boost有自己的苦衷。它需要一個簡短的東西來做占位符?!?br>你誤會我的意思了。我是同意你的,我的意思是確實(shí)應(yīng)該避免使用,因?yàn)橄?boost,stl 之類的庫很多地使用了這樣的下劃線。

【將_HandlerMapIter改為HandlerMapIter或者HandlerMapIter_真的沒有壞處。
本來就是一個private,庫用戶不可見的名字。屬于Handler,也不會和其他重名。還避免了(可能微乎其微)與_HandlerMapIter宏沖突?!?br>嗯,明白你的意思。其實(shí)這是我的習(xí)慣,外部不可見的都加個下劃線在前頭,沒考慮到與標(biāo)準(zhǔn)庫發(fā)生沖突的情況,你想的更細(xì)致一點(diǎn)。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 21:05 by cexer
【不過我想了一個例子,比如我要在xxx-yyy時間段內(nèi),檢查這個消息,返回真,這個時間段外,就返回假。
這個checker就做不到了。 肯定要放到其他地方做,比如hanlder中?!?br>是可以的啊,可以像下面這樣實(shí)現(xiàn)。
    class TimeMessageChecker:public MessageChecker
    {
    public:
        TimeMessageChecker( timeStart,timeTo )
            :MessageChecker( 0,timeStart,timeTo,&TimeMessageChecker:virtualIsOk )
        {}
    protected:
        static bool virtualIsOk( const MessageChecker* pthis,const Message& )
        {
            timeNow   = GetCurrentTime();
            timeStart = pthis->wparam;
            timeTo    = pthis->lparam;
            return timeNow>=timeStart && timeNow<=timeTo;
        }
    }

可能你的意思是遇到消息參數(shù)裝不下檢查時需要的信息的情況應(yīng)該怎么辦,這里舉例說明一下怎么實(shí)現(xiàn)。比如說要檢查是否某個字符串,這樣的實(shí)現(xiàn)可以裝下任何需要的信息。
先定義一個多態(tài)的數(shù)據(jù)類型,為了效率這里可以使用引用計(jì)數(shù)之類的東西
    class MessageData
    {
    public:
        virtual ~MessageData()
        {}
        virtual void release()
        {
            delete this;
        }
        virtual MessageData* clone() const = 0;
    };

MessageChecker 當(dāng)中有這個成員
    class MessageChecker
    {
    //......
    public:
        MessageData* data;
    }

修改拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù)
    MessageChecker::MessageChecker( const MessageChecker& other )
    {
        if ( data )
        {
            data->release();
            data = NULL;
        }
        if ( other.data )
        {
            data = other.data->clone();
        }
    }

    MessageChecker::~MessageChecker()
    {
        if ( data )
        {
            data->release();
            data = NULL;
        }
    }

定義一個裝字符串的多態(tài)數(shù)據(jù)類
    class StringMessageData:public MessageData
    {
    public:
        StringMessageData( const String& str_ )
            :string( str_ )
        {}
        virtual MessageData* clone() const
        {
            return new StringMessageData(string);
        }
    public:
        String  string;
    };

利用數(shù)據(jù)成員 data 所裝的信息可以檢查字符串的消息檢查者
    class StringMessageChecker:public MessageChecker
    {
    public:
        StringMessageChecker( const String& string )
            :MessageChecker( 0,0,0,&StringMessageChecker::virutalIsOk )
        {
            data = new StringMessageData( string );
        }
    public:
        static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
        {
            StringMessageData* data = (StringMessageData*)pthis->data;
            if ( !data )
            {
                return false;
            }
            std::string stirngToCheck = (const Char*)( message.wparam );
            return stirngToCheck == data->string;
        }
    };

【對,這就是我對"框架"感到反感的地方之一 —— 它"限制"你做事的方法。不對它熟悉到一定程度,就沒法工作……
作為程序員……我也了解用戶的需求是很惡心的…… 就像你有一篇blog里寫的那樣……所以,推己及人……對我提出的這個惡心的需求,聽過之后就當(dāng)是廢話好了~_~】
說實(shí)話我一直對“框架”和“類庫”的概念分不大清楚,我想“框架”從名字上來說多了一個“可以擴(kuò)展,可以填充”的意思,沒有你說的“限制你做事的方法”這種感覺。還有你提到了 GUI 框架用戶的需求。要說明一下的是,實(shí)現(xiàn)的這個消息檢查者只是在框架內(nèi)工作的,方便 GUI 框架的開發(fā)者和擴(kuò)充者使用,GUI 框架的使用者不會接觸到這個東西。最終的用戶只需要使用是像這個樣子:
    window.onCreated += messageHandler( &::_handleCreated );
    window.onCreated += messageHandler ( this,&Window::_handleCreated );

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-22 21:21 by cexer
cppblg 這爛程序,吃空格太嚴(yán)重了。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 02:21 by OwnWaterloo
@cexer
確實(shí)…… cppblog的評論做得不咋嘀……
所以代碼我也沒仔細(xì)看…… 意思差不多明白了。
WndProc可以用這些參數(shù)表達(dá)任意多的參數(shù),所以我們也可以……
對不需要多余參數(shù)的,可以直接使用這幾個。
對需要的,再引入多態(tài)與自由存儲的開銷,并將其包裹在一個值語意的類型中。
是這樣嗎?

這思路不錯。 預(yù)留幾個常用的參數(shù),免得需要參數(shù)就要動用動態(tài)內(nèi)存……



說實(shí)話我一直對“框架”和“類庫”的概念分不大清楚,我想“框架”從名字上來說多了一個“可以擴(kuò)展,可以填充”的意思,沒有你說的“限制你做事的方法”這種感覺。

哎…… 因果弄反了……
為什么需要【擴(kuò)展】框架? —— 就是因?yàn)榭蚣芟拗屏俗鍪碌姆绞剑粩U(kuò)展就沒法做自己需要但框架又不提供的功能了。

說得好聽點(diǎn),叫擴(kuò)展了框架;說得不好聽,叫順了框架的意;再難聽點(diǎn),就是被框架qj了……


以你這篇文章里提到框架來說吧:
1個listener 有 1個m_map
1個m_map 有若干個HanlderPair
1個HandlerPair 包含一個 Checker和HandlerVector
1個HandlerVector 包含若干個Handler

這就是一種限制。MessageChecker也算。

設(shè):從源頭WndProc到實(shí)現(xiàn)某個功能所需要做的全部事情為S。
【必須按這個框架制定的規(guī)則,將S拆分為S1、S2等等,然后安排到框架中的預(yù)定位置。】
比如將事情分為監(jiān)聽、檢查、映射、處理等工作。

這就是我說的限制的意思。【框架規(guī)定了編程的模型】。

假設(shè),有一項(xiàng)需求框架沒有提供,比如Checker需要更多的參數(shù)。
如果還想繼續(xù)使用這個框架,就必須設(shè)計(jì)一個MessageData。
假設(shè)MessageData用戶而非框架提供的,是為【擴(kuò)展】了框架。
假設(shè)MessageData是框架自身提供的,但如果它又有另一個需求沒有滿足呢? 它必須提供一些【可擴(kuò)展點(diǎn)】讓用戶【大部分按框架的思路編程】,并在需要的時候,擴(kuò)展一下框架。否則用戶就會放棄這個框架了。

如果框架的規(guī)定在大部分時候合理,框架就是合理的。
這個尺度很難把握……

總之,用框架編程,框架是主體?;蛘哒f底層的實(shí)現(xiàn)不用用戶操心,只需要注重業(yè)務(wù)邏輯。
用類庫編程,程序員自己是主體。




倒是沒考慮過使用函數(shù)來實(shí)現(xiàn)消息檢查者,我更喜歡它有對象的感覺一點(diǎn),OOP語言嘛,而且類是肯定比函數(shù)有大得多的靈活性的。

這個,其實(shí)也是反的……
OO是思想,class只是它的一種實(shí)現(xiàn)方式。也許使用會比較方便。
但靈活性是不如函數(shù)+數(shù)據(jù)的。

1.
比如C++、java、C#都沒有提供多重分派(multi-method)。
所以,很明顯的,o.method(...); 只是method( o , ... );在1個對象下的簡便形式。
如果需要method( o1, o2, ... ) —— 根據(jù)o1和o2的類型來決定做什么事。這3門語言是不提供支持的,也必須用函數(shù)來表達(dá)。
我知道有個xxx模式…… 但3重分派它又解決不了了…… 治標(biāo)不治本。

當(dāng)然,某種支持多分派的語言可以繼續(xù)提供 o1&o2.method( ... ); 的語法……

2. OO將算法埋葬到自己的類里。
如果這個算法很通用,更好的是將它實(shí)現(xiàn)為一個函數(shù),而不是委身到一個class中。
呃,這種情況在gui中可能不多見。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 02:45 by OwnWaterloo
想起一個說明限制的更通俗的例子:std::for_each。
它就算一種微型框架。
它完成了遍歷的步驟,同時限制了遍歷的方法—— 用一元函數(shù)。

使用for_each時
1. 直接存在現(xiàn)有的一元函數(shù):
vector<void*> m; // memory blocks
for_each(m.begin(), m.end(), free );

2. 可以通過for_each配套提供的一些設(shè)施完成工作:
vector<shape*> s;
for_each(s.begin(), s.end(), bind2nd(mem_fun(&s::draw),canvas) );
for_each(s.begin(), s.end(), mem_fun(&s::reset) );

3. 擴(kuò)展for_each —— 添加更多functor
vector<elem> e;
for_each(e.begin(), e.end(), boost::bind( &e::f, ... ) );

但怎么做,都只是個binder而已。還有一些人做出一些帶有簡單邏輯的functor,然后繼續(xù)使用std::for_each,語法丑陋得,我都不知道怎么寫…… 所以這里就不列了…… toplanguage上可以找到不少……

4. 當(dāng)這些都失效時……
可以說,for_each這個框架在大多數(shù)時候都是雞肋。
讓人不得不繞過它的限制(傳遞一元函數(shù)),直接寫for循環(huán)。


而boost.lambda和C++0x的lambda使得for_each變得實(shí)用了不少。所以框架是否實(shí)用,很難把握。
說for_each是框架嘛…… 主要是因?yàn)樗南拗啤?br>但是它規(guī)模有點(diǎn)小……也可以很輕易的被丟掉。反正是函數(shù)模板,不使用就不會被實(shí)例化。這又有類庫的性質(zhì)。
算是一個不太恰當(dāng)?shù)睦影伞?br>


框架定義了輪廓線,由程序員去填充顏色。
類庫提供調(diào)色板等工具,由程序員去繪制。


另外,傳遞給for_each的一元函數(shù)的調(diào)用語法是:
f( *first ); 而不是 ((*first).*f)();
也是一個自由函數(shù)比成員函數(shù)更普適與靈活的例子。

將成員函數(shù)以自由函數(shù)的語法進(jìn)行調(diào)用:
bind(&c::f)( o, ... );

將自由函數(shù)以成員函數(shù)語法調(diào)用…… 好像要定義一個類……

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 10:17 by cexer
@OwnWaterloo
【框架定義了輪廓線,由程序員去填充顏色。類庫提供調(diào)色板等工具,由程序員去繪制?!?br>這句話比喻得很好。你把框架理解成一種束縛是覺得需要按照它規(guī)定的方式去使用,但類庫甚至 API 何嘗不是也有自己的規(guī)則。我理解的框架和類庫其基本目的都是一樣的:對繁瑣的細(xì)節(jié)進(jìn)行封裝,提供更為便利的接口。這當(dāng)中肯定會損失靈活性,畢竟魚和熊掌很難兼得。但我覺得框架其實(shí)是類庫的進(jìn)化后的身份,從字面意義上來講,“框架”看起來更有彈性,擴(kuò)展的意思。但實(shí)際上大家對這兩個詞的概念并沒有很明白的分辨,比如說到 MFC,有人說框架有人說類庫大家的感覺都是一樣的。

【這個,其實(shí)也是反的……OO是思想,class只是它的一種實(shí)現(xiàn)方式。也許使用會比較方便。但靈活性是不如函數(shù)+數(shù)據(jù)的?!?br>我的想法相反。你說類的靈活性不如函數(shù)加數(shù)據(jù),但類難道不正是建立在函數(shù)和數(shù)據(jù)之上的一個超強(qiáng)結(jié)合體?之所以用C之類的 OP 語言實(shí)現(xiàn)模式不如C++這樣的 OO 語言容易,一大原因正是它缺少類的支持。

【message_checker ButtonClickingChecker(WORD id);
message_checker xxxChecker( ... );】
這是你舉例說明的用函數(shù)來實(shí)現(xiàn)檢查者。你可以嘗試真的用函數(shù)來實(shí)現(xiàn)消息檢查者,這個 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函數(shù)內(nèi)部你各自要怎么實(shí)現(xiàn)?它既要包含不同的數(shù)據(jù),又要包含不同的操作,它們返回完全相同的 message_checker 對象,這個 message_checker 又要怎么實(shí)現(xiàn)才能以一已之身完成眾多不同的功能?由于不同參數(shù)列表的函數(shù)實(shí)際上是完全不同的東西,你甚至不能以統(tǒng)一的方式保存它們管理它們。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 16:05 by 陳梓瀚(vczh)
繼續(xù)攪局:你憑什么認(rèn)為MessageChecker父類的成員變量一定夠用呢?如果夠用的話,就證明你的邏輯已經(jīng)是固定的了,為什么要子類?就一個MessageChecker好了,不用繼承。你矛盾了。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 16:10 by 陳梓瀚(vczh)
@cexer
你說類的靈活性不如函數(shù)加數(shù)據(jù),但類難道不正是建立在函數(shù)和數(shù)據(jù)之上的一個超強(qiáng)結(jié)合體?

類的靈活性在于,你使用shared_ptr<Base>保存了一個Derived*,然后調(diào)用虛函數(shù)。代價(jià)非常低,我經(jīng)常把智能指針放進(jìn)容器,也可以不管誰去釋放,總之會被釋放。而且我這種寫compiler的人對循環(huán)引用十分敏感所以我基本不會犯這種錯誤……

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 16:12 by 陳梓瀚(vczh)
@cexer
【這是你舉例說明的用函數(shù)來實(shí)現(xiàn)檢查者。你可以嘗試真的用函數(shù)來實(shí)現(xiàn)消息檢查者,這個 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函數(shù)內(nèi)部你各自要怎么實(shí)現(xiàn)?它既要包含不同的數(shù)據(jù),又要包含不同的操作,它們返回完全相同的 message_checker 對象,這個 message_checker 又要怎么實(shí)現(xiàn)才能以一已之身完成眾多不同的功能?由于不同參數(shù)列表的函數(shù)實(shí)際上是完全不同的東西,你甚至不能以統(tǒng)一的方式保存它們管理它們。】

TR1有functor給你用,解決了這些問題。無論是函數(shù)指針,成員函數(shù)指針,有operator()的類,統(tǒng)統(tǒng)都可以放進(jìn)去。所以我覺得你的listener應(yīng)該是
vector<function<bool(Message&)>>,然后處理了返回true,沒處理返回false。function自己可以有自己的成員,你可以把有operator()的對象放進(jìn)去,把成員函數(shù)放進(jìn)去什么的,統(tǒng)統(tǒng)都可以。要堅(jiān)定不移地使用這些東西。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 16:13 by 陳梓瀚(vczh)
@cexer
而且,千萬不要去考慮標(biāo)準(zhǔn)庫里面什么類的性能如何的問題,你永遠(yuǎn)假定他們是用魔法完成的,不需要任何CPU周期。不然你一行代碼都寫不出來。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 16:14 by 陳梓瀚(vczh)
@cexer
至于什么是框架什么是類庫,有一個很容易的判斷標(biāo)準(zhǔn):template method就是框架。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 16:15 by 陳梓瀚(vczh)
@cexer
所以框架和類庫可以互相包含,這也是很常見的。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 16:57 by cexer
@陳梓瀚(vczh)
【繼續(xù)攪局:你憑什么認(rèn)為MessageChecker父類的成員變量一定夠用呢?如果夠用的話,就證明你的邏輯已經(jīng)是固定的了,為什么要子類?就一個MessageChecker好了,不用繼承。你矛盾了。】
歡迎攪局!你提出一問題確實(shí)有“以子之矛攻子之盾”的殺傷力,不過幸好我不是既賣矛又賣盾的。關(guān)于數(shù)據(jù)不夠用的情況 OwnWaterloo 的也提出過,你可以倒著往上看給 OwnWaterloo 的回復(fù),在 MessageChecker 當(dāng)中加一個多態(tài)的 Data 成員可以放下所有東西,我還是把這東西更新到博文里去吧免得又有人問?!斑壿嫛钡年P(guān)鍵顯然不是數(shù)據(jù),而是那個檢查的動作。就像銀行存了五千萬,不拿出去揮霍也住不上豪宅一樣,成員變量夠用成員函數(shù)不夠用也是白搭,必須得要改寫。要想一個類,不繼承,不改寫,而要滿足不斷出現(xiàn)的需求,這肯定是不能完成的任務(wù)。“數(shù)據(jù)夠用”和“函數(shù)改寫”并矛盾。

【類的靈活性在于,你使用shared_ptr<Base>保存了一個Derived*,然后調(diào)用虛函數(shù)。代價(jià)非常低,我經(jīng)常把智能指針放進(jìn)容器,也可以不管誰去釋放,總之會被釋放。而且我這種寫compiler的人對循環(huán)引用十分敏感所以我基本不會犯這種錯誤……】
你是建議我把 MessageChecker 在堆上生成用智能指針管理起來吧。我也覺得這樣確實(shí)可以使用真正強(qiáng)大的虛函數(shù),但這樣要付出每次都在堆上構(gòu)造的效率成本和管理上的復(fù)雜性,而實(shí)際上大多數(shù)情況下這樣的付出是不必的:在 Windows 的消息機(jī)制下,消息檢查大多數(shù)時候需要的數(shù)據(jù)是很少的,用 WPARAM 和 LPARAM 就可以裝下,但確實(shí)有需要在堆上保存數(shù)據(jù)的情況,所以我在 MessgaeChecker 增加了一個多態(tài)的 Data 成員來保存,它只有極少時候的才會從堆上生成,這樣盡量避免了堆上生成的復(fù)雜性和效率損失,而完成的功能又絲毫不減。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 17:03 by 陳梓瀚(vczh)
@cexer
使用tr1::function<bool(Message&)>,毫無管理復(fù)雜性,幾乎沒有效率成本。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 17:04 by 陳梓瀚(vczh)
@陳梓瀚(vczh)
但是實(shí)際上windows的消息很亂,事件跟消息并不是一一對上號的。因此MessageChecker之類的東西最終用戶是看不到的,你要根據(jù)每一個控件的具體情況,重新整理出一系列事件,才能讓用的時候真正爽快起來。這里沒有技術(shù)問題。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 17:31 by cexer
@陳梓瀚(vczh)
【要堅(jiān)定不移地使用這些東西。 使用tr1::function<bool(Message&)>,毫無管理復(fù)雜性,幾乎沒有效率成本。】
把 MessageChecker::isOk(const Message&) 的實(shí)現(xiàn)放到 bool MessageChecker::operator()(const Message&) 中去,函數(shù)體MessageChecker 就變成了和 function 類似的東西,但這樣又有什么本質(zhì)區(qū)別呢。tr1::function 的功能雖然強(qiáng)大,用它去實(shí)現(xiàn)消息檢查者,該寫的邏輯還得寫,省不了功夫,擴(kuò)展性也是個問題。

【至于什么是框架什么是類庫,有一個很容易的判斷標(biāo)準(zhǔn):template method就是框架?!?br>謝謝,很多書對這個也言之不詳,只是叫著爽就行了,所以我自己也就不大明白。我從此以后照你這個標(biāo)準(zhǔn)去評判好了。

【而且,千萬不要去考慮標(biāo)準(zhǔn)庫里面什么類的性能如何的問題,你永遠(yuǎn)假定他們是用魔法完成的,不需要任何CPU周期。不然你一行代碼都寫不出來?!?br>我是從不考慮那些的:標(biāo)準(zhǔn)庫的效率再差,也不會到我放棄使用它們的地步的。另外以我的水平寫出來的東西絕對比它的慢,哪有資格嫌棄人家。

【但是實(shí)際上windows的消息很亂,事件跟消息并不是一一對上號的。因此MessageChecker之類的東西最終用戶是看不到的,你要根據(jù)每一個控件的具體情況,重新整理出一系列事件,才能讓用的時候真正爽快起來。這里沒有技術(shù)問題?!?br>你說得對,框架用戶是看不到 MessageChecker 的,他們看到的是 onCreated,onClosed,onClicked 之類的東西。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 18:25 by 陳梓瀚(vczh)
@cexer
function的好處就是你可以使用子類的同時不用管delete啊

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-23 20:09 by cexer
@陳梓瀚(vczh)
【function的好處就是你可以使用子類的同時不用管delete啊】
想了下用 tr1::function 來實(shí)現(xiàn)確實(shí)要比手寫類簡單得多,函數(shù)和參數(shù)綁定功能在這里很有用處,有多少參數(shù)都不用搞個多態(tài)的 Data 了,也不用自己去 delete 。確實(shí)很強(qiáng)大。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-24 00:52 by OwnWaterloo
@cexer
【我的想法相反。你說類的靈活性不如函數(shù)加數(shù)據(jù),但類難道不正是建立在函數(shù)和數(shù)據(jù)之上的一個超強(qiáng)結(jié)合體?之所以用C之類的 OP 語言實(shí)現(xiàn)模式不如C++這樣的 OO 語言容易,一大原因正是它缺少類的支持?!?br>
是的,類是函數(shù)和數(shù)據(jù)的結(jié)合,還加上數(shù)據(jù)隱藏。
超強(qiáng)到談不上……
我也說了的,對單個object,這3門語言的OO實(shí)現(xiàn)確實(shí)是方便。

靈活與范化的東西,通常比較難用和不方便。



這是你舉例說明的用函數(shù)來實(shí)現(xiàn)檢查者。你可以嘗試真的用函數(shù)來實(shí)現(xiàn)消息檢查者,這個 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函數(shù)內(nèi)部你各自要怎么實(shí)現(xiàn)?它既要包含不同的數(shù)據(jù),又要包含不同的操作,它們返回完全相同的 message_checker 對象,這個 message_checker 又要怎么實(shí)現(xiàn)才能以一已之身完成眾多不同的功能?由于不同參數(shù)列表的函數(shù)實(shí)際上是完全不同的東西,你甚至不能以統(tǒng)一的方式保存它們管理它們。

你那個MessageChecker怎么完成以一己之身完成,眾多不同功能的?
同樣可以應(yīng)用到message_checker 上,兩者是相通的,都是靠結(jié)構(gòu)體中嵌的一個函數(shù)指針。


-------- -------- -------- --------

關(guān)于那個id,wp,lp,我覺得還不錯。

Windows確實(shí)需要將系統(tǒng)與用戶之間使用【一條統(tǒng)一的渠道【來通信。
這條渠道演化到【極端】就是這種形式:
void* (*)(void* all_parameter);

但如果所有消息都需要使用動態(tài)內(nèi)存,就有點(diǎn)浪費(fèi)。
添加一些其他比較常用的值參數(shù) vs 和一個值參數(shù)都不用,就是一種折衷。
完全不用,肯定每次都需要動態(tài)內(nèi)存分配。
值參數(shù)多了,又可能在一些情況用不上。

所以,Windows最后選擇了
LRESULT (CALLBACK*)(HWND, UINT, WPARAM, LPARAM);
參數(shù)少,直接傳遞,參數(shù)多,將某個參數(shù)理解為指針……
(此處純猜測……)


所以,迎合WndProc的設(shè)計(jì)還不錯。


-------- -------- -------- --------

其實(shí)我是被這段語法吸引的:
window.onCreated += messageHandler( &::_handleCreated );
window.onCreated += messageHandler ( this,&Window::_handleCreated );

很像boost.signal。


而且真正吸引人的是樓主說它是廉價(jià)工人~_~


boost.function如果不用支持bind,可能可以不動用動態(tài)存儲。
要支持bind…… 而且不使用動態(tài)存儲…… 好像不行……
boost.signal肯定是要動用動態(tài)存儲的。


等著樓主這部分的實(shí)現(xiàn)了~_~

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-24 10:38 by cexer
【你那個MessageChecker怎么完成以一己之身完成,眾多不同功能的?
同樣可以應(yīng)用到message_checker 上,兩者是相通的,都是靠結(jié)構(gòu)體中嵌的一個函數(shù)指針?!?br>我的意思是函數(shù)本身不容易實(shí)現(xiàn),只用函數(shù)主要有數(shù)據(jù)保存的問題。加上 function 來實(shí)現(xiàn)就容易了,直接綁定一些檢查函數(shù)和檢查的數(shù)據(jù):
    typedef boost::function<bool (const Message&)> MessageChecker

    bool checkCommand( const Message& message,WORD id,WORD code );
    MessageChecker checkYesClicked = boost::bind( &checkCommand,_1,IDYES,BN_CLICKED );

    bool checkMessage( const Message& message,UINT messageId, );
    MessageChecker checkCreated = boost::bind( &checkMessage,_1,WM_CREATE );

【其實(shí)我是被這段語法吸引的:
    window.onCreated += messageHandler( &::_handleCreated );
    window.onCreated += messageHandler ( this,&Window::_handleCreated );
很像boost.signal。
而且真正吸引人的是樓主說它是廉價(jià)工人~_~】
+= 之前的和之后的是兩個不同的東西,onCreated 是幫助消息映射的一個東西,其實(shí)就是一個轉(zhuǎn)發(fā)調(diào)用,所以成本很低。messageHandler() 是消息處理者和消息分解者共同組成的東西。在這里 ::_handleCreated 和 Window::_handleCreated 參數(shù)列表可以是不同的,這里和 boost.signal 不大一樣,因?yàn)橐粋€ signal 只能對應(yīng)一種 signautre 的 functor 的。

【boost.function如果不用支持bind,可能可以不動用動態(tài)存儲。
要支持bind…… 而且不使用動態(tài)存儲…… 好像不行……
boost.signal肯定是要動用動態(tài)存儲的?!?br>嗯。主要是管理起來很容易了,任何的參數(shù)直接綁定進(jìn)去就行了,不用自己弄個堆對象來保存,然后還要記得刪除。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-24 16:47 by 陳梓瀚(vczh)
@cexer
所以說嘛,用標(biāo)準(zhǔn)庫的時候要假設(shè)他們是0CPU消耗,這樣你就不會想太多多余的事情了。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-26 14:59 by lch
GUI 框架固然重要,
但漂亮的控件,和所見即所得的GUI設(shè)計(jì)器更加重要

Mac OS, IPhone, Andriod都是如此

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-26 17:28 by cexer
@lch
一個是手段,一個是目的,何來重要和更重要之說。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-27 00:10 by lch
我的意思是原生的框架已經(jīng)夠用的情況下,控件的成熟度顯得更重要。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-11-30 10:54 by 俠客西風(fēng)
高手討論吧,

新手路過,學(xué)習(xí)中...

以后水平高了再和你們討論...

又見 OwnWaterloo 兄,
還認(rèn)識了cexer,陳梓瀚(vczh) 高手...

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-12-01 10:10 by cexer
@lch
這個我同意。不過到后來的豐富控件,已經(jīng)基本上是體力活了。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2009-12-19 16:51 by Goteet
我想問個問題,假如消息接受者在收到消失的時候要把消息發(fā)送者刪除怎么辦

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2010-01-05 12:30 by 赤腳流浪人
怎么加你好友啊,很喜歡你寫的這些東西

# re: GUI框架:消息檢查者[未登錄]  回復(fù)  更多評論   

2011-05-22 21:35 by Neo
關(guān)于GUI的文章停了嘛,我在恭候呢。

# re: GUI框架:消息檢查者  回復(fù)  更多評論   

2012-09-04 09:57 by Richard Wei
mark下
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产精品亚洲综合| 狠狠88综合久久久久综合网| 欧美喷潮久久久xxxxx| 国产精品美女久久| 日韩亚洲精品电影| 欧美国产日本韩| 久久福利毛片| 国产日产欧美精品| 欧美亚洲一区在线| 亚洲少妇自拍| 欧美日韩天堂| 亚洲一区黄色| 中文有码久久| 国产精品视屏| 久久精品成人| 久久gogo国模裸体人体| 国内成人精品2018免费看| 久久精品免费| 久久精品综合网| 狠狠88综合久久久久综合网| 久久全国免费视频| 理论片一区二区在线| 亚洲激情一区| 亚洲精品久久久久久久久| 久久婷婷亚洲| 日韩视频免费观看| 99精品视频免费在线观看| 欧美三区在线| 久久国产成人| 久久嫩草精品久久久精品| 久久男女视频| 99在线|亚洲一区二区| 亚洲精品一区二区在线观看| 欧美精品自拍| 亚洲欧美中文日韩在线| 午夜亚洲精品| 在线观看国产一区二区| 亚洲国产日韩一区二区| 欧美日韩精品在线| 欧美一区二区三区免费观看视频| 欧美影视一区| 亚洲精品美女久久7777777| 一区二区冒白浆视频| 国产亚洲精品久久飘花| 欧美成人精品| 国产精品高潮视频| 老司机亚洲精品| 欧美日韩一区二区三区在线视频| 欧美亚洲三级| 老司机午夜精品视频| 亚洲欧美国产视频| 老牛嫩草一区二区三区日本| 亚洲视频中文字幕| 久久久国产一区二区| 亚洲视频二区| 老妇喷水一区二区三区| 欧美亚洲视频在线看网址| 欧美国产日韩亚洲一区| 久久久午夜视频| 国产精品久久久91| 欧美激情亚洲精品| 国产在线国偷精品产拍免费yy| 久久久久网站| 亚洲精品日韩一| 国产真实精品久久二三区| 亚洲精品欧美精品| 1000部精品久久久久久久久| 亚洲一二三区在线| 日韩午夜电影| 快射av在线播放一区| 久久精品视频在线免费观看| 欧美视频在线一区二区三区| 欧美夫妇交换俱乐部在线观看| 国产精品三上| 99视频精品全部免费在线| 亚洲国产成人高清精品| 欧美在线视频观看| 欧美亚洲免费| 国产精品视频专区| 在线性视频日韩欧美| 99精品国产一区二区青青牛奶| 久久精品日韩| 久久精彩视频| 国产精品美女久久久久av超清| 亚洲全部视频| 亚洲免费观看在线视频| 蜜桃久久精品一区二区| 你懂的国产精品永久在线| 国内精品一区二区三区| 欧美一区二区三区久久精品| 欧美亚洲尤物久久| 国产欧美精品日韩精品| 亚洲综合色网站| 欧美一级理论片| 国产欧美日韩在线观看| 亚洲欧洲av一区二区| 久久xxxx精品视频| 国产一区二区三区高清| 欧美在线|欧美| 久久综合伊人77777麻豆| 精品动漫3d一区二区三区免费| 欧美专区在线| 欧美不卡视频一区发布| 亚洲精品网址在线观看| 欧美日韩国产综合视频在线观看中文 | 男同欧美伦乱| 亚洲精品一区中文| 欧美日韩国产区一| 亚洲色无码播放| 欧美一区二区性| 精品av久久久久电影| 欧美不卡三区| 一本色道久久综合亚洲精品不卡 | 欧美一区二区私人影院日本| 国产一区二区三区无遮挡| 久久久久国产精品一区二区| 欧美黑人国产人伦爽爽爽| 日韩视频免费大全中文字幕| 国产精品美女一区二区在线观看 | 欧美日韩亚洲国产一区| 亚洲影院在线| 美女亚洲精品| 一区二区三区产品免费精品久久75 | 国产精品美女久久久久久免费| 西西裸体人体做爰大胆久久久| 久久婷婷国产综合精品青草| 亚洲人www| 国产精品v欧美精品v日韩精品| 亚洲欧美国产高清va在线播| 免费在线看成人av| 亚洲最新色图| 国产亚洲精品aa午夜观看| 欧美福利电影网| 亚洲女人天堂av| 欧美激情成人在线| 午夜精品福利一区二区蜜股av| 精品91在线| 国产精品激情电影| 免费一区二区三区| 午夜精品在线观看| 亚洲精品久久久久| 六月天综合网| 午夜精品网站| 一区二区高清在线观看| 黄色成人在线网址| 国产精自产拍久久久久久| 欧美高清不卡| 久久精品官网| 亚洲欧美清纯在线制服| 亚洲美女性视频| 欧美成人影音| 久久久亚洲影院你懂的| 亚洲欧美美女| 一区二区三区日韩精品| 亚洲国产高清高潮精品美女| 国产亚洲毛片在线| 国产精品久久| 欧美日韩在线播放三区四区| 你懂的网址国产 欧美| 性色av一区二区三区红粉影视| 亚洲日本理论电影| 欧美激情免费观看| 另类酷文…触手系列精品集v1小说| 午夜在线视频一区二区区别| 99国产精品久久久久久久久久 | 久久久亚洲成人| 久久国产精品一区二区三区| 亚洲欧美日韩国产综合精品二区| 亚洲乱码国产乱码精品精可以看| 好男人免费精品视频| 国产日韩欧美综合精品| 国产精品婷婷| 国产精品视频一区二区三区| 国产精品久久久久久久久果冻传媒| 欧美激情一区二区三区全黄| 蜜桃久久精品乱码一区二区| 美女免费视频一区| 美女诱惑黄网站一区| 老鸭窝毛片一区二区三区| 久久综合电影一区| 美女脱光内衣内裤视频久久网站| 蜜臀av性久久久久蜜臀aⅴ| 久久亚洲综合网| 美女视频黄免费的久久| 欧美成人三级在线| 欧美另类视频| 欧美私人网站| 国产精品一区二区久久| 国产午夜精品在线观看| 国产日韩综合| 狠狠爱www人成狠狠爱综合网| 黄色精品在线看| 亚洲欧洲午夜| 亚洲制服丝袜在线| 亚洲精品一区在线观看香蕉| 夜色激情一区二区| 亚洲一区二区三区精品在线| 午夜精品久久| 久久综合久久综合久久| 欧美激情精品久久久久久变态|