cexer

cexer
posts - 12, comments - 334, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

GUI框架:消息检查者

Posted on 2009-11-22 02:36 cexer 阅读(3677) 评论(40)  编辑 收藏 引用 所属分类: GUI

转帖请注明出处 http://www.cppblog.com/cexer/archive/2009/11/22/101591.html

1 胸口碎大石

  紧接上话:GUI框架:谈谈框架,写写代码 。废话是肯定首先要说的,既为了承前启后点明主题,也为了拉拢人心骗取回复。本来我想像自己上篇博文写出来势必像胸口碎大石一样威猛有力,在街边拉开阵势,大吼一声举起锤子正要往下砸的时候,却看到几位神仙手提酱油瓶优雅地踏着凌波微步路过,听他们开口闭口说的都是六脉神剑啊,九阴真级啊这些高级东西,我的威猛感一下消失于无形,取而代之的是小孩子玩水枪的渺小。但是不管怎么样摊子都铺开了,这一锤子不砸下去,对不起那凉了半天石头的胸肌。

  在此之前首先得感谢一下各位酱油众。无论你们是看热闹的还是砸场子的,你们的围观都令我的博文增光不少。特别要感谢那几位打架的神仙,你们使上篇博文真正变得有思想交锋的精彩。我觉得你们的那些想法和争论都非常有价值,建议你们不要只让它们在这个角落里藏着,都写到自己的博客上去让更多的人看到吧。

  走过路过不要错过,有钱的捧个钱场,没钱的继续挥舞你的酱油瓶加油呐喊,我这一锤要砸下去了!

2 实现消息检查者

  上文将消息框架分为几个部分,这篇博文实现其中的消息检查者。经典的用 API 编写 GUI 程序的方式当中,消息检查都是用 if 或者 switch 语句进行的:

   1: // 经典的 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 语句上的皇帝的新衣。这种消息检查方式的好处是速度快,不用额外的空间消耗,但坏处更明显:不容易扩充。我觉得在好处和坏处之间的取舍很容易,有必要单独给消息检查的过程实现一个更具 OO 含义的执行者:消息检查者(MessageChecker )。

2.1 其实很简单

  要有消息检查者,首先得有个消息(Message)。上篇博文中的消息定义虽然非常简单,却完全可以胜任目前需要的工作,因此我们直接复制过来。

   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: };

  有了消息,现在开始定义消息检查者。消息检查者的职责:根据某种条件检查消息并返回(是,否)的检查结果,所以它既应该有用于检查的数据,也应该有用于检查的动作函数,并且该函数检查返回布尔值。这不是很容易就出来了吗:

   1: // 领衔的消息检查者
   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:     // 用于检查消息的函数
  13:     virtual bool    isOk( const Message& message ) const;
  14:
  15: public:
  16:     // 用于检查消息的数据
  17:     MessageId        id;
  18:     MessageWparam    wparam;
  19:     MessageLparam    lparam;
  20: };

  其中 MessageChecker::isOk 很明显应该可以被后继者重写以实现不同的检查方式,所以它应该是虚函数,这样后继者可以这样的形式扩充检查者队伍:

   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 这些和谐的名字,感觉消息检查者就这样实现完成了,我们的 GUI 框架似乎已经成功迈出了重要的第一步。但是面对函数 isOk ,有经验的程序员肯定会有疑问:真的这样简单就 ok 了?当然是 no。要是真有那么简单,我何苦还在后面写那么长的篇符呢,cppblog 又不能多写字骗稿费的。

2.2 堆上生成 & 对象切割

  看着虚函数 isOk 会想联到两个关键词:多态,指针。多态意味着要保存为指针,指针意味着要在堆上生成。消息检查者保存为指针的映射像下面这样子(假设消息处理者叫做 MessageHandler ):

   1: // 允许一个消息对应多个处理者
   2: typedef vector<MessageHandler>                _HandlerVector;
   3:
   4: // 为了多态必须保存 MessageChecker 的指针
   5: typedef pair<MessageChecker*,_HandlerVector>   _HandlerPair;
   6:
   7: // 消息映射
   8: typedef vector<_HandlerPair>                 _HandlerMap;

  堆上生成是万恶之源。谁来负责销毁?何时销毁?效率问题怎么办?有的人此时可能想到了引用计数,小对象分配技术,内存池。。。只是一个消息检查的动作就用那么昂贵的实现,就像花两万块买张鼠标垫一样让人难以接受。所以我们想保存消息映射者的对象 MessageChecker 而不是它的指针,就像这个样子:

   1: // 允许一个消息对应多个处理者
   2: typedef vector<MessageHandler>                _HandlerVector;
   3:
   4: // 保存 MessageChecker 对象而不是指针
   5: typedef pair<MessageChecker,_HandlerVector>   _HandlerPair;
   6:
   7: // 消息映射
   8: typedef vector<_HandlerPair>                 _HandlerMap;

  但这样的保存方式带来了一个新的问题:对象切割。如果往映射中放入派生类的对象比如 CommandChecker 或者 NotifyChecker,编译器会铁手无情地对它们进行切割,切割得它们体无完肤摇摇欲坠,只剩下 MessageChecker 子对象为止 。砍头不要紧,只要主义真,但是要是切割过程中切掉了虚函数表这个命根子就完蛋了,在手执电锯的编译器面前玩耍虚函数,很难说会发生什么可怕的事情。

  有一种解决方案是我们不使用真正的虚函数,而是自己模拟虚函数的功能。具体办法是在 MessageChecker 当中保存一个函数指针,由子类去把它指向自己实现的函数,非虚的 MessageChecker::isOk 函数去调用这个指针。修改 MessageChecker 的定义:

   1: // 领衔的消息检查者,用函数指针模拟虚函数
   2: class MessageChecker
   3: {
   4:     // 模拟虚函数的函数指针类型
   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:     // 非虚函数调用函数指针
  17:     bool isOk( const Message& message ) const
  18:     {
  19:         return m_visok( this,message );
  20:     }
  21:
  22: protected:
  23:     // 不骗你,我真的是虚函数
  24:     static bool virtualIsOk( const MessageChecker* pthis,const Message& message )
  25:     {
  26:         return pthis->id == message.id;
  27:     }
  28:
  29: public:
  30:     // 用于检查消息的数据
  31:     MessageId        id;
  32:     MessageWparam    wparam;
  33:     MessageLparam    lparam;
  34:
  35: protected:
  36:     // 模拟虚函数的函数指针
  37:     _VirtualIsOk    m_visok;
  38: };

  如代码所示的,MessageChecker::virtualIsOk 的默认实现是只检查消息id,这也是 MFC 的映射宏 MESSAGE_HANDLER 干的事情。现在解决了不要堆生成与要求多态的矛盾关系,真正完成了消息检查者的定义。

