• <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>

            cexer

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

            GUI框架:消息檢查者

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

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

            1 胸口碎大石

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

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

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

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

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

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

            2.1 其實很簡單

              要有消息檢查者,首先得有個消息(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)在開始定義消息檢查者。消息檢查者的職責:根據(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)該可以被后繼者重寫以實現(xiàn)不同的檢查方式,所以它應(yīng)該是虛函數(shù),這樣后繼者可以這樣的形式擴充檢查者隊伍:

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

            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;
            

              堆上生成是萬惡之源。誰來負責銷毀?何時銷毀?效率問題怎么辦?有的人此時可能想到了引用計數(shù),小對象分配技術(shù),內(nèi)存池。。。只是一個消息檢查的動作就用那么昂貴的實現(xiàn),就像花兩萬塊買張鼠標墊一樣讓人難以接受。所以我們想保存消息映射者的對象 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,編譯器會鐵手無情地對它們進行切割,切割得它們體無完膚搖搖欲墜,只剩下 MessageChecker 子對象為止 。砍頭不要緊,只要主義真,但是要是切割過程中切掉了虛函數(shù)表這個命根子就完蛋了,在手執(zhí)電鋸的編譯器面前玩耍虛函數(shù),很難說會發(fā)生什么可怕的事情。

              有一種解決方案是我們不使用真正的虛函數(shù),而是自己模擬虛函數(shù)的功能。具體辦法是在 MessageChecker 當中保存一個函數(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 的默認實現(xiàn)是只檢查消息id,這也是 MFC 的映射宏 MESSAGE_HANDLER 干的事情。現(xiàn)在解決了不要堆生成與要求多態(tài)的矛盾關(guān)系,真正完成了消息檢查者的定義。

            2.3 擴充隊伍

              要擴展消息檢查者隊伍,可以從 MessageChecker 派生新類,定義自己的檢查函數(shù)(virtualIsOk 或者其它名字都可以)并將 MessageChecker::m_visok 指向這個函數(shù)。要注意的是因為存在對象切割問題,所以派生類不應(yīng)該定義新的數(shù)據(jù)成員,畢竟切掉花花草草也是非常不好的。舉例說明派生方法。

              比如從消息檢查者派生一個命令消息(Command)的檢查者 CommandChecker:它定義了一個函數(shù) virtualIsOk ,此函數(shù)檢查消息id是否為 WM_COMMAND ,并進一步檢查控件id和命令code,然后將指針 Message::m_visok 指向這個函數(shù) CommandChecker::virualIsOk,這樣 MessageChecker::isOk 實際上就是調(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,并進一步檢查控件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 ,并進一步檢查控件 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ā)揮想像進行擴展,這個消息檢查者可以做很多事情。比如定義一個范圍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,并進一步檢查控件 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: };
            

              定義一個按鈕點擊消息的消息檢查者:

               1: // 按鈕點擊的命令消息檢查者
            
               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,并進一步檢查命令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é)是新加的。因為有人提出數(shù)據(jù)容納不下的問題:MessageChecker 目前的定義只能容納 WPAWAM,LPARAM 兩個參數(shù)大小的數(shù)據(jù),而因為在存在對象切割問題,子類又不能定義新的數(shù)據(jù),那如果 WPARAM,LPARAM 裝不下需要的數(shù)據(jù)了怎么辦?難道就只能把數(shù)據(jù)在子類當中定義,然后每次都在堆上生成 MessageChecker 嗎?

              我在這里的解決方案是,在 MessageChecker 里面定義一個多態(tài)的數(shù)據(jù)成員,這個成員大多數(shù)時候是空的,當需要的時候從堆上生成它,用它來裝下數(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 當中加入這個數(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: };

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

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

            2.5 龍?zhí)籽輪T

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

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

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

              消息監(jiān)聽者(MessageListener )保存有消息檢查者與消息處理者的映射,并提供接口操作這些映射,當監(jiān)聽到消息的時候,消息監(jiān)聽者調(diào)用映射中的檢查者進行檢查,如果通過檢查則調(diào)用消息處理者來進行處理。消息監(jiān)聽者干的都是添加/刪除/查找這樣的體力活,看似實現(xiàn)起來用不著大腦,可是當涉及到真正的消息處理時情況會變得復(fù)雜,會遇到消息重入之類的問題。所以真正的實現(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 ) ,實現(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 進行測試

              龍?zhí)锥家丫臀唬瑢?dǎo)演喊:"action!",現(xià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ù)都沒有用到一個,更沒有談到效率,優(yōu)化什么的,肯定是不足以誘惑技術(shù)流的 cpper 們的。

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

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

            Feedback

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

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




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

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

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


            2. class vs struct
            當然,對這個問題蠻適合的。
            因為樓主的方案在子類中同樣不能塞新的數(shù)據(jù),否則一樣會被切割。
            然后,調(diào)用那個函數(shù)指針時,就會訪問到無效內(nèi)容。


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

            struct message_checker
            {

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

            };

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

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


            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
            【強行使用class, 會使得需要定制的時候,就需要定義一個類 —— 其實僅僅需要定制一個函數(shù)就可以了。】
            這樣的設(shè)計也是跟框架的整體設(shè)計有關(guān)系,以后寫了消息映射者你再看看。倒是沒考慮過使用函數(shù)來實現(xiàn)消息檢查者,我更喜歡它有對象的感覺一點,OOP語言嘛,而且類是肯定比函數(shù)有大得多的靈活性的。

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

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

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

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

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

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

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

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

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

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

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

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

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

            不說不變式了。我也覺得這詞太學(xué)究,所以在后面補了一句:
            【調(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……
            我又看錯了…… 代碼看得不仔細……



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

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

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

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

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


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

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


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

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


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

            所以我對lambda表達式特別渴望……
            比如上面的代碼,對應(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里寫的那樣……
            所以,推己及人……
            對我提出的這個惡心的需求,聽過之后就當是廢話好了~_~

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

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

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

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

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

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

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

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


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



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

            # 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有自己的苦衷。它需要一個簡短的東西來做占位符。】
            你誤會我的意思了。我是同意你的,我的意思是確實應(yīng)該避免使用,因為像 boost,stl 之類的庫很多地使用了這樣的下劃線。

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

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

            2009-11-22 21:05 by cexer
            【不過我想了一個例子,比如我要在xxx-yyy時間段內(nèi),檢查這個消息,返回真,這個時間段外,就返回假。
            這個checker就做不到了。 肯定要放到其他地方做,比如hanlder中。】
            是可以的啊,可以像下面這樣實現(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)該怎么辦,這里舉例說明一下怎么實現(xiàn)。比如說要檢查是否某個字符串,這樣的實現(xiàn)可以裝下任何需要的信息。
            先定義一個多態(tài)的數(shù)據(jù)類型,為了效率這里可以使用引用計數(shù)之類的東西
                class MessageData
                {
                public:
                    virtual ~MessageData()
                    {}
                    virtual void release()
                    {
                        delete this;
                    }
                    virtual MessageData* clone() const = 0;
                };

            MessageChecker 當中有這個成員
                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里寫的那樣……所以,推己及人……對我提出的這個惡心的需求,聽過之后就當是廢話好了~_~】
            說實話我一直對“框架”和“類庫”的概念分不大清楚,我想“框架”從名字上來說多了一個“可以擴展,可以填充”的意思,沒有你說的“限制你做事的方法”這種感覺。還有你提到了 GUI 框架用戶的需求。要說明一下的是,實現(xiàn)的這個消息檢查者只是在框架內(nèi)工作的,方便 GUI 框架的開發(fā)者和擴充者使用,GUI 框架的使用者不會接觸到這個東西。最終的用戶只需要使用是像這個樣子:
                window.onCreated += messageHandler( &::_handleCreated );
                window.onCreated += messageHandler ( this,&Window::_handleCreated );

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

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

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

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

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



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

            哎…… 因果弄反了……
            為什么需要【擴展】框架? —— 就是因為框架限制了做事的方式,不擴展就沒法做自己需要但框架又不提供的功能了。

            說得好聽點,叫擴展了框架;說得不好聽,叫順了框架的意;再難聽點,就是被框架qj了……


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

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

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

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

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

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

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




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

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

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

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

            2. OO將算法埋葬到自己的類里。
            如果這個算法很通用,更好的是將它實現(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. 擴展for_each —— 添加更多functor
            vector<elem> e;
            for_each(e.begin(), e.end(), boost::bind( &e::f, ... ) );

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

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


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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            【類的靈活性在于,你使用shared_ptr<Base>保存了一個Derived*,然后調(diào)用虛函數(shù)。代價非常低,我經(jīng)常把智能指針放進容器,也可以不管誰去釋放,總之會被釋放。而且我這種寫compiler的人對循環(huán)引用十分敏感所以我基本不會犯這種錯誤……】
            你是建議我把 MessageChecker 在堆上生成用智能指針管理起來吧。我也覺得這樣確實可以使用真正強大的虛函數(shù),但這樣要付出每次都在堆上構(gòu)造的效率成本和管理上的復(fù)雜性,而實際上大多數(shù)情況下這樣的付出是不必的:在 Windows 的消息機制下,消息檢查大多數(shù)時候需要的數(shù)據(jù)是很少的,用 WPARAM 和 LPARAM 就可以裝下,但確實有需要在堆上保存數(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)
            但是實際上windows的消息很亂,事件跟消息并不是一一對上號的。因此MessageChecker之類的東西最終用戶是看不到的,你要根據(jù)每一個控件的具體情況,重新整理出一系列事件,才能讓用的時候真正爽快起來。這里沒有技術(shù)問題。

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

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

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

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

            【但是實際上windows的消息很亂,事件跟消息并不是一一對上號的。因此MessageChecker之類的東西最終用戶是看不到的,你要根據(jù)每一個控件的具體情況,重新整理出一系列事件,才能讓用的時候真正爽快起來。這里沒有技術(shù)問題。】
            你說得對,框架用戶是看不到 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 來實現(xiàn)確實要比手寫類簡單得多,函數(shù)和參數(shù)綁定功能在這里很有用處,有多少參數(shù)都不用搞個多態(tài)的 Data 了,也不用自己去 delete 。確實很強大。

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

            2009-11-24 00:52 by OwnWaterloo
            @cexer
            【我的想法相反。你說類的靈活性不如函數(shù)加數(shù)據(jù),但類難道不正是建立在函數(shù)和數(shù)據(jù)之上的一個超強結(jié)合體?之所以用C之類的 OP 語言實現(xiàn)模式不如C++這樣的 OO 語言容易,一大原因正是它缺少類的支持。】

            是的,類是函數(shù)和數(shù)據(jù)的結(jié)合,還加上數(shù)據(jù)隱藏。
            超強到談不上……
            我也說了的,對單個object,這3門語言的OO實現(xiàn)確實是方便。

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



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

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


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

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

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

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

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


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


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

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

            很像boost.signal。


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


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


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

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

            2009-11-24 10:38 by cexer
            【你那個MessageChecker怎么完成以一己之身完成,眾多不同功能的?
            同樣可以應(yīng)用到message_checker 上,兩者是相通的,都是靠結(jié)構(gòu)體中嵌的一個函數(shù)指針。】
            我的意思是函數(shù)本身不容易實現(xiàn),只用函數(shù)主要有數(shù)據(jù)保存的問題。加上 function 來實現(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 );

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

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

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

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

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

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

            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 俠客西風
            高手討論吧,

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

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

            又見 OwnWaterloo 兄,
            還認識了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下
            久久久久久亚洲精品影院| 狠狠综合久久综合88亚洲| 久久午夜电影网| 久久国产免费直播| 噜噜噜色噜噜噜久久| 久久国产精品一国产精品金尊| 777久久精品一区二区三区无码| 办公室久久精品| 亚洲AV无码久久| 久久精品国产精品亚洲| 无码久久精品国产亚洲Av影片 | 久久青青草视频| 久久精品国产亚洲欧美| 久久精品国产亚洲av麻豆图片| 97精品伊人久久大香线蕉app| 色婷婷综合久久久久中文字幕 | 欧美一级久久久久久久大| 亚洲va国产va天堂va久久| 香蕉久久影院| 久久激情亚洲精品无码?V| 国产精品一区二区久久精品| 亚洲va中文字幕无码久久| 无码人妻久久一区二区三区蜜桃 | 久久久久亚洲AV成人网人人网站 | 伊人 久久 精品| 久久免费视频6| 久久久精品国产Sm最大网站| 2020最新久久久视精品爱| 久久91精品国产91久久麻豆| 久久久久人妻一区精品色| 久久久女人与动物群交毛片| 成人午夜精品无码区久久| 久久久久久久久久久| 亚洲精品无码久久久久去q | 热99RE久久精品这里都是精品免费 | 亚洲va久久久噜噜噜久久男同| 麻豆av久久av盛宴av| 欧美伊人久久大香线蕉综合| 亚洲精品无码久久一线| 久久婷婷五月综合国产尤物app | 国产激情久久久久久熟女老人|