MFC 消息映射机制

目录

消息映射机制概述

宏展开

宏展开的作用

消息映射机制的执行流程

消息处理


消息映射机制概述

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中查到

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

刷题记录第五十一天-去除重复字母

题目要求的是字典序最小的结果。只需要理解一点就是按大小顺序排列的字符串的字典序就是最小的&#xff0c;如“abcd”这种。 解题思路如下&#xff1a; 首先明确要使用栈结构&#xff0c;并且是从栈底到栈顶递增&#xff0c;要尽可能保证递增&#xff0c;这样就能保证字典序最…

exsi 6.5 添加RTL8111/8168/8411 网卡驱动重新打包

参考安装esxi时候的No Network Adapters报错 解决办法-CSDN博客 lspci 查看网卡型号 RTL8111/8168/8411 PCI Express 驱动下载地址 List of currently available ESXi packages - V-Front VIBSDepot Wiki 注入驱动程序 https://vibsdepot.v-front.de/tools/ESXi-Customi…

mysql 23-2day 数据库查询(DQL)

目录 数据库查询(DQL)环境&#xff1a;准备一个表格作为查询环境查看数据根据要求查看数据运算查询as 可以修改字段名字 进行查询查询所有部门拼接两个字段查询 2017年入职的员工一个是空null 一个是空白查询 NULL集合排序查询查看有那些组通配符正则查询函数 数据库查询(DQL) …

传输层协议分析--第4关:UDP 包分析

任务描述 本关任务&#xff1a;能够掌握简单的 UDP 包分析。 相关知识 为了更好掌握本章内容&#xff0c;你需要了解的有&#xff1a; UDP 报文的简介&#xff1b;UDP 报文格式&#xff1b;Wireshark 软件中的 UDP 抓包分析。 UDP 简介 UDP&#xff08;User Datagram Pro…

Python与Flink的完美融合:合流基本操作解析

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Apache Flink 是一个流式处理框架&#xff0c;支持复杂事件处理和大规模数据分析。在 Flink 中&#xff0c;合流&#xff08;Join&#xff09;是一种常见的操作&#xff0c;用于将两个或多个流中的数据按照指定条…

12.21 汇编点亮STM32MP157小灯

.text .global _start _start: 时钟使能pb6 pf6 pe9LDR r0,0x50000A28LDR r1,[r0]ORR r1,r1,#(0x1<<4)ORR r1,r1,#(0x1<<5)ORR r1,r1,#(0x1<<1)STR r1,[r0]配置GPIO模式LDR r0,0x50006000LDR r1,[r0]BIC r1,r1,#(0x2<<20)ORR r1,r1,#(0x1<<20)B…

【UE】阅读和理解距离剔除源码

距离剔除 官方文档&#xff1a;虚幻引擎中的剔除距离体积 | 虚幻引擎5.2文档 (unrealengine.com) 距离剔除&#xff0c;顾名思义&#xff0c;是根据距离来将场景对象的渲染进行加卸载的一种管理方式。 用距离剔除&#xff0c;可以减轻场景同时渲染大量物品的情况&#xff0c;…

ACM32F42x/4x3优势有那些?可应用在那些场景上?

优势 • 最大4MB Flash&#xff0c;可用于同时存储程序代码静态图片 • 128KB/196KB SRAM用于程序堆栈部分图片缓存 • 叠封最大8MB PSRAM&#xff0c;用于大容量图片缓存 • 180MHz M33内核&#xff0c;处理性能极佳 • 可选QFN32(4x4)、QFN48(5x5)小封装&#xff0…

动画渲染需要什么配置电脑?动画云渲染有什么优惠?

​在电影制作、游戏开发、广告设计以及其他设计领域&#xff0c;CG&#xff08;计算机图形学&#xff09;这一发展迅速、并融合了艺术创作与科技应用的领域发挥了重大作用。对于追求在 CG 创作中达到卓越表现的人来说&#xff0c;拥有一台高性能电脑设备至关重要。为此&#xf…

利用prometheus+grafana进行Linux主机监控

