小识MFC,一套设计优雅与不优雅并存的类库----小话MFC(2)

news2024/12/23 13:08:12

Q1: CPoint继承于POINT,这样有什么好处?

A: 继承的一个最基本的好处当然就是减少代码量。CPoint和POINT内部数据一样,只是一个提供了更多的方法来操作对象。

typedef struct tagPOINT
{
    LONG  x;
    LONG  y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;
class CPoint :
	public tagPOINT

这样的方式,很自然,CPoint不用再单独加入成员x,y, 而且函数参数可以很自然的实现从派生类到基类的转换,是个不错的设计。

Q2: CDocument, CView实现界面展示、变化以及用户操作UI是MVC架构吗?

A: 看起来有点像,实际并不是纯正的MVC设计。CDocument的设计更多考虑了windows操作系统MDI文档视图,它希望提供一个MDI中每个视图的模板原型。

CView主要实现显示,不过对于用户和UI的交互,MFC架构中并没有提供框架为MVC的控制器来单独考虑,它将控制器放入了CView, CDocument甚至CWinApp中。

这听起来并不是一个很好的设计,但是实际上,视图和操作视图放在一起也并不是一个万恶不赦的设计, 因为UI本来就改来改去,程序员还是可以接受这样的方式。

Q3: CWnd类内部如此多的成员函数,这样设计合理吗?

A: CWnd主要处理一个窗口的显示,包括窗口标题、最大化最小化、内部子控件获取等。不过ms的设计,将此类内部加入了太多和CWnd关系不是很大的东西,导致了此类成员很多,弊端不用说了,此类不是一个优秀设计,它处理了太多不该去处理的东西,使得整个类库设计清晰度降低;

不过,也有一定优点,很多在主框架或者view中可以直接调用它的成员函数,不用花心思再去想需要调用的函数出自哪个类。

Q4: 使用MFC向导创建的应用程序,里面的消息处理流程很复杂,如何很好地查看消息流?

A: 函数堆栈是查看它的很好方式。

如上,是一个使用MFC app wizzard创建的SDI应用程序basic_mfc.exe开始运行后的调用堆栈。

可以看出,应用程序开始运行后,会调用应用程序类的ProcessShellCommand解析命令行参数,此过程可能就进入了消息处理过程(比如,一个应用程序刚打开,默认的处理是打开一个新文档),如下是ProcessShellCommand的部分代码:

	case CCommandLineInfo::FileNew:
		if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))
			OnFileNew();
		if (m_pMainWnd == NULL)
			bResult = FALSE;
		break;

	// If we've been asked to open a file, call OpenDocumentFile()
	case CCommandLineInfo::FileOpen:
		if (!OpenDocumentFile(rCmdInfo.m_strFileName))
			bResult = FALSE;
		break;

	// If the user wanted to print, hide our main window and
	// fire a message to ourselves to start the printing
	case CCommandLineInfo::FilePrintTo:
	case CCommandLineInfo::FilePrint:
		m_nCmdShow = SW_HIDE;
		ASSERT(m_pCmdInfo == NULL);
		if(OpenDocumentFile(rCmdInfo.m_strFileName))
		{
			m_pCmdInfo = &rCmdInfo;
			ENSURE_VALID(m_pMainWnd);
			m_pMainWnd->SendMessage(WM_COMMAND, ID_FILE_PRINT_DIRECT);
			m_pCmdInfo = NULL;
		}
		bResult = FALSE;
		break;


从上面可以看出,FileNew就是从这里进去的。OnCmdMsg函数会调用全局函数_AfxDispatchCmdMsg,它的部分代码如下:

case AfxSigCmd_v:
		// normal command or control notification
		ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED
		ASSERT(pExtra == NULL);
		(pTarget->*mmf.pfnCmd_v_v)();
		break;

	case AfxSigCmd_b:
		// normal command or control notification
		ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED
		ASSERT(pExtra == NULL);
		bResult = (pTarget->*mmf.pfnCmd_b_v)();
		break;

	case AfxSigCmd_RANGE:
		// normal command or control notification in a range
		ASSERT(CN_COMMAND == 0);        // CN_COMMAND same as BN_CLICKED
		ASSERT(pExtra == NULL);
		(pTarget->*mmf.pfnCmd_v_u)(nID);
		break;

	case AfxSigCmd_EX:
		// extended command (passed ID, returns bContinue)
		ASSERT(pExtra == NULL);
		bResult = (pTarget->*mmf.pfnCmd_b_u)(nID);
		break;

	case AfxSigNotify_v:
		{
			AFX_NOTIFY* pNotify = (AFX_NOTIFY*)pExtra;
			ENSURE(pNotify != NULL);
			ASSERT(pNotify->pResult != NULL);
			ASSERT(pNotify->pNMHDR != NULL);
			(pTarget->*mmf.pfnNotify_v_NMHDR_pl)(pNotify->pNMHDR, pNotify->pResult);
		}
		break;

	case AfxSigNotify_b:
		{
			AFX_NOTIFY* pNotify = (AFX_NOTIFY*)pExtra;
			ENSURE(pNotify != NULL);
			ASSERT(pNotify->pResult != NULL);
			ASSERT(pNotify->pNMHDR != NULL);
			bResult = (pTarget->*mmf.pfnNotify_b_NMHDR_pl)(pNotify->pNMHDR, pNotify->pResult);
		}
		break;