2.3 扩充队伍

  要扩展消息检查者队伍,可以从 MessageChecker 派生新类,定义自己的检查函数(virtualIsOk 或者其它名字都可以)并将 MessageChecker::m_visok 指向这个函数。要注意的是因为存在对象切割问题,所以派生类不应该定义新的数据成员,毕竟切掉花花草草也是非常不好的。举例说明派生方法。

  比如从消息检查者派生一个命令消息(Command)的检查者 CommandChecker:它定义了一个函数 virtualIsOk ,此函数检查消息id是否为 WM_COMMAND ,并进一步检查控件id和命令code,然后将指针 Message::m_visok 指向这个函数 CommandChecker::virualIsOk,这样 MessageChecker::isOk 实际上就是调用的 CommandChecker::virtualIsOk 了。CommandChecker 的功能类似于 MFC 的宏 COMMAND_HANDLER:

   1: // 命令消息检查者
   2: class CommandChecker:public MessageChecker
   3: {
   4: public:
   5:     // 将函数指针指向自己的函数 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:     // 将函数指针指向自己的函数 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: };

  发挥想像进行扩展,这个消息检查者可以做很多事情。比如定义一个范围id内命令消息的检查者 RangeIdCommandChecker,其功能类似于 MFC 的 ON_COMMAND_RANGE 宏:

   1: // 范围 id 命令消息检查者
   2: class RangeIdCommandChecker:public MessageChecker
   3: {
   4: public:
   5:     // 将函数指针指向自己的函数 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:     // 将函数指针指向自己的函数 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 数据不够用

  这一节是新加的。因为有人提出数据容纳不下的问题:MessageChecker 目前的定义只能容纳 WPAWAM,LPARAM 两个参数大小的数据,而因为在存在对象切割问题,子类又不能定义新的数据,那如果 WPARAM,LPARAM 装不下需要的数据了怎么办?难道就只能把数据在子类当中定义,然后每次都在堆上生成 MessageChecker 吗?

  我在这里的解决方案是,在 MessageChecker 里面定义一个多态的数据成员,这个成员大多数时候是空的,当需要的时候从堆上生成它,用它来装下数据。如代码所示:

  先定义一个多态的数据类 MessageData:

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

  在 MessageChecker 当中加入这个数据成员:

   1: // 加入多态的数据成员
   2: class MessageChecker
   3: {
   4: //......
   5: public:
   6:     MessageData* data;
   7: }
   8:  
   9: // 拷贝构造时同时深拷贝数据
  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: // 拷贝时同时深拷贝数据
  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: // 析构时删除
  43: MessageChecker::~MessageChecker()
  44: {
  45:     if ( data )
  46:     {
  47:         data->release();
  48:         data = NULL;
  49:     }
  50: }

  举例说明怎么样使用 data 成员。例如要在一个消息检查者需要比较字符串,则在其中必须要保存供比较的字符串。先从 MessageData 派生一个保存字符串的数据类 MessageString:

   1: // 装字符串多态数据类
   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: };

  为了使用方便可以定义一个数据类的模板:

   1: // 数据类的模板
   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: };

  然后可以将字符串数据类的定义简化为:

   1: // 利用模板生成数据类
   2: typedef MessageDataT<String>    MessageString;