文章目录 一.架构说明与资源准备二.部署prometheus1.上传软件包2.解压软件包并移动到指定位置3.修改配置文件4.编写启动脚本5.启动prometheus服务 三.部署node-exporter1.上传和解压软件包2.设置systemctl启动3.启动服务 四.部署grafana1.安装和启动grafana2.设置prometheus数据…

Python实验作业,爬虫,中国院士信息

实验内容&#xff1a; 爬取中国工程院网页上&#xff0c;把每位院士的简介保存为本地文本文件&#xff0c;把每位院士的照片保存为本地图片&#xff0c;文本文件和图片文件都以院士的姓名为主文件名。 实验代码&#xff1a; import os.path import time from urllib.request …

web打印技术方案

在B/S应用系统开发中常常遇到表单打印需求&#xff0c;尤其是OA、ERP类的企业运营管理系统&#xff0c;打印的需求很常见&#xff0c;但WEB应用的打印一直以来是一个难题&#xff0c;特别是在应用中完成标签打印&#xff08;如包裹面单、货运标签等&#xff09;、票据打印&…

华为OD机试 - 区间交集 - 深度优先搜索dfs算法(滥用)(Java 2023 B卷 200分)

目录 专栏导读一、题目描述二、输入描述三、输出描述备注用例1、输入2、输出3、说明 四、解题思路1、核心思路&#xff1a;2、具体步骤 五、Java算法源码再重新读一遍题目&#xff0c;看看能否优化一下~解题步骤也简化了很多。 六、效果展示1、输入2、输出3、说明 华为OD机试 2…

用最通俗的语言讲解 TCP “三次握手,四次挥手”

目录 一. 前言 二. TCP 报文的头部结构 三. 三次握手 3.1. 三次握手过程 3.2. 为什么要三次握手 四. 四次挥手 4.1. 四次挥手过程 4.2. 为什么要四次挥手 五. 大白话说 5.1. 大白话说三次握手 5.2. 大白话说四次挥手 六. 总结 一. 前言 TCP 是一种面向连接的、可靠…

【SpringBoot快速入门】(3)SpringBoot整合junit和MyBatis 详细代码示例与讲解

目录 1.SpringBoot整合junit1.1 环境准备1.2 编写测试类 2.SpringBoot整合mybatis2.1 回顾Spring整合Mybatis2.2 SpringBoot整合mybatis2.2.1 创建模块2.2.2 定义实体类2.2.3 定义dao接口2.2.4 定义测试类2.2.5 编写配置2.2.6 测试2.2.7 使用Druid数据源 之前我们已经学习的Spr…

I.MX6ULL_Linux_驱动篇(47)linux RTC驱动

RTC 也就是实时时钟&#xff0c;用于记录当前系统时间&#xff0c;对于 Linux 系统而言时间是非常重要的&#xff0c;就和我们使用 Windows 电脑或手机查看时间一样&#xff0c;我们在使用 Linux 设备的时候也需要查看时间。本章我们就来学习一下如何编写 Linux 下的 RTC 驱动程…

spring boot回顾02

配置文件 SpringBoot使用一个全局的配置文件 &#xff0c; 配置文件名称是固定的 application.properties 语法结构 &#xff1a;keyvalue application.yml 语法结构 &#xff1a;key&#xff1a;空格 value 配置文件的作用 &#xff1a;修改SpringBoot自动配置的默认值&am…

低功耗 电源管理 SCMI接口

SCMI overview&#xff1a; SCMI 协议&#xff1a;

本地websocket服务端结合cpolar内网穿透实现公网访问

文章目录 1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功,暴露端口默认99995. 创建隧道映射内网端口6. 查看状态->在线隧道,复制所创建隧道的公网地址加端口号7. 以…

VMware克隆虚拟机

要求&#xff1a;利用模板虚拟机hadoop100&#xff0c;克隆出hadoop101虚拟机。 1、鼠标右键点击已存在的模板虚拟机hadoop100 --> 管理 --> 克隆 2、选择克隆自虚拟机中的当前状态 3、创建完整克隆 4、修改虚拟机名称、位置 5、等待克隆完成后&#xff0c;则成功克隆出…