MFC消息映射机制

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

由于工作需要,这几天学了一点MFC,在AFX里看到很多熟悉的东西,如类型信息,序列化,窗口封装和消息分派。几乎每个界面库都必须提供这些基础服务,但提供的手法却千差万别。MFC大量地借用了宏,映射表来实现,而VCL则更多的在语言级别上给与支持。这其实是很容易理解的,因为C++是一个标准,不会因某个应用而随便扩展语言;相反Delphi完全由一个公司掌握,因此每支持一项新技术,变化最大的往往是语言本身。

学习MFC的代码,再对照VCL的实现,这真是一个很有意思的过程,其中可以看到两个框架在一些设计思想上是殊途同归的,所不同的是表现手法,以及封装的程度。我计划将这段时间阅读MFC的心得写成一系列文章,其中可能会穿插与VCL的对比,不管你熟悉VCL还是MFC,通过这些文章或许可从另一个角度来看待自己熟悉的框架。

这是第一篇:消息分派。

消息处理函数表

MFC和VCL在对消息进行封装的时候,都没有使用虚函数机制。原因是虚函数带来了不必要的空间开销。那么它们用什么来代替虚函数,即可减少空间浪费,也可实现类似虚函数的多态呢?让我们从一个例子开始。

假设父类ParentWnd处理了100个消息,并且将这100个处理函数声明为虚函数;此时有一个子类ChildWnd它只需要处理2个消息,另外98个交由ParentWnd默认处理,但是ChildWnd的虚表仍然占有100个函数指针,其中2个指向自己的实现,另外98个指向父类的实现,情况大概像下面这样:

指向父类实现的函数指针浪费了空间,当控件类非常多的时候,这种浪费就非常明显。因此必须走另一条路,将不必要的函数指针去掉,如下图所示:

ChildWnd去掉函数指针之后,当有一个消息需要Fun100处理时,ChildWnd就束手无策了。需要一个方法让ChildWnd能够找到父类的表,我们再对这个数据结构进行改进如下:

现在看来好多了,如果ChildWnd有一个消息需要Fun1处理,则查找ChildWnd的MsgHandlers,找到Fun1函数指针调用之;如果需要Fun100处理,发现ChildWnd的MsgHandlers没有Fun100,则通过ParentTable找到父类的MsgHandlers继续查找。如此一直查找,到最后再找不到,就调用DefWindowProc作默认处理。

MFC和VCL都是通过类似的方法实现消息分派的。只是VCL有编译器的支持,直接将这个表放到VMT中,因此实现起来非常简单,只需在控件类里作如下声明:

procedure WMMButtonDown(var Message: TWMMButtonDown); message WM_MBUTTONDOWN; TObject.Dispatch会将WM_MBUTTONDOWN正确分派到WMMButtonDown。

MFC就没有这么简单,它需要手工去构建这个表,如果一个类想处理消息,它必须声明一些结构和函数,代码类似这样:struct AFX_MSGMAP_ENTRY

{

UINT nMessage; // 消息

AFX_PMSG pfn; // 消息处理函数

};

struct AFX_MSGMAP

{

const AFX_MSGMAP* pBaseMap; //指向基类的消息映射

const AFX_MSGMAP_ENTRY* lpEntries; //消息映射表

};

class CMFCTestView : public CView

{

protected:

void OnLButtonDown(UINT nFlags, CPoint point);

private:

static const AFX_MSGMAP_ENTRY _messageEntries[];

protected:

static const AFX_MSGMAP messageMap;

virtual const AFX_MSGMAP* GetMessageMap() const;

};

仔细看_messageEntries和messageMap的声明,是不是和上面的图非常相似,接下来看看实现部分如何初始化这个表:const AFX_MSGMAP* CMFCTestView::GetMessageMap() const

{ return & CMFCTestView::messageMap; }

const AFX_MSGMAP CMFCTestView::messageMap =

{ & CView::messageMap, & CMFCTestView::_messageEntries[0] };

const AFX_MSGMAP_ENTRY CMFCTestView::_messageEntries[] =

{

{ WM_LBUTTONDOWN, (AFX_PMSG)&OnLButtonDown }

{0, (AFX_PMSG)0 }

};

void CMFCTestView::OnLButtonDown(UINT nFlags, CPoint point)

{

CView::OnLButtonDown(nFlags, point);

}

messageMap的第一个成员指向其父类的messageMap,即&CView::messageMap;第二个成员则指向下面的消息映射表;

GetMessageMap是一个虚函数,显然是为了父类分派消息的时候能够找到正确的消息映射结构,后面会看到这一点。

_messageEntries数组为消息映射表,第一个元素处理WM_LBUTTONDOWN消息,其处理函数是OnLButtonDown,这个函数在CMFCTestView声明和实现;第二个元素标识了映射表的结尾。

现在,你想处理什么消息,都可以往_messageEntries里加新的元素并指定你的处理函数,只是如果每一个类都需要手工写这些代码,那将是很繁琐的事情;幸好C++有宏,可以用宏来作一些简化的代码,先将消息映射的声明用DECLARE_MESSAGE_MAP()表示,则类声明变成下面这样:

class CMFCTestView : public CView

{

DECLARE_MESSAGE_MAP()

相关文档
最新文档