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

2024-03-30 23:48

本文主要是介绍[技术学习]浅谈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

 

这篇关于[技术学习]浅谈MFC中超类化技术的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/862901

相关文章

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义