它其实是对不同的消息类型调用不同的默认回调函数,有的是空参数的,有的以一个整形为参数的,等等。最终的返回值表征是否已经处理,外部会根据这个返回值决定是否继续处理下去。

如下是接下来的处理:

这里可以看到,MFC类库内部完成了主要的消息传递过程,最终到达应用程序document类的OnNewDocument来完成最后的处理。

ok,当应用程序启动后,手动点击菜单的新建或者工具栏中的新建,调用堆栈如下:

注意,上面的调用堆栈并不完全,堆栈最底层的是在ntdll中线程启动的代码,这里不列出了。

不过可以看出,应用程序启动后,对于菜单或者工具栏的操作将通过应用程序类的Run函数,它会将UI命令传递进去,让适当的模块处理,这和刚刚启动时的调用堆栈不一致。

在这里,我们主要看看AfxInternalPumpMessage这个函数:

BOOL AFXAPI AfxInternalPumpMessage()
{
	_AFX_THREAD_STATE *pState = AfxGetThreadState();

	if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
	{
#ifdef _DEBUG
		TRACE(traceAppMsg, 1, "CWinThread::PumpMessage - Received WM_QUIT.\n");
			pState->m_nDisablePumpCount++; // application must die
#endif
		// Note: prevents calling message loop things in 'ExitInstance'
		// will never be decremented
		return FALSE;
	}

#ifdef _DEBUG
  if (pState->m_nDisablePumpCount != 0)
	{
	  TRACE(traceAppMsg, 0, "Error: CWinThread::PumpMessage called when not permitted.\n");
	  ASSERT(FALSE);
	}
#endif

#ifdef _DEBUG
	_AfxTraceMsg(_T("PumpMessage"), &(pState->m_msgCur));
#endif

  // process this message

	if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
	{
		::TranslateMessage(&(pState->m_msgCur));
		::DispatchMessage(&(pState->m_msgCur));
	}
  return TRUE;
}


可以看到,它其实主要就是GetMessage, TranslateMessage, DispatchMessage这3个函数,是windows应用程序消息处理基本过程。

后面的调用关系就不具体说了。


微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。

