[技术学习]浅谈MFC中超类化技术的实现 .

Panr 的 Blog // 关键词:
//  面对对象编程、超类化、子类化、Superclassing
//  MFC、CWnd::SubclassWindow
//  通用控件、CMNCTRL
//
// 主题:
//  通过CWnd::SubclassWindow 函数的分析,浅谈MFC中超类化技术的实现
//
//
// 背景
//  我在2002-12月见了mahongxi (烤鸡翅膀)(色摸)在CSDN上的一个帖
//  介绍了MFC中窗体的超类化的概念,以下是对我个人回贴的总结
//
// 日志
//  修改:Panr 2002-12-15 13:30 版式整理,转帖到CSDN文档中心
//  修改:Panr 2002-12-15 13:30 勘误
//  原作:Panr 2002-12-13 12:00
//
// 关于“文档中心”
//  在那个帖子里看到njtu_shiyl(玉晶)提到了文档中心,
//  我就一直在想文档中心在哪?
//  后来再回顾那个帖时,跟着翅膀兄就来到了这儿
//  所以这篇也就顺理成章是我的第一次
//  估计我是找对了地方...
//
//

一:超类化概述
在MFC中窗体实例对某个窗体句柄超类化后,系统提供了这样两种能力:
1.我们对该窗体实例调用成员函数将会直接改变相关窗体句柄对应的窗体
2.系统传给相关窗体句柄的消息会先经过该窗体实例的消息映射

我举一个例子来说明:
比如我自己写了一个类叫CSuperEdit(父类为CEdit),在该类中我声明了void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);并在消息循环里添加了ON_WM_CHAR 一行
现在我只要在对话框CProg1Dlg 中声明CSuperEdit m_edit;然后在CProg1Dlg::OnInitDialog中,添加以下代码,就完成了“超类化”:
HWND hWndControl = ::GetDlgItem(pParent->m_hWnd, IDC_EDIT1);
m_edit.SubclassWindow (hWndControl);

这样超类化处理以后:
当我们调用m_edit.SetWindowText("<请输入A、B、C>");,后IDC_EDIT1窗体上对应的文字就会改变为"<请输入A、B、C>"
当用户在IDC_EDIT1窗体中敲键盘时,系统会调用我自己写的CSuperEdit::OnChar函数(而不是原先的CEdit::OnChar)

二:超类化实现的概述
所有的秘密都在CWnd::SubclassWindow 中,让我们查看一下它到底做了些什么吧,以下是函数体(在WINCORE.CPP文件内):
BOOL CWnd::SubclassWindow(HWND hWnd)
{
  if (!Attach(hWnd))
     return FALSE;

   // allow any other subclassing to occur
  PreSubclassWindow ();

  // now hook into the AFX WndProc
  WNDPROC* lplpfn = GetSuperWndProcAddr();
  WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)AfxGetAfxWndProc());
  ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());
  return TRUE;
}

结合注释不难想到PreSubclassWindow 是非功能性的函数,所以我们只要研究两个函数就可以了解CWnd::SubclassWindow 的大概功能 CWnd::Attach和 ::AfxGetAfxWndProc
两者中当中CWnd::Attach 对应于实现了功能1,即“我们对该窗体实例调用成员函数将会直接改变相关窗体句柄对应的窗体”
::AfxGetAfxWndProc函数对应于实现了功能2,即“系统传给相关窗体句柄的消息会先经过该窗体实例的消息映射”

 

三:功能1的实现
CWnd::Attach 的函数体如下(在WINCORE.CPP文件内):
BOOL CWnd::Attach(HWND hWndNew)
{
  if (hWndNew == NULL)
     return FALSE;

  CHandleMap* pMap = afxMapHWND(TRUE); // create map if not exist

  ASSERT(pMap != NULL);
  pMap->SetPermanent(m_hWnd = hWndNew, this);
  return TRUE;
}
最关键的是m_hWnd = hWndNew 一句(接触过windows的API的朋友都知道,windows系统所有窗体操作函数都是把窗体句柄作为一个调用参数),显然只要我把窗体的句柄保存下来,那我就可以在系统中唯一地指定一个窗体,然后对该窗体进行操作
是的,思路就是这么简单。我们现在看到CWnd(别忘了CsuperEdit 是从CWnd继承的,这里的CWnd实际就是CsuperEdit )在Attach 函数中把IDC_EDIT1 的句柄保存在了成员变量m_hWnd 中,那么实现功能1,自然也就不在话下了

至于CHandleMap::SetPermanent 函数则是用来延长句柄的使用期的,与“超类化”无关,不在此处讨论,其具体实现可参考WINHAND_.H文件

 

四:功能2的实现
四点一:窗体句柄的GWL_WNDPROC属性
在前面的讨论中,我说过功能2是跟::AfxGetAfxWndProc 有关的,该函数的实现是这样的(也是在WINCORE.CPP文件中):
WNDPROC AFXAPI AfxGetAfxWndProc()
{
#ifdef _AFXDLL
 return AfxGetModuleState()->m_pfnAfxWndProc;
#else
 return &AfxWndProc;
#endif
}