2.5 龙套演员

  接下来可以进行消息检查者的测试了。但因为消息检查者的工作牵到其它两个角色,所以测试之前必须要先简单模拟出这两个龙套角色:消息处理者和消息监听者。

  消息处理者(MessageHandler )的作用顾名思义不用多作解释,它在 GUI 框架当中的作用十分重要,实现起来也最复杂,但在这本文中它不是主角,所以可以先随便拉个路人甲来跑跑龙套,路人甲长得像这个样子:

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

  消息监听者(MessageListener )保存有消息检查者与消息处理者的映射,并提供接口操作这些映射,当监听到消息的时候,消息监听者调用映射中的检查者进行检查,如果通过检查则调用消息处理者来进行处理。消息监听者干的都是添加/删除/查找这样的体力活,看似实现起来用不着大脑,可是当涉及到真正的消息处理时情况会变得复杂,会遇到消息重入之类的问题。所以真正的实现留待日后再说,这里也先给出一个简单的模拟定义含混过去:

   1: // 消息监听者
   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: };

  其中调用消息检查者的是关键函数是 MessageListener::onMessage( const Message& mesage ) ,实现很简单,查找出消息处理者列表然后逐个调用其中处理者:

   1: // 调用检查者检查,通过则调用处理者处理
   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 进行测试

  龙套都已就位,导演喊:"action!",现在可以写点测试代码测试一下了:

   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 收场的话

  这第一锤终于砸完了,石头一裂为二,胸口完好无损。其实砸的时候心想,这一锤的分量砸下去不轰动神州也要震惊天府吧。但是回头看看上面所有的文字,觉得这个东西怎么这么简单,甚至连模板参数都没有用到一个,更没有谈到效率,优化什么的,肯定是不足以诱惑技术流的 cpper 们的。

  想起自己曾经写过的几个消息框架,可以算是把 C++ 的编译期技术发挥得淋漓尽致了,但是出来的东西却并不理想,后来慢慢领悟到一个道理:高尖的技术虽然炫酷,并不是处处都合适用。我的版本的消息检查者就止于这个程度了,肯定有比这个更好的实现,希望走过路过的高手们不要吝啬自己的好想法,提出来与广大酱油众分享。

  消息检查总算写完了。没选上好季节,电脑前坐了大半天手脚都冰凉的。上床睡觉去了,养足精神希望能看到新一轮的神仙打架。文章涉及的所有代码项目下载:MessageChecker_200911251055.rar

Feedback

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 05:14 by OwnWaterloo
貌似沙发又得被我占领……
作为一个对oo不屑一顾的c(pp)er,看到这样的代码相当不适……




比如MessageChecker和它的子类……
不适啊……

1. 切割与多态
从msvc和gcc的实现来看,虚指针是切割不掉的,它在父类中。
虚表就更不存在切割一说……
不能多态的原因不是因为切割,而是因为 —— 没有使用引用或指针类型进行调用……
所以啊,对这种情况 …… 可以恶心点……
按值保存(会被切割),同时对每个值也保存一个对应的指针,指向该值。
通过指针而非值进行调用,诱使编译器使用虚指针。

为什么感到恶心呢……
是因为这样可以多态调用,但不能保证子类的不变式……
调用的是子类的函数,使用的是父类的数据(被切割了)……


2. class vs struct
当然,对这个问题蛮适合的。
因为楼主的方案在子类中同样不能塞新的数据,否则一样会被切割。
然后,调用那个函数指针时,就会访问到无效内容。


为什么感到不适呢……
上面说了,只要是按值存储,子类就不可能添加数据成员 —— 否则就会被切割。
也就是说,无论怎样继承,数据成员就那么几个……
这里需要的其实不是class, 而是struct ……
强行使用class, 会使得需要定制的时候,就需要定义一个类 —— 其实仅仅需要定制一个函数就可以了。

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更多参数的checker又该怎么办呢……


for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it)
这个效率是很低的……
_HandlerMapIter的命名也是不符合c/c++规范的…… 都被Gof带坏了……

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 12:33 by cexer
@OwnWaterloo
【强行使用class, 会使得需要定制的时候,就需要定义一个类 —— 其实仅仅需要定制一个函数就可以了。】
这样的设计也是跟框架的整体设计有关系,以后写了消息映射者你再看看。倒是没考虑过使用函数来实现消息检查者,我更喜欢它有对象的感觉一点,OOP语言嘛,而且类是肯定比函数有大得多的灵活性的。

【有些不需要id,wp,lp的checker该怎么办呢……需要比id,wp,lp更多参数的checker又该怎么办呢……】
之所以不需要再需要新的成员数据,是因为消息本身就只有那几个数据。如果需要检查更复杂的情况,比如说字符串,这样的模式也是可以胜任的,以前实现过。在 MessageChecker 当中加一个多态的 Data 成员,它的多态复杂度要比 MessageChecker 本身在堆上分配的小得多,目前没遇到id,wparam,lparam 三个参数解决不了问题的情况,暂时不会增加这个 Data 成员。

【从msvc和gcc的实现来看,虚指针是切割不掉的,它在父类中。虚表就更不存在切割一说……】
这样走旁门左道是因为以前确实遇到过虚函数失效的问题,应该不会真是我忘了用指针调用了吧?更可能是编译器bug,因为指针调用虚函数从小就记得。VC71对于C++标准的支持不是很理想,遇到过很多编译器报“cl.exe 内部错误”,特别是涉及模板的时候。

【是因为这样可以多态调用,但不能保证子类的不变式……调用的是子类的函数,使用的是父类的数据(被切割了)……】
什么不变式要变式哦,听起来好讨厌,能抓猫的就是牛老鼠。写出来的程序编译通过链接成功,客户能运行它的时候觉得心里爽就行了,所有的规则应该是为这个最终的目的服务的,而不应该为规则而规则。

【“for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it)这个效率是很低的……HandlerMapIter的命名也是不符合c/c++规范的…… 都被Gof带坏了……】
我倒是并不觉得它的效率很低,因为只是十多次的循环,不过也希望看看你有更有效率的方法。命令风格是被翻炒了千亿次的问题了,使程序员从编码到设计都能保持最大的个性,我觉得正是C++的一大特色。就算有人腰上围一圈炸弹手执打火机来逼我改风格,我依然视死如归地觉得自己的命名方式美妙绝伦无以伦比的,坚持风格得有血可流有头可断发型不能乱的勇气啊。所以你就将就着看了。

“规则,风格,效率”,OwnWaterloo兄弟啊,你就是典型的传说中的学院派 cpper 。很高兴又来占我沙发,同时很是抱歉,这篇博文可能让你失望了。但我会再接再励,希望后续的文章能引起你的兴趣,不负你两占沙发的厚望。

# re: GUI框架:消息检查者[未登录]  回复  更多评论   

2009-11-22 12:57 by Loaden
下午还有课!虽然是星期天!!
唉。
这么好的文章,竟然只能晚上才能拜读,遗憾啊!
多谢cexer分享,期待下文...

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 13:06 by cexer
@Loaden
谢谢老邓!也希望你的框架再进化!

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 13:53 by 空明流转
对GUI我早就烦了。。。
归根结底,GUI是个适合于自动生成的玩意儿。
实在不行,还是学Qt吧,MOC解决所有问题。。。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 14:04 by OwnWaterloo
@cexer
刚醒…… 一边吃饭一边先把不涉及风格的东西解释一下……

不说不变式了。我也觉得这词太学究,所以在后面补了一句:
【调用的是子类的函数,使用的是父类的数据(被切割了)……】

而且,我说错了……
编译器会在复制(以及切割)的同时修正虚指针 —— 忘了……
要memcpy( &b, &d, sizeof(b) ); 才行……


关于【for (_HandlerMapIter it = m_map.begin(); it!=m_map.end(); ++it)】
我指的不是for each。而是遍历。
不明白为什么只需要遍历,却用了一个map。
然后…… 我再看了看这行代码之上的代码,这是一个vector……
我又看错了…… 代码看得不仔细……



关于coding-style。其实我是最不讲究这个的了……
但有些东西不属于coding-style,而是属于底线 —— 使用c/c++语言必须遵守的 —— 不能使用语言保留的标识符。
语言保留的标识符还分了几种, 统一起来就是以下划线开始的程序员全都不能使用。
这在很多书里面都有说,又在很多书(比如Gof,不知道算不算经典)中犯错。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 14:16 by 个性艺术签名
黄金时代何健飞道护肤

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 14:38 by OwnWaterloo
@cexer
说说我对gui框架的需求吧……
以用户的身份…… 用户的需求总得听听吧……


从这里说起:
【目前没遇到id,wparam,lparam 三个参数解决不了问题的情况】
我编写gui的经验不多。所以对"这3个参数是否能解决所有问题"是一点概念都没有。

不过我想了一个例子,比如我要在xxx-yyy时间段内,检查这个消息,返回真,这个时间段外,就返回假。
这个checker就做不到了。 肯定要放到其他地方做,比如hanlder中。


对,这就是我对"框架"感到反感的地方之一 —— 它"限制"你做事的方法。
它会将可能本来是一件完整的工作,拆散,然后分散到框架的一些地方去。

为了完成这个工作,你要按框架所拆分的那样 —— 需要顺着框架的思路,而非我自己的思路 ——只需要id,wp,lp就能check的,放到checker中。其他即使也是check,也得放到handler中。
如果框架拆分得恰当,就很好,比如 CommandChecker等。
如果框架拆分得不恰当,就很糟……
所以我对mfc一点好感都没有。


作为一个了解且不排斥win32api的用户(当然,gui框架大多着眼的都不是这种用户囧……),我需要的仅仅是弥补一下WndProc,使得它可以找到this —— 这个工作几乎是必须做的,即使是用C编写gui的家伙。
然后,就可以从hwnd,msg,wp,lp开始干活了 —— 不需要再学任何关于框架的知识,比如要将check放在哪,hanlder放在哪。
我希望框架提供一个,在window上add(挂)多个listener的机制就差不多了…… 如何分配checker和handler还有cracker的工作,还是将它们揉在一起,用户可以自行决定。

所以我对lambda表达式特别渴望……
比如上面的代码,对应的伪代码可能是这样:

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


然后,在每天编程闲暇的时候,发现库(而非框架)里面有command_check,我就可以这样写代码了:
w.add_listener( void (const msg&m) { if (is_command(m,id) cout<<command_crack(m).id<<endl; } );

如果我没发现command_check,我也可以开始我的工作,只是代码更繁琐一些。

这就是库和框架的一个很大的区别。
你可以使用熟悉的方式工作,然后慢慢熟悉这个库,慢慢用它干善自己的工作,并提高效率。
而框架,不对它熟悉到一定程度,就没法工作……



作为程序员……
我也了解用户的需求是很恶心的…… 就像你有一篇blog里写的那样……
所以,推己及人……
对我提出的这个恶心的需求,听过之后就当是废话好了~_~

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 14:48 by cexer
@OwnWaterloo
【刚醒…… 一边吃饭一边先把不涉及风格的东西解释一下……】
你们那里没有出太阳?这么大好时光不要拿来睡觉浪费了。回复完这个,我就出去晒太阳了。

【调用的是子类的函数,使用的是父类的数据(被切割了)……】
函数指针和虚函数的唯一区别就是函数指针少个读表的动作。所以如果说这样破坏了“不变式”,那么虚函数也是一样的。使用子类函数读父类数据,这种手法是经常被用到各种模式中的,这里也不存在切割问题,因为已经没有什么可以切割的。

【语言保留的标识符还分了几种, 统一起来就是以下划线开始的程序员全都不能使用。】
这只专家们一个很学究气的建议,跟“谨慎使用内联函数”一样的警示级别。因为标准库的作者确实使用了一些下划线的变量,boost 也有很多下划线开始的东西,像占位符 _1,_2,..._n。为了避免名字冲突确实少用为妙,但用了也不应该看作错误。

# re: GUI框架:消息检查者  回复  更多评论   

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_是不合法的,不能以数字开头。
改为其他,不太简洁……



也不能算完全的学究。它们不会永远被保留。
C99已经开始动用这些保留的标识符了。
_LongLong, _Complex,_Bool等。
C++也会跟进。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 15:06 by OwnWaterloo
将_HandlerMapIter改为HandlerMapIter或者HandlerMapIter_真的没有坏处。
本来就是一个private,库用户不可见的名字。
属于Handler,也不会和其他重名。
还避免了(可能微乎其微)与_HandlerMapIter宏冲突。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 20:07 by cexer
@OwnWaterloo
【boost有自己的苦衷。它需要一个简短的东西来做占位符。】
你误会我的意思了。我是同意你的,我的意思是确实应该避免使用,因为像 boost,stl 之类的库很多地使用了这样的下划线。

【将_HandlerMapIter改为HandlerMapIter或者HandlerMapIter_真的没有坏处。
本来就是一个private,库用户不可见的名字。属于Handler,也不会和其他重名。还避免了(可能微乎其微)与_HandlerMapIter宏冲突。】
嗯,明白你的意思。其实这是我的习惯,外部不可见的都加个下划线在前头,没考虑到与标准库发生冲突的情况,你想的更细致一点。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 21:05 by cexer
【不过我想了一个例子,比如我要在xxx-yyy时间段内,检查这个消息,返回真,这个时间段外,就返回假。
这个checker就做不到了。 肯定要放到其他地方做,比如hanlder中。】
是可以的啊,可以像下面这样实现。
    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;
        }
    }

可能你的意思是遇到消息参数装不下检查时需要的信息的情况应该怎么办,这里举例说明一下怎么实现。比如说要检查是否某个字符串,这样的实现可以装下任何需要的信息。
先定义一个多态的数据类型,为了效率这里可以使用引用计数之类的东西
    class MessageData
    {
    public:
        virtual ~MessageData()
        {}
        virtual void release()
        {
            delete this;
        }
        virtual MessageData* clone() const = 0;
    };

MessageChecker 当中有这个成员
    class MessageChecker
    {
    //......
    public:
        MessageData* data;
    }

修改拷贝构造函数和析构函数
    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;
        }
    }

定义一个装字符串的多态数据类
    class StringMessageData:public MessageData
    {
    public:
        StringMessageData( const String& str_ )
            :string( str_ )
        {}
        virtual MessageData* clone() const
        {
            return new StringMessageData(string);
        }
    public:
        String  string;
    };

利用数据成员 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 框架用户的需求。要说明一下的是,实现的这个消息检查者只是在框架内工作的,方便 GUI 框架的开发者和扩充者使用,GUI 框架的使用者不会接触到这个东西。最终的用户只需要使用是像这个样子:
    window.onCreated += messageHandler( &::_handleCreated );
    window.onCreated += messageHandler ( this,&Window::_handleCreated );

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-22 21:21 by cexer
cppblg 这烂程序,吃空格太严重了。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 02:21 by OwnWaterloo
@cexer
确实…… cppblog的评论做得不咋嘀……
所以代码我也没仔细看…… 意思差不多明白了。
WndProc可以用这些参数表达任意多的参数,所以我们也可以……
对不需要多余参数的,可以直接使用这几个。
对需要的,再引入多态与自由存储的开销,并将其包裹在一个值语意的类型中。
是这样吗?

这思路不错。 预留几个常用的参数,免得需要参数就要动用动态内存……



说实话我一直对“框架”和“类库”的概念分不大清楚,我想“框架”从名字上来说多了一个“可以扩展,可以填充”的意思,没有你说的“限制你做事的方法”这种感觉。

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

说得好听点,叫扩展了框架;说得不好听,叫顺了框架的意;再难听点,就是被框架qj了……


以你这篇文章里提到框架来说吧:
1个listener 有 1个m_map
1个m_map 有若干个HanlderPair
1个HandlerPair 包含一个 Checker和HandlerVector
1个HandlerVector 包含若干个Handler

这就是一种限制。MessageChecker也算。

设:从源头WndProc到实现某个功能所需要做的全部事情为S。
【必须按这个框架制定的规则,将S拆分为S1、S2等等,然后安排到框架中的预定位置。】
比如将事情分为监听、检查、映射、处理等工作。

这就是我说的限制的意思。【框架规定了编程的模型】。

假设,有一项需求框架没有提供,比如Checker需要更多的参数。
如果还想继续使用这个框架,就必须设计一个MessageData。
假设MessageData用户而非框架提供的,是为【扩展】了框架。
假设MessageData是框架自身提供的,但如果它又有另一个需求没有满足呢? 它必须提供一些【可扩展点】让用户【大部分按框架的思路编程】,并在需要的时候,扩展一下框架。否则用户就会放弃这个框架了。

