[技术学习]浅谈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

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Spring Security自定义身份认证的实现方法

《SpringSecurity自定义身份认证的实现方法》:本文主要介绍SpringSecurity自定义身份认证的实现方法,下面对SpringSecurity的这三种自定义身份认证进行详细讲解,... 目录1.内存身份认证(1)创建配置类(2)验证内存身份认证2.JDBC身份认证(1)数据准备 (2)配置依

利用python实现对excel文件进行加密

《利用python实现对excel文件进行加密》由于文件内容的私密性,需要对Excel文件进行加密,保护文件以免给第三方看到,本文将以Python语言为例,和大家讲讲如何对Excel文件进行加密,感兴... 目录前言方法一:使用pywin32库(仅限Windows)方法二:使用msoffcrypto-too