这是指在DLL中调用的话返回AfxGetModuleState()->m_pfnAfxWndProc;否则返回AfxWndProc 函数的地址。于是在一般的可执行文件中CWnd::SubclassWindow 为功能2所做的事可以简化为一行::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)&AfxWndProc);

该函数的作用是把窗体句柄hWnd 的GWL_WNDPROC 属性设置为AfxWndProc 的地址,那么现在急需解决的问题是:窗体句柄的GWL_WNDPROC 属性是干什么用的?其实不用我说,大家都猜得到(因为我们是在讨论窗体的消息嘛,而且我也一直在说AfxWndProc是一个函数),它的作用是指定窗体消息的处理函数
对于该属性更准确地描述如下:对于发给窗体的所有消息,Windows操作系统将会以该消息为参数调用窗体句柄的GWL_WNDPROC属性所指定的函数

四点二:被传递到MFC环境中
(本节参考了侯捷老师《深入浅出MFC》中“消息映射与命令传递”一章的“两万五千里长征”)
于是功能2可以表述为:AfxWndProc函数是如何找到我为CSuperEdit 类所写的消息映射的?还是从函数体出发
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
 // special message which identifies the window as using AfxWndProc
 if (nMsg == WM_QUERYAFXWNDPROC)
  return 1;

 // all other messages route through message map
 CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
 return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

如上所列::AfxWndProc 整个函数只有四行,显然它仅仅是包装了::AfxCallWndProc 函数,只是把hWnd参数包装成pWnd,然后转道::AfxCallWndProc。
::AfxCallWndProc该函数才是真正做了一些事的,但其中与消息传递有关直接关系的就一句:
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
 WPARAM wParam = 0, LPARAM lParam = 0)
{
 ...

 // delegate to object's WindowProc
 lResult = pWnd->WindowProc(nMsg, wParam, lParam);

 ...
 return lResult;
}

现在我们已经看到通过::AfxWndProc/::AfxCallWndProc 两个函数的接力,操作系统中消息被传递到MFC环境中的。
进一步的讨论可以把所有的目光都集中到LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam);

 

四点三:总结
我们看到转机了:为了实现不同的函数调用,OOP(面对对象编程)本身提供继承、虚函数之类的许多的方法。MFC正是一种面对对象的语言

现在CsuperEdit 是继承自CEdit,CEdit 又继承自CWnd,我们要让程序调用CsuperEdit::OnChar 也就没什么技术难度。比如,可以在CWnd中写一个响应键盘消息的虚函数 virtual void CWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);,并在CWnd::WindowProc 中调用OnChar
那么我只要重载CsuperEdit::OnChar 函数,程序自然而然就会调用我写的函数了

微软为了减小程序文件的体积,做了一些优化工作,它未用virtual 修饰符来修饰所有的函数,而是把“要响应的消息和相应的响应函数”登记在一张MESSAGE_MAP(称,消息映射)里。
在AFXMSG_.H文件中ON_WM_CHAR 宏定义被为{WM_CHAR, 0, 0, ... &OnChar},它的作用就是把WM_CHAR和当前类(现在指CsuperEdit)的OnChar函数,填加到了消息映射的登记表中
既然有了“消息映射”这样一张的登记表,对于“让CWnd在接受到WM_CHAR 消息时调用CsuperEdit::OnChar”的算法和代码,估计你我都能在两小时内实现,我就不在此处罗嗦了,至于MFC中的相关的代码请参考“深入浅出”一书

 

=======================================================================

参考: http://msdn.microsoft.com/en-us/library/ms649784.aspx

时间: 2024-10-18 13:38:52

[技术学习]浅谈MFC中超类化技术的实现 .的相关文章

一起谈.NET技术,浅谈C#中的延迟加载(2)——善用virtual

之前的文章"浅谈C#中的延迟加载(1)--善用委托"中介绍了三层结构中在Model层对实体类的属性实现延迟加载的方法,该方法利用C#中的委托来实现,最后虽然延迟加载的目的得以实现,但是给客户端(例如UI层)暴露了不必要的属性(一个委托对象,我使用了泛型的Fun类来实现).这篇文章介绍一种方法来隐藏这个属性,同时又可以达到延迟加载的目的,更重要的是这一切都是在之前的基础上来完成的,不需要改变原来使用到实体类的地方的代码. 按照惯例,我们考虑一下想要我们的代码达到什么效果:首先在Model

策先生:浅谈SEO中关键词分词技术