如果框架的规定在大部分时候合理,框架就是合理的。
这个尺度很难把握……

总之,用框架编程,框架是主体。或者说底层的实现不用用户操心,只需要注重业务逻辑。
用类库编程,程序员自己是主体。




倒是没考虑过使用函数来实现消息检查者,我更喜欢它有对象的感觉一点,OOP语言嘛,而且类是肯定比函数有大得多的灵活性的。

这个,其实也是反的……
OO是思想,class只是它的一种实现方式。也许使用会比较方便。
但灵活性是不如函数+数据的。

1.
比如C++、java、C#都没有提供多重分派(multi-method)。
所以,很明显的,o.method(...); 只是method( o , ... );在1个对象下的简便形式。
如果需要method( o1, o2, ... ) —— 根据o1和o2的类型来决定做什么事。这3门语言是不提供支持的,也必须用函数来表达。
我知道有个xxx模式…… 但3重分派它又解决不了了…… 治标不治本。

当然,某种支持多分派的语言可以继续提供 o1&o2.method( ... ); 的语法……

2. OO将算法埋葬到自己的类里。
如果这个算法很通用,更好的是将它实现为一个函数,而不是委身到一个class中。
呃,这种情况在gui中可能不多见。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 02:45 by OwnWaterloo
想起一个说明限制的更通俗的例子:std::for_each。
它就算一种微型框架。
它完成了遍历的步骤,同时限制了遍历的方法—— 用一元函数。

使用for_each时
1. 直接存在现有的一元函数:
vector<void*> m; // memory blocks
for_each(m.begin(), m.end(), free );

2. 可以通过for_each配套提供的一些设施完成工作:
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,然后继续使用std::for_each,语法丑陋得,我都不知道怎么写…… 所以这里就不列了…… toplanguage上可以找到不少……

4. 当这些都失效时……
可以说,for_each这个框架在大多数时候都是鸡肋。
让人不得不绕过它的限制(传递一元函数),直接写for循环。


而boost.lambda和C++0x的lambda使得for_each变得实用了不少。所以框架是否实用,很难把握。
说for_each是框架嘛…… 主要是因为它的限制。
但是它规模有点小……也可以很轻易的被丢掉。反正是函数模板,不使用就不会被实例化。这又有类库的性质。
算是一个不太恰当的例子吧。



框架定义了轮廓线,由程序员去填充颜色。
类库提供调色板等工具,由程序员去绘制。


另外,传递给for_each的一元函数的调用语法是:
f( *first ); 而不是 ((*first).*f)();
也是一个自由函数比成员函数更普适与灵活的例子。

将成员函数以自由函数的语法进行调用:
bind(&c::f)( o, ... );

将自由函数以成员函数语法调用…… 好像要定义一个类……

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 10:17 by cexer
@OwnWaterloo
【框架定义了轮廓线,由程序员去填充颜色。类库提供调色板等工具,由程序员去绘制。】
这句话比喻得很好。你把框架理解成一种束缚是觉得需要按照它规定的方式去使用,但类库甚至 API 何尝不是也有自己的规则。我理解的框架和类库其基本目的都是一样的:对繁琐的细节进行封装,提供更为便利的接口。这当中肯定会损失灵活性,毕竟鱼和熊掌很难兼得。但我觉得框架其实是类库的进化后的身份,从字面意义上来讲,“框架”看起来更有弹性,扩展的意思。但实际上大家对这两个词的概念并没有很明白的分辨,比如说到 MFC,有人说框架有人说类库大家的感觉都是一样的。

【这个,其实也是反的……OO是思想,class只是它的一种实现方式。也许使用会比较方便。但灵活性是不如函数+数据的。】
我的想法相反。你说类的灵活性不如函数加数据,但类难道不正是建立在函数和数据之上的一个超强结合体?之所以用C之类的 OP 语言实现模式不如C++这样的 OO 语言容易,一大原因正是它缺少类的支持。

【message_checker ButtonClickingChecker(WORD id);
message_checker xxxChecker( ... );】
这是你举例说明的用函数来实现检查者。你可以尝试真的用函数来实现消息检查者,这个 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函数内部你各自要怎么实现?它既要包含不同的数据,又要包含不同的操作,它们返回完全相同的 message_checker 对象,这个 message_checker 又要怎么实现才能以一已之身完成众多不同的功能?由于不同参数列表的函数实际上是完全不同的东西,你甚至不能以统一的方式保存它们管理它们。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 16:05 by 陈梓瀚(vczh)
继续搅局:你凭什么认为MessageChecker父类的成员变量一定够用呢?如果够用的话,就证明你的逻辑已经是固定的了,为什么要子类?就一个MessageChecker好了,不用继承。你矛盾了。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 16:10 by 陈梓瀚(vczh)
@cexer
你说类的灵活性不如函数加数据,但类难道不正是建立在函数和数据之上的一个超强结合体?

类的灵活性在于,你使用shared_ptr<Base>保存了一个Derived*,然后调用虚函数。代价非常低,我经常把智能指针放进容器,也可以不管谁去释放,总之会被释放。而且我这种写compiler的人对循环引用十分敏感所以我基本不会犯这种错误……

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 16:12 by 陈梓瀚(vczh)
@cexer
【这是你举例说明的用函数来实现检查者。你可以尝试真的用函数来实现消息检查者,这个 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函数内部你各自要怎么实现?它既要包含不同的数据,又要包含不同的操作,它们返回完全相同的 message_checker 对象,这个 message_checker 又要怎么实现才能以一已之身完成众多不同的功能?由于不同参数列表的函数实际上是完全不同的东西,你甚至不能以统一的方式保存它们管理它们。】

TR1有functor给你用,解决了这些问题。无论是函数指针,成员函数指针,有operator()的类,统统都可以放进去。所以我觉得你的listener应该是
vector<function<bool(Message&)>>,然后处理了返回true,没处理返回false。function自己可以有自己的成员,你可以把有operator()的对象放进去,把成员函数放进去什么的,统统都可以。要坚定不移地使用这些东西。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 16:13 by 陈梓瀚(vczh)
@cexer
而且,千万不要去考虑标准库里面什么类的性能如何的问题,你永远假定他们是用魔法完成的,不需要任何CPU周期。不然你一行代码都写不出来。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 16:14 by 陈梓瀚(vczh)
@cexer
至于什么是框架什么是类库,有一个很容易的判断标准:template method就是框架。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 16:15 by 陈梓瀚(vczh)
@cexer
所以框架和类库可以互相包含,这也是很常见的。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 16:57 by cexer
@陈梓瀚(vczh)
【继续搅局:你凭什么认为MessageChecker父类的成员变量一定够用呢?如果够用的话,就证明你的逻辑已经是固定的了,为什么要子类?就一个MessageChecker好了,不用继承。你矛盾了。】
欢迎搅局!你提出一问题确实有“以子之矛攻子之盾”的杀伤力,不过幸好我不是既卖矛又卖盾的。关于数据不够用的情况 OwnWaterloo 的也提出过,你可以倒着往上看给 OwnWaterloo 的回复,在 MessageChecker 当中加一个多态的 Data 成员可以放下所有东西,我还是把这东西更新到博文里去吧免得又有人问。“逻辑”的关键显然不是数据,而是那个检查的动作。就像银行存了五千万,不拿出去挥霍也住不上豪宅一样,成员变量够用成员函数不够用也是白搭,必须得要改写。要想一个类,不继承,不改写,而要满足不断出现的需求,这肯定是不能完成的任务。“数据够用”和“函数改写”并矛盾。

【类的灵活性在于,你使用shared_ptr<Base>保存了一个Derived*,然后调用虚函数。代价非常低,我经常把智能指针放进容器,也可以不管谁去释放,总之会被释放。而且我这种写compiler的人对循环引用十分敏感所以我基本不会犯这种错误……】
你是建议我把 MessageChecker 在堆上生成用智能指针管理起来吧。我也觉得这样确实可以使用真正强大的虚函数,但这样要付出每次都在堆上构造的效率成本和管理上的复杂性,而实际上大多数情况下这样的付出是不必的:在 Windows 的消息机制下,消息检查大多数时候需要的数据是很少的,用 WPARAM 和 LPARAM 就可以装下,但确实有需要在堆上保存数据的情况,所以我在 MessgaeChecker 增加了一个多态的 Data 成员来保存,它只有极少时候的才会从堆上生成,这样尽量避免了堆上生成的复杂性和效率损失,而完成的功能又丝毫不减。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 17:03 by 陈梓瀚(vczh)
@cexer
使用tr1::function<bool(Message&)>,毫无管理复杂性,几乎没有效率成本。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 17:04 by 陈梓瀚(vczh)
@陈梓瀚(vczh)
但是实际上windows的消息很乱,事件跟消息并不是一一对上号的。因此MessageChecker之类的东西最终用户是看不到的,你要根据每一个控件的具体情况,重新整理出一系列事件,才能让用的时候真正爽快起来。这里没有技术问题。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 17:31 by cexer
@陈梓瀚(vczh)
【要坚定不移地使用这些东西。 使用tr1::function<bool(Message&)>,毫无管理复杂性,几乎没有效率成本。】
把 MessageChecker::isOk(const Message&) 的实现放到 bool MessageChecker::operator()(const Message&) 中去,函数体MessageChecker 就变成了和 function 类似的东西,但这样又有什么本质区别呢。tr1::function 的功能虽然强大,用它去实现消息检查者,该写的逻辑还得写,省不了功夫,扩展性也是个问题。

【至于什么是框架什么是类库,有一个很容易的判断标准:template method就是框架。】
谢谢,很多书对这个也言之不详,只是叫着爽就行了,所以我自己也就不大明白。我从此以后照你这个标准去评判好了。

【而且,千万不要去考虑标准库里面什么类的性能如何的问题,你永远假定他们是用魔法完成的,不需要任何CPU周期。不然你一行代码都写不出来。】
我是从不考虑那些的:标准库的效率再差,也不会到我放弃使用它们的地步的。另外以我的水平写出来的东西绝对比它的慢,哪有资格嫌弃人家。

【但是实际上windows的消息很乱,事件跟消息并不是一一对上号的。因此MessageChecker之类的东西最终用户是看不到的,你要根据每一个控件的具体情况,重新整理出一系列事件,才能让用的时候真正爽快起来。这里没有技术问题。】
你说得对,框架用户是看不到 MessageChecker 的,他们看到的是 onCreated,onClosed,onClicked 之类的东西。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 18:25 by 陈梓瀚(vczh)
@cexer
function的好处就是你可以使用子类的同时不用管delete啊

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-23 20:09 by cexer
@陈梓瀚(vczh)
【function的好处就是你可以使用子类的同时不用管delete啊】
想了下用 tr1::function 来实现确实要比手写类简单得多,函数和参数绑定功能在这里很有用处,有多少参数都不用搞个多态的 Data 了,也不用自己去 delete 。确实很强大。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-24 00:52 by OwnWaterloo
@cexer
【我的想法相反。你说类的灵活性不如函数加数据,但类难道不正是建立在函数和数据之上的一个超强结合体?之所以用C之类的 OP 语言实现模式不如C++这样的 OO 语言容易,一大原因正是它缺少类的支持。】

是的,类是函数和数据的结合,还加上数据隐藏。
超强到谈不上……
我也说了的,对单个object,这3门语言的OO实现确实是方便。

灵活与范化的东西,通常比较难用和不方便。



这是你举例说明的用函数来实现检查者。你可以尝试真的用函数来实现消息检查者,这个 ButtonClickingChecker(WORD id) , xxxChecker( ... ) 函数内部你各自要怎么实现?它既要包含不同的数据,又要包含不同的操作,它们返回完全相同的 message_checker 对象,这个 message_checker 又要怎么实现才能以一已之身完成众多不同的功能?由于不同参数列表的函数实际上是完全不同的东西,你甚至不能以统一的方式保存它们管理它们。

你那个MessageChecker怎么完成以一己之身完成,众多不同功能的?
同样可以应用到message_checker 上,两者是相通的,都是靠结构体中嵌的一个函数指针。


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

关于那个id,wp,lp,我觉得还不错。

Windows确实需要将系统与用户之间使用【一条统一的渠道【来通信。
这条渠道演化到【极端】就是这种形式:
void* (*)(void* all_parameter);

但如果所有消息都需要使用动态内存,就有点浪费。
添加一些其他比较常用的值参数 vs 和一个值参数都不用,就是一种折衷。
完全不用,肯定每次都需要动态内存分配。
值参数多了,又可能在一些情况用不上。

所以,Windows最后选择了
LRESULT (CALLBACK*)(HWND, UINT, WPARAM, LPARAM);
参数少,直接传递,参数多,将某个参数理解为指针……
(此处纯猜测……)


所以,迎合WndProc的设计还不错。


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

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

很像boost.signal。


而且真正吸引人的是楼主说它是廉价工人~_~


boost.function如果不用支持bind,可能可以不动用动态存储。
要支持bind…… 而且不使用动态存储…… 好像不行……
boost.signal肯定是要动用动态存储的。


等着楼主这部分的实现了~_~

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-24 10:38 by cexer
【你那个MessageChecker怎么完成以一己之身完成,众多不同功能的?
同样可以应用到message_checker 上,两者是相通的,都是靠结构体中嵌的一个函数指针。】
我的意思是函数本身不容易实现,只用函数主要有数据保存的问题。加上 function 来实现就容易了,直接绑定一些检查函数和检查的数据:
    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 是帮助消息映射的一个东西,其实就是一个转发调用,所以成本很低。messageHandler() 是消息处理者和消息分解者共同组成的东西。在这里 ::_handleCreated 和 Window::_handleCreated 参数列表可以是不同的,这里和 boost.signal 不大一样,因为一个 signal 只能对应一种 signautre 的 functor 的。

【boost.function如果不用支持bind,可能可以不动用动态存储。
要支持bind…… 而且不使用动态存储…… 好像不行……
boost.signal肯定是要动用动态存储的。】
嗯。主要是管理起来很容易了,任何的参数直接绑定进去就行了,不用自己弄个堆对象来保存,然后还要记得删除。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-24 16:47 by 陈梓瀚(vczh)
@cexer
所以说嘛,用标准库的时候要假设他们是0CPU消耗,这样你就不会想太多多余的事情了。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-26 14:59 by lch
GUI 框架固然重要,
但漂亮的控件,和所见即所得的GUI设计器更加重要

Mac OS, IPhone, Andriod都是如此

# re: GUI框架:消息检查者  回复  更多评论   

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

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-27 00:10 by lch
我的意思是原生的框架已经够用的情况下,控件的成熟度显得更重要。

# re: GUI框架:消息检查者  回复  更多评论   

2009-11-30 10:54 by 侠客西风
高手讨论吧,

新手路过,学习中...

以后水平高了再和你们讨论...

又见 OwnWaterloo 兄,
还认识了cexer,陈梓瀚(vczh) 高手...

# re: GUI框架:消息检查者  回复  更多评论   

2009-12-01 10:10 by cexer
@lch
这个我同意。不过到后来的丰富控件,已经基本上是体力活了。

# re: GUI框架:消息检查者  回复  更多评论   

2009-12-19 16:51 by Goteet
我想问个问题,假如消息接受者在收到消失的时候要把消息发送者删除怎么办

# re: GUI框架:消息检查者  回复  更多评论   

2010-01-05 12:30 by 赤脚流浪人
怎么加你好友啊,很喜欢你写的这些东西

# re: GUI框架:消息检查者[未登录]  回复  更多评论   

2011-05-22 21:35 by Neo
关于GUI的文章停了嘛,我在恭候呢。

# re: GUI框架:消息检查者  回复  更多评论   

2012-09-04 09:57 by Richard Wei
mark下

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理