我是程序员小迷(致力于C、C++、Java、Kotlin、Android、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

欢迎关注。助您在编程路上越走越好!

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

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

相关文章

ARM IHI0069F GIC architecture specification (7)

3.1 GIC逻辑组件 GICv3体系结构由一组逻辑组件组成: •Distributor。 •每个受支持的PE都有一个Redistributor。 •支持的每个PE都有一个CPU interface。 •中断翻译服务组件(ITS),支持将事件翻译为LPI。 Distri…

APM2.8飞控

ArduPilotMega 主控可应用于 固定翼、直升机、多旋翼、地面车辆 APM2.8飞控供电有两种 1.电流计供电, 2.带BEC(稳压功能)的电调供电 ArduPilotMega 内部的硬件结构图: 调试时,不要使用向导,由于向导功能不…

windows内存管理

一 windows系统的内存管理涉及哪些 1.1 虚拟内存管理机制 windows操作系统使用虚拟内存技术,将磁盘文件,通过映射对象(存储在物理内存)关联,映射到虚拟内存作为文件试图。即用户操作"虚拟内存中File View Objec…

卷出新高度,直呼太强!时隔三月,YOLO再度进化升级:《YOLOv10—实时端到端目标检测》重磅来袭

真的是不止一次感叹,学习的速度都跟不上发论文出新品的速度。。。。。 继前文YOLOv9发布以来也就不到三个月的时间,YOLOv10就来了! 《太卷了,目标检测新成员——YOLOv9: Learning What You Want to LearnUsing Programmable Gra…

openflow协议抓包分析

1、准备实验拓扑: 在Mininet环境中创建一个简单的SDN拓扑,包括控制器、交换机、主机等。 确保拓扑能够正常运行,SDN交换机与控制器建立连接。 采用主机Ubuntu22.04主机,IP地址是192.168.87.130,安装opendaylight控制…

DreamerV3阅读笔记

DreamerV3 文章希望解决的一个挑战是用固定的hyperparameter来同时处理不同domain的任务。文章发现,通过结合KL balancing 和free bits可以使得world model learn without tuning(是指上面这件事,即不需要对不同任务改变hyperparameter&#…

力扣239. 滑动窗口最大值

Problem: 239. 滑动窗口最大值 文章目录 题目描述思路复杂度Code 题目描述 思路 1.编写实现优先队列类: 1.1.实现push(int n):将元素n添加到队列尾,同时将n前面大于n的元素删除 1.2.实现int max():将队列头元素取出(由于实现了push所以此时队…

【Python性能优化】取最值的差异

取最值的差异 测试Windows 测试结果Linux 测试结果 测试 测试内容:从一组 x, y, z 坐标值中获得每个维度(x、y、z)的值域范围。此处不考虑将数据临时存放到内存,再整组获取值域的操作(因为对单文件这么做问题不大&…

多线程基本常识

多线程的状态 在Java中,一个线程的生命周期有以下几种状态: 新建(New):当线程对象被创建时,线程处于新建状态。此时线程对象存在,但还没有调用start()方法启动线程。 运行(Runnable…

Prometheus Operator创建告警规则并接入钉钉报警

prometheus之钉钉报警 前言1. 添加prometheus报警规则1.2 添加自定义报警规则文件 2. 配置钉钉报警2.2 部署dingding插件 3. 编写alertmanager配置文件 前言 在kubenetes上安装了kube-promethues(包含Prometheus Operator),程序正常跑起来了&#xff0c…

【找出缺失的观测数据】python

思路: 主要在于分配剩余的部分分配问题 代码: class Solution:def missingRolls(self, rolls: List[int], mean: int, n: int) -> List[int]:m len(rolls)total_sum (n m) * meantoset total_sum - sum(rolls)# 检查 toset 是否在可能的范围内i…

影响所有股票、债券和ETF交易!一文看懂美国“T+1”结算新规

T1对投资者有何好处?有哪些风险?T1已经到来,T0还远吗? 美股将在本周迎来历史性时刻。 从当地时间5月28日开始,美股交易结算周期将由T2缩短至T1,即投资者当天卖出的股票,在交易后一个工作日就能…

C# Chart图表应用

1,Chart简介 Chart控件是微软自带的一种图形可视化组件,使用简单灵活。在.NET4.0之后(即VS2010之后)已集成在了VS里面,直接拖拽控件到窗体即可使用。 需要使用命名空间:using System.Windows.Forms.DataVis…

NDIS驱动程序堆栈

NDIS 6.0 引入了暂停和重启驱动程序堆栈的功能。 若要支持 NDIS 6.0 提供的堆栈管理功能,必须重写旧版驱动程序。 NDIS 6.0 还引入了 NDIS Filter驱动程序。 Filter驱动程序可以监视和修改协议驱动程序与微型端口驱动程序之间的交互。 与 NDIS 5 相比,F…

AI智能体研发之路-模型篇(四):一文入门pytorch开发

博客导读: 《AI—工程篇》 AI智能体研发之路-工程篇(一):Docker助力AI智能体开发提效 AI智能体研发之路-工程篇(二):Dify智能体开发平台一键部署 AI智能体研发之路-工程篇(三&am…

Vue中,点击提交按钮,路由多了个问号

问题 当点击提交按钮是路由多了问号: http://localhost:8100/#/ 变为 http://localhost:8100/?#/原因 路由中出现问号通常是由于某些路径或参数处理不当造成的。在该情况下,是因为表单的默认行为导致的。提交表单时,如果没有阻止表单的默…

完全背包洛谷题单

[USACO08NOV] Buying Hay S 题解:这题看到每个都可以卖出无限多个干草包,就应该想到完全背包,但又不同于普通的完全背包,普通的完全背包是让你通过对应的背包求出最大的价值,但是在这题理解上却是知道能够达到背包容量…

EPSON爱普生RTC RA8900CE/RA8000CE+松下Panasonic电池组合

RTC是一种实时时钟,用于记录和跟踪时间,具有独立供电和时钟功能。在某些应用场景中,为了保证RTC在断电或者其他异常情况下依然能够正常工作,需要备份电池方案来提供稳定的供电。本文将介绍EPSON爱普生RTC RA8900CE/RA8000CE松下Pa…

【Linux】升级GCC(版本9.3),补充:binutils

GCC:GNU Compiler Collection 。编译器,几乎Linux中所有程序(包括内核)都是gcc编译的,包括libc。 gcc不仅仅是编译器,gcc也有很多库,依赖libc。gcc和libc互相依赖。 GCC官网:GCC, …

【B站 heima】小兔鲜Vue3 项目学习笔记Day05

Day05 文章目录 Day05登录1. 整体认识和路由设置2. 表单校验实现3. 表单-统一校验4. 基础登录业务实现5. Pinia管理用户数据6. Pinia 数据持久化7. 登录和非登录状态下的模板适配8. 请求拦截器携带Token9. 退出登录功能的实现10. Token失效401拦截处理 购物车1. 流程梳理2. 本地…