背景资料: 策先生--策恩"世界名鞋淘宝客"推广大赛特约评委 曾于第一季推广大赛期间对参赛选手表现进行每周精简点评,专治疑难杂症,为一众参赛选手所知晓.为人低调且神秘,专注网站推广数年.现任第二季策恩"世界名鞋淘宝客"推广大赛评委,行踪不甚明朗. 说到中文分词,我觉得只要从事SEO工作有关的朋友都应该深刻理解这一知识,理解搜索引擎是如何识别词与词之间的关系,如何判别语句的含义的.因为您只有充分的了解.贯穿应用中文分词才可以在关键词收集.分析.布局,包括标题的攒写做得

一起谈.NET技术,浅谈C#中的延迟加载(3)——还原模型的业务规则

上一篇文章讲到把实体类中需要实现延迟加载的属性声明为virtual,然后继承实体类做一个子类,在子类里面实现该属性,配合使用委托来实现比较完美的延迟加载(原本的模型层依旧保持在最底层用于贯穿三层结构,同时又可以实现在实体类的属性里面访问到比他高层的数据访问层).文章的最后依旧出现杯具,原因是在对模型的属性实现延迟加载之前,这个属性可能由于我们业务的需要,它并不单单是作为一个存储和读取的功能使用,而是在其get或者set的访问器中都包含这或许复杂或许简单的逻辑代码. 举例:考虑一下这个情景,我们有

一起谈.NET技术,浅谈C#中的延迟加载(1)——善用委托

很久以前就听过延迟加载这个东西,不过没有理解是什么意思,现在算是了解一二了,写点文章作为读书笔记,把自己的想法记录一下,希望对初学者帮助,不管是初学者或者高手如果发现文章那里写得不好或者有更好的思路和做法记得告诉我哦^^.文章打算写成两三篇,这个是第一篇. 在三层结构中我们通常会使用多一个叫做模型层的东西,这一层中最主要做的事情是把数据库中的表 (或者其他数据源,例如xml或者自己定义的一种数据格式)转成对应的类,例如有一个文章表,这时候在这一层就会有一个文章类:文章类的属性对应着文章表的列,例

一起谈.NET技术,浅谈.Net中容易混淆的委托和接口

本文适合对委托和接口概念非常了解的朋友,并且欢迎各位朋友与Snake一起探讨有关这方面的知识.本文不适合对委托和接口概念或用途了解一知半解(模糊)的朋友,这篇文章可能会对您产生误导,请千万别看. 在文章正式开始之前我需要将MSDN上对委托和接口的内容放上来,作为文章之基. 委托: 委托是一种定义方法签名的类型.当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联.您可以通过委托实例调用方法. 委托用于将方法作为参数传递给其他方法.事件处理程序就是通过委托调用的方法.您可以创建一个自定义方

浅谈JS中的bind方法与函数柯里化_javascript技巧

绑定函数bind()最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值.不同于call和apply只是单纯地设置this的值后传参,它还会将所有传入bind()方法中的实参(第一个参数之后的参数)与this一起绑定. 关于这个特性看<JS权威指南>原文的例子: var sum = function(x,y) { return x + y }; var succ = sum.bind(null, 1); //让this指向null,其后的实参也会作为实参传入被绑定的函数sum

浅谈VC中的字节对齐

原文地址:浅谈VC中的字节对齐 前几天时,在公司和同事说到了字节对齐,一直对这个概念比较模糊,只是在<程序员面试宝典>中看到过简单的描述和一些面试题.后来在论坛中有看到有朋友在询问字节对齐的相关问题,自己也答不上来,觉得应该研究一下,所以就有了这一篇博文,是对学习的一个总结,也是对成长轨迹的一个记录.       字节对齐,又叫内存对齐,个人理解就是一种C++中的类型在内存中空间分配策略.每一种类型存储的起始地址,都要求是一个对齐模数(alignment modulus)的整数倍.问题来了,为

C++ 中超类化和子类化及他们的区别

C++ 中超类化和子类化 超类化和子类化没有具体的代码,其实是一种编程技巧,在MFC和WTL中可以有不同的实现方法. 窗口子类化: 原理就是改变一个已创建窗口类的窗口过程函数.通过截获已创建窗口的消息,从而实现监视或修改已创建窗口类的行为属性.可以用来改变或者扩展一个已存在的窗口的行为,而不用重新开发.比如要获得那些预定义控件窗口类(按钮控件.编辑控件.列表控件.下 拉列表控件.静态控件和滚动条控件)的功能而又要修改它们的某些行为. 子类化的优点主要体现在以下两个方面:首先,它不需要创建新的窗口

浅谈JS中逗号运算符的用法_javascript技巧

注意: 一.由于目前正在功读JavaScript技术,所以这里拿JavaScript为例.你可以自己在PHP中试试. 二.JavaScript语法比较复杂,因此拿JavaScript做举例. 最近重新阅读JavaScript权威指南这本书,应该说很认真的阅读,于是便想把所学的东西多记录下来.后 面本人将逐步写上更多关于本书的文章. 本文的理论知识来自于JavaScript权威指南,我这里做一下整理,或者说叫笔记. 如果你的基础够好的话,完全理解不成问题,但是如果读得有些郁闷的话,可以加我的QQ: