MFC 消息映射机制

news2024/9/21 4:38:48

目录

消息映射机制概述

宏展开

宏展开的作用

消息映射机制的执行流程

消息处理


消息映射机制概述

MFC的消息映射映射机制是可以在不重写WindowProc虚函数的大前提下,仍然可以处理消息。

类必须具备的要件

类内必须添加声明宏   DECLARE_MESSAGE_MAP()

类外必须添加实现宏:

  • BEGIN_MESSAGE_MAP(theClass , baseClass)
  • END_MESSAGE_MAP()

总结:当一个类具备上述两个要件,这个类就可以按照消息映射机制来处理消息。

MFC利用消息映射机制处理消息:以处理WM_CREATE消息为例

类内:

  • 添加声明宏   DECLARE_MESSAGE_MAP()
  • 添加处理WM_CREATE消息的函数声明

类外:

  • 添加宏:BEGIN_MESSAGE_MAP(theClass , baseClass)   END_MESSAGE_MAP()
  • 实现处理WM_CREATE消息的函数定义

宏展开

对宏代码进行展开,得到下面的成果

#include <afxwin.h>
class CMyFrameWnd : public CFrameWnd {
	//	DECLARE_MESSAGE_MAP()
protected:
	static const AFX_MSGMAP* PASCAL GetThisMessageMap();
	virtual const AFX_MSGMAP* GetMessageMap() const;
public:
	LRESULT OnCreate(WPARAM wParam, LPARAM lParam);
};
//BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
//	ON_MESSAGE( WM_CREATE, OnCreate )
//END_MESSAGE_MAP()


const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
	return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap()
{
	static const AFX_MSGMAP_ENTRY _messageEntries[] =
	{
		{ WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)
			(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)> (&OnCreate)) },

		{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
	};
	static const AFX_MSGMAP messageMap = { &CFrameWnd::GetThisMessageMap, &_messageEntries[0] };
	return &messageMap;
}

LRESULT CMyFrameWnd::OnCreate(WPARAM wParam, LPARAM lParam) {
	AfxMessageBox("WM_CREATE");
	return 0;
}

class CMyWinApp : public CWinApp {
public:
	virtual BOOL InitInstance();
};
CMyWinApp theApp;//爆破点
BOOL CMyWinApp::InitInstance() {
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	pFrame->Create(NULL, "MFCCreate");
	m_pMainWnd = pFrame;
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

 声明了两个成员函数

DECLARE_MESSAGE_MAP()

声明两个函数:

protected:
	static const AFX_MSGMAP* PASCAL GetThisMessageMap();
	virtual const AFX_MSGMAP* GetMessageMap() const;

拓展:在C++中,static修饰函数可以有以下两种含义:

(1) 静态成员函数,特点包括:

  1. 不属于类的任何特定对象,而是属于整个类。
  2. 可以访问类的静态成员变量和其他静态成员函数,但不能直接访问类的非静态成员变量和非静态成员函数。
  3. 不能使用this指针,因为this指针指向类的对象实例,而静态成员函数并不属于任何特定对象。
  4. 静态成员函数可以直接通过作用域解析运算符(::)访问类的静态成员变量和静态成员函数,无需通过对象。

静态成员函数通常用于执行与类相关的操作,而不依赖于特定对象的状态。例如,可以在静态成员函数中计算或处理类的静态成员变量,或者实现与类相关的全局操作。

(2) 文件作用域的静态函数

文件作用域的静态函数是指在C或C++中使用static关键字声明的函数,这种函数的作用域限定在当前文件内,不能被其他文件访问或调用。在文件中使用static修饰的函数通常用于实现模块内部的辅助函数或者限制函数的作用域,以减少全局命名空间的污染。

拓展:在C++中,const修饰函数可以分为两种情况:const成员函数和const修饰的非成员函数。

(1)  const成员函数:const成员函数是指在函数声明或定义的末尾加上const关键字,用于表示该成员函数不会修改对象的状态。在const成员函数中,不能修改成员变量的值,也不能调用非const成员函数,以确保该函数不会改变对象的状态。

(2)  const修饰的非成员函数: const修饰的非成员函数是指在函数声明或定义的末尾加上const关键字,用于表示函数的返回值是常量。

示例:

const int getValue() {
    return 10; // 返回一个常量值
}

const string& getName() {
    static const string name = "John";
    return name; // 返回一个常量引用
}

实现了两个函数

BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)

END_MESSAGE_MAP()

第一个函数的返回值是第二个函数的返回值 

const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const
{
	return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap()
{
	static const AFX_MSGMAP_ENTRY _messageEntries[] =
	{
		{ WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)
			(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)> (&OnCreate)) },

		{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
	};
	static const AFX_MSGMAP messageMap = { &CFrameWnd::GetThisMessageMap, &_messageEntries[0] };
	return &messageMap;
}

ON_MESSAGE( WM_CREATE, OnCreate )  相当于

{ WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)
			(static_cast<LRESULT(AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)> (&OnCreate)) },

宏展开的作用

首先学习两个数据结构

这个结构体,与我们需要处理的消息有关,主要需要关注第一个与最后一个即可

struct AFX_MSGMAP_ENTRY
{
	UINT nMessage;    
	UINT nCode;       
	UINT nID;       
	UINT nLastID;    
	UINT_PTR nSig;     
	AFX_PMSG pfn;    
};

这个结构体的成员表示如下:

  • 消息ID,用于标识Windows消息类型的整数值。
  • 通知码,标识控件ID值
  • 命令ID,用于区分控件的不同命令,比如:有一个“打开”菜单项和一个“保存”菜单项,它们分别对应着打开文件和保存文件的操作。在程序内部,为了识别用户点击了哪个菜单项,就需要为每个菜单项分配一个唯一的命令ID。当用户点击“打开”菜单项时,程序就会根据这个命令ID来执行打开文件的操作;当用户点击“保存”菜单项时,程序则会根据另一个命令ID来执行保存文件的操作。
  • 最后一个命令ID,用于标识此消息关联的前一个命令的整数值。
  • 处理消息的函数类型
  • 处理消息的函数名(地址)

这个结构体主要和遍历链表有关

struct AFX_MSGMAP
{
	const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
	const AFX_MSGMAP_ENTRY* lpEntries;
};

这个结构体的成员表示如下:

  • 父类宏展开的静态变量地址
  • 本类宏展开的静态数组首地址

宏展开各部分的作用

局部静态变量:与普通的局部变量不同之处在于其生存期和作用域。当函数被调用时,静态局部变量不会被销毁,而是保留其数值,直到程序运行结束。此外,在函数内部,静态局部变量的作用域仅限于声明它的函数内部。

GetThisMessageMap():静态函数

作用:定义静态变量和静态数组,并返回本类静态变量地址(获取链表头)

_messageEntries[]:静态数组(进程级声明周期)

作用:数组每个元素,保存为 消息ID 和 处理消息的函数名(地址)

messageMap:静态变量(进程级声明周期)

作用:第一个成员,保存父类宏展开的静态变量地址(负责连接链表)

           第二个成员,保存本类的静态数组首地址

GetMessageMap():虚函数

作用:返回本类静态变量地址(获取链表头)

两个结构体在消息映射机制实现的作用

  • messageMap第一个是父类GetThisMessageMap函数地址,第二个是本类的_messageEntries数组地址
  • _messageEntries结构体数组地址中的每一个元素都是消息和对应处理函数地址

CMyFrameWnd有一套这样的局部静态本类,CFrameWnd,CWnd都有,到此为止,CWnd的父类就没有了

这就构成一个链表的结构:

在 CFrameWnd,CWnd 类中都有对消息的处理函数

消息映射机制的执行流程

下载WM_CREATE消息处理函数下断点

消息产生进入窗口处理函数(AfxWndProc),对此函数下断点开始分析,前三个消息不是WM_CREATE消息,先F5放过,直到 nMsg 值为1

通过句柄拿到框架窗口对象

CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

之后调用AfxCallWndProc,在之后调用WindowProc

lResult = pWnd->WindowProc(nMsg, wParam, lParam);

再之后调用OnWndMsg

	if (!OnWndMsg(message, wParam, lParam, &lResult))
		lResult = DefWindowProc(message, wParam, lParam);

在这里对不同的消息处理都不一样

获取本类宏站开的静态变量的地址(链表头结点)

const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();

F11,回到我们的代码了

开始遍历结构体数组,循环中每次迭代条件就是获得父类GetThisMessageMap函数地址

如果找到返回找到的数组元素的地址,如果没找到返回NULL,找到之后goto跳出循环

if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,message, 0, 0)) != NULL)
{
	pMsgCache->lpEntry = lpEntry;
	winMsgLock.Unlock();
	goto LDispatch;
}
lpEntry->pfn; //CMyFrameWnd::OnCreate

调用CMyFrameWnd::OnCreate函数完成消息的处理

LRESULT CMyFrameWnd::OnCreate(WPARAM wParam, LPARAM lParam) {
	AfxMessageBox("WM_CREATE");
	return 0;
}

执行完后,再次会到这里

总结:

  1. 消息产生进入窗口处理函数(AfxWndProc )
  2. 根据已知窗口句柄,找到和它绑定在一起的框架类对象地址( pFrame )。
  3. 利用框架类对象地址( pFrame )调用框架类成员虚函数WindowProc
  4. 获取本类对应的静态变量,并到对应数组中匹配查找。
  5. 如果没有找到获取父类对应的静态变量,并到对应数组中匹配查找。
  6. 如果找到了,利用找到的数组元素的最后一个成员,并调用之,完成消息处理。
     

消息分类

主要有以下三类:

  • 标准windows消息:ON_WM_XXX
  • 自定义消息:ON_MESSAGE
  • 命令消息:ON_COMMAND,暂且不管

第一类消息处理函数的返回值,参数都是固定的,第二类就不是了。这些规定可以再MSDN中查到

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1325757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【音视频 | AAC】AAC格式音频文件解析

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

33 在Vue3中如何通过插槽向父组件传值

概述 通过插槽向父组件传值&#xff0c;是一种比较高级的&#xff0c;但是非常使用的技术&#xff0c;在很多UI组件库里面经常看到。 这节课我们来学习一下这种用法。 基本用法 我们创建src/components/Demo33.vue&#xff0c;代码如下&#xff1a; <script setup> …

U盘无法读取怎么办?U盘无法读取修复方法

U盘无法读取是常见的故障&#xff0c;可能的原因包括U盘驱动程序未安装、U盘损坏、文件系统损坏等。为了解决这个问题&#xff0c;可以尝试重新安装U盘驱动程序、格式化U盘、检查U盘是否损坏等方法。如果以上方法均无效&#xff0c;建议寻求专业人士的帮助。 U盘无法读取怎么办…

MySQL——基础篇

学习视频链接&#xff1a;https://www.bilibili.com/video/BV1Kr4y1i7ru/?spm_id_from333.999.0.0&vd_source619f8ed6df662d99db4b3673d1d3ddcb 前言✴️ 基础篇——MySQL概述、SQL、函数、约束、多表查询、事务 进阶篇——存储引擎、索引、SQL优化、视图/存储过程/触发…

磁盘类型选择对阿里云RDS MySQL的性能影响

测试说明 这是一个云数据库性能测试系列&#xff0c;旨在通过简单标准的性能测试&#xff0c;帮助开发者、企业了解云数据库的性能&#xff0c;以选择适合的规格与类型。这个系列还包括&#xff1a; * 云数据库(RDS MySQL)性能深度测评与对比 * 阿里云RDS标准版(x86) vs 经济…

GitHub two-factor authentication开启教程

问题描述 最近登录GitHub个人页面动不动就有一个提示框”… two-factor authentication will be required for your account starting Jan 4, 2024 …“&#xff0c;点击去看了一下原来是GitHub对所有的用户登录都要开启双重身份认证&#xff0c;要在1月4号前完成 解决办法 …

Jenkins + gitlab 持续集成和持续部署的学习笔记

1. Jenkins 介绍 软件开发生命周期(SLDC, Software Development Life Cycle)&#xff1a;它集合了计划、开发、测试、部署的集合。 软件开发瀑布模型 软件的敏捷开发 1.1 持续集成 持续集成 (Continuous integration 简称 CI): 指的是频繁的将代码集成到主干。 持续集成的流…

清风数学建模学习笔记-斯皮尔曼相关系数

内容&#xff1a;斯皮尔曼相关系数 一.原理&#xff1a; 二.算法&#xff1a; 1.MATLAB: 2.SPSS&#xff1a; 分析-相关-双变量相关-勾选标注显著性相关性 3. 相关性系数的选择&#xff1a;

三大主流前端框架介绍及选型

在前端项目中&#xff0c;可以借助某些框架&#xff08;如React、Vue、Angular等&#xff09;来实现组件化开发&#xff0c;使代码更容易复用。此时&#xff0c;一个网页不再是由一个个独立的HTML、CSS和JavaScript文件组成&#xff0c;而是按照组件的思想将网页划分成一个个组…

IDEA版SSM入门到实战(Maven+MyBatis+Spring+SpringMVC) -SpringMVC搭建框架

第一章 初识SpringMVC 1.1 SpringMVC概述 SpringMVC是Spring子框架 SpringMVC是Spring 为**【展现层|表示层|表述层|控制层】**提供的基于 MVC 设计理念的优秀的 Web 框架&#xff0c;是目前最主流的MVC 框架。 SpringMVC是非侵入式&#xff1a;可以使用注解让普通java对象&…

自动化测试|Eolink Apikit 如何保存、使用测试用例

测试用例是测试过程中很重要的一类文档&#xff0c;它是测试工作的核心&#xff0c;是一组在测试时输入和输出的标准&#xff0c;是软件需求的具体对照。 测试用例可以帮助测试人员理清测试思路&#xff0c;确保测试覆盖率&#xff0c;发现需求漏洞&#xff0c;提高软件质量&a…

HarmonyOS 应用事件打点开发指导

简介 传统的日志系统里汇聚了整个设备上所有程序运行的过程流水日志&#xff0c;难以识别其中的关键信息。因此&#xff0c;应用开发者需要一种数据打点机制&#xff0c;用来评估如访问数、日活、用户操作习惯以及影响用户使用的关键因素等关键信息。 HiAppEvent 是在系统层面…

计算机模拟仿真:技术与应用

计算机模拟仿真&#xff1a;技术与应用 一、引言 计算机模拟仿真是一种利用计算机技术对现实世界或系统进行模拟和仿真的方法。随着计算机技术的不断发展&#xff0c;计算机模拟仿真已经成为许多领域中不可或缺的技术工具。本文将介绍计算机模拟仿真的基本概念、技术原理、应用…

在做题中学习(34):两整数之和(不准用运算符+)

371. 两整数之和 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;异或&#xff08;两个数异或可看作无进位相加&#xff09; 当进位b为全0的时候&#xff0c;那异或的结果就是真正相加的结果。 class Solution { public:int getSum(int a, int b) {while(b!0){int…

2023年度佳作:AIGC、AGI、GhatGPT 与人工智能大模型的创新与前景展望

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 写在前面参与规则 ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论…

系统设计架构——互联网案例

Netflix 的技术栈 移动和网络:Netflix 采用 Swift 和 Kotlin 来构建原生移动应用。对于其 Web 应用程序,它使用 React。 前端/服务器通信:Netflix 使用 GraphQL。 后端服务:Netflix 依赖 ZUUL、Eureka、Spring Boot 框架和其他技术。 数据库:Netflix 使用 EV 缓存、Cas…

阿里推荐 LongAdder ,不推荐 AtomicLong !

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、CAS 1.1 CAS 全称 1.2 通俗理解CAS 1.3 CAS的问题 1.4 解决 ABA 问题 二、LongAdder 2.1 什么是 LongAdder 2.2 为什么推…

骨传导蓝牙耳机什么品牌音质好,最全盘点骨传导耳机品牌前五汇总

秋天到了&#xff0c;天气不那么热了&#xff0c;运动的感觉又回来了&#xff01;这时候&#xff0c;配上耳机里舒缓的音乐&#xff0c;简直是一种享受。说到运动耳机&#xff0c;大家知道吗&#xff1f;骨传导耳机可是运动的最佳拍档哦。它跟普通蓝牙耳机不一样&#xff0c;不…

【git学习笔记 01】打标签

文章目录 一、声明二、对标签的基本认知什么是标签&#xff1f;为什么要打标签&#xff1f;如何生成类似github中readme的图标 三、标签相关命令四、示例操作 一、声明 本帖持续更新中如有纰漏&#xff0c;望批评指正&#xff01;参考视频链接&#xff0c;非常感谢原作者&…

外贸SEO建站系统有哪些?海洋建站的优势?

外贸SEO建站怎么做比较好&#xff1f;如何做谷歌独立站SEO优化&#xff1f; 外贸企业越来越需要建立自己的在线形象&#xff0c;以吸引更多的潜在客户。而要实现这一目标&#xff0c;外贸SEO建站系统是一种非常有效的手段。那么&#xff0c;外贸SEO建站系统有哪些呢&#xff1…