【MFC】05.MFC六大机制:程序启动机制-笔记

news2025/2/24 14:43:42

MFC程序开发所谓是非常简单,但是对于我们逆向人员来说,如果想要逆向MFC程序,那么我们就必须了解它背后的机制,这样我们才能够清晰地逆向出MFC程序,今天这篇文章就来带领大家了解MFC的第一大机制:程序启动机制:

首先,我们创建一个单文档架构程序,我们来观察一下:

这里我创建的项目名称为:MFCApplication1

我们发现一共有三个类:(这里有一些是MFC自动为我们写好的类,类名称通常为项目名称,继承了MFC库中的一些类,我们只看MFC库中的类)我们发现大致三个类:

CFrame类:这个类是框架窗口类,封装了框架窗口的操作

CWinApp类:这个类是应用程序类,封装了流程的操作

CDocument类:这个类是文档类,封装了数据的处理,例如存储,转换等

CView类:这个类是视图类,封装了视图窗口的操作

我们主要讲解的是CFrame类和CWinApp类

这里我创建好的项目,我们先来看看部分源代码:

CMFCApplication1.h:


// MFCApplication1.h: MFCApplication1 应用程序的主头文件
//
#pragma once

#ifndef __AFXWIN_H__
	#error "include 'pch.h' before including this file for PCH"
#endif

#include "resource.h"       // 主符号


// CMFCApplication1App:
// 有关此类的实现,请参阅 MFCApplication1.cpp
//

class CMFCApplication1App : public CWinApp
{
public:
	CMFCApplication1App() noexcept;


// 重写
public:
	virtual BOOL InitInstance();
	virtual int ExitInstance();

// 实现
	afx_msg void OnAppAbout();
	DECLARE_MESSAGE_MAP()
};

extern CMFCApplication1App theApp;

MianFrm.h:


// MainFrm.h: CMainFrame 类的接口
//

#pragma once

class CMainFrame : public CFrameWnd
{
	
protected: // 仅从序列化创建
	CMainFrame() noexcept;
	DECLARE_DYNCREATE(CMainFrame)

// 特性
public:

// 操作
public:

// 重写
public:
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

// 实现
public:
	virtual ~CMainFrame();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif

protected:  // 控件条嵌入成员
	CToolBar          m_wndToolBar;
	CStatusBar        m_wndStatusBar;

// 生成的消息映射函数
protected:
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	DECLARE_MESSAGE_MAP()

};

看完这部分源码之后,我们来自己实现,不用MFC自动帮我们实现的类,来加深我们的理解:

我们创建一个控制台应用,修改项目属性:

  1. 常规:MFC的使用:在静态库中使用MFC
  2. 连接器->系统->子系统:窗口

接下来,我们删除掉main.cpp中的所有代码,我们来自己仿照MFC写一个窗口:

#include <afxwin.h>

class CMyFrameWnd:public CFrameWnd{
public:
}

class CMyApp:public CWinApp{
public:
  CMyApp(){};
  //必须要重写虚函数:
  virtual BOOL InitInstance(){
    CMyFrameWnd* pFrame = new CMyFrameWnd;
    pFrame->Create(NULL,L"FirstMFC");
    m_pMainWnd = pFrame;
    pFrame.ShouWindow(SW_SHOW);
    pFrame->UpdateWindow();
    return TRUE;
  }
};

CMyWinApp theApp;

这里的CMyFrameWnd由于只是一个框架类,我们不需要做任何操作,只是继承MFC的框架类就可以了,狗仔函数会调用CFrameWnd中写好的构造函数

接下来我们创建一个自己的应用程序类,继承MFC的CWinApp类,这里我们写一个空的构造函数,当构造的时候,就会调用父类的构造函数了

我们在自己的应用程序类中还必须重写虚函数InitInstance,在这个函数中,我们创建窗口,显示窗口

最后,我们还需要一个应用程序类全局变量theApp

当我们写好这些之后,我们运行,发现窗口已经神奇的创建好了:

在这里插入图片描述

我们在创建Win32应用程序的时候,需要做的几个步骤:

  1. 定义窗口类,创建窗口
  2. 注册窗口
  3. 刷新窗口
  4. 显示窗口

但是在我们这样实现了MFC之后,我们貌似少做了很多操作,几行代码就完成了窗口的创建,甚至我们连WinMain函数都没有看见,那么MFC的程序启动机制到底是怎样的呢?我们首先就来深究一下MFC的程序运行机制:

我们知道,在C++中,全局变量的构造是优先于main函数的,那么我们少做的很多操作,肯定就是在这里全局变量的构造函数中了

我们在我们自己的应用程序类的构造函数上下断点,我们跟到CWinApp的构造函数中来看看:

这里我就写伪代码了:

我们应用程序类的父类:CWinApp的构造函数:
CWinApp::CWinApp(LPCTSTR lpszAppName){
  //首先判断lpszAppName是否为空,做了一些操作,我们没必要关注
  
  //AFX_MODULE_STATE这个结构体是MFC中的 程序模块状态信息结构体
  //这里是获取当前应用程序模块状态信息
  AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
  //这里是获取当前程序线程状态信息,可以看出来,当前程序状态信息是模块状态信息中的一个成员
	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
	//这里是给线程信息的某个成员赋值,给的是this指针,也就是theApp的地址
	pThreadState->m_pCurrentWinThread = this;
	
	
	//这里是给theApp的成员赋值,我们自己的应用程序类没有写成员,这里都是CWinApp中写好的,我们只是继承过来了
	//获取当前应用程序线程句柄
	m_hThread = ::GetCurrentThread();
	//获取当前应用程序线程ID
	m_nThreadID = ::GetCurrentThreadId();
	
	//给当前应用程序模块状态信息的某个成员赋值
	//不难看出这里保存的是当前应用程序类的地址,也就是theApp的地址
	pModuleState->m_pCurrentWinApp = this;
	
	
	//之后就是一些给类中的其他成员赋初值,很多都是NULL,我们不再关注
	
}

但是跟完构造函数后,我们也没有找到WinMain,更没有找到注册窗口等操作

既然在应用程序类的构造中我们没有找到,我们来看看接下来我们写的pFrame->Create(NULL, L"FirstMFC");这一段代码,但是这一段代码很明显是框架类的方法,我们猜测很多操作就在这里完成

我们在这一段代码上下断点,断下来之后,我们F11跟进,然后查看调用对战,我们就发现了wWinMian和AfxWInMain,由于我们写过win32程序,我们知道底层就是wWinMain函数,我们就跟到这个函数中来看:

这里还是写伪代码

_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	_In_ LPTSTR lpCmdLine, int nCmdShow)
{
  //我们发现在wWinMain函数中,做了转发,而且根据名称来看,是MFC的wWimMain函数,我们跟进去看看:
	//这里注意,有些人到这里就不会往进去跟了,我们在这行代码上下断点,然后去掉之前的断点,重新调试,就能跟进去了
	return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow){
	  	//这里根据名称来看,是获取当前应用程序线程信息,我们跟进去看看:
	  	CWinThread* pThread = AfxGetThread(){
	  	    //这里跟前面的应用程序类的构造函数中一样,获取当前应用程序的模块状态信息
          AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
          //取出得到的模块状态信息中的成员->当前线程
        	CWinThread* pThread = pState->m_pCurrentWinThread;
        	//返回线程
        	return pThread;
	  	}
	  	
    	//这里根据名称来看,是获取了当前应用程序的应用程序类的地址,我们还是跟进去看看
    	CWinApp* pApp = AfxGetApp(){
    	  //这里afx开头的函数
    	  return afxCurrentWinApp{
    	    //这里可以肯定,是返回了应用程序类地址
    	    return pResult = _afxBaseModuleState.GetData();
    	  }
    	}
    	
    	这里这两步跟完之后,大家有没有发现,这里实现了C++的多态?
    	CWinThread,是爷类指针,指向派生类类对象
    	CWinApp是父类的指针,指向了派生类对象,theApp是我们自己实现的应用程序类嘛
    	
    	//下一步:执行了这个函数,在源代码中,是在if的条件中实现的,这里直接拉出来了
    		AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow){
    		  //根据名称,这个是初始化的函数
    		  		pApp->m_hInstance = hInstance;
          		hPrevInstance; // Obsolete.
          		pApp->m_lpCmdLine = lpCmdLine;
          		pApp->m_nCmdShow = nCmdShow;
          		pApp->SetCurrentHandles();
    		}
    		
    		程序启动初始化操作 虚函数我们可以重写,貌似是关于文档的一些初始化操作
    		pApp->InitApplication()
    	
      	//接下来这个函数也是在if条件中进行的,这里直接拉出来
      	pThread->InitInstance(){
      	  这是虚函数,我们在我们自己的类中已经实现
      	}
	}
}

代码跟到这里,我们已经解决了很多前面我们提出来的问题:WinMain函数等但是还有一个问题:消息循环呢?我们知道,如果没有消息循环,那么这个窗口根本运行不了,那既然我们能够关闭窗口等操作,那肯定是已经写好了消息循环,我们来接着上面的代码来跟:

上面的函数紧接着,我们就看到了一行代码:
这就是MFC的消息循环,我们来跟进去看一下:
pThread->Run(){
  //这里也相当于做了转发,在转发之前有一些判断,这里省略
  int CWinApp::Run(){
    //跟到这里之后,我们就发现了消息循环:
    	for (;;)//死循环
    	{
    	  //这里的PeekMessage是到消息列表中检查是否有消息
    		while (bIdle &&!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))
    		{
    		  //这里是说:如果没有消息的话,就会进行空闲操作,比如刷新窗口,检查定时器等等
    			if (!OnIdle(lIdleCount++))
    				bIdle = FALSE;
    		}
    		//死循环里面还有一个do——while循环,我们来看看:
    		do
    		{
    		  //这里,如果是WM_QUIT消息,才会返回false,才会进去退出程序
    			if (!PumpMessage(){//在这个PumpMessage中,发现了翻译消息,派发消息
    			  	if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
              	{
              		::TranslateMessage(&(pState->m_msgCur));
              		::DispatchMessage(&(pState->m_msgCur));
              	}
    			}
    			)
    				return ExitInstance();
    			if (IsIdleMessage(&(pState->m_msgCur)))
    			{
    				bIdle = TRUE;
    				lIdleCount = 0;
    			}
    
    		} while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));
    	}
    }
}

至此,我们的消息循环就跟踪完了,这里,再总结一下MFC的程序启动机制:

  • 首先是theApp,也就是我们自己的窗口类(CMyApp)的构造,也就是CWinApp的构造,在构造函数中:
    1. 将theApp的地址,保存到了当前应用程序线程状态信息中
    2. 将theApp的地址,保存到了当前应用程序的模块状态信息中
  • 然后进入了WinMain函数,在WinMain函数中:
    1. 利用全局函数AfxGetThread()获取到了theApp的地址
    2. 利用theApp调用虚函数 InitApplication,进行文档类的一些初始化操作
    3. 利用theApp调用虚函数 InitInstance,也就是我们重写的虚函数,创建窗口
    4. 利用theApp调用虚函数 Run 消息循环
    5. 在消息循环中,我们跟踪到了获取消息,翻译消息,派发消息等
    6. 利用TheApp调用退出函数

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

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

相关文章

YOLOv5 、YOLOv8改进 :SimAM:无参数的注意力机制

在本文中&#xff0c;我们提出了一个用于卷积神经网络的概念简单但非常有效的注意模块。与现有的通道关注模块和空间关注模块相比&#xff0c;我们的模块无需向原始网络添加参数&#xff0c;而是在一层中推断特征图的3-D关注权重。具体来说&#xff0c;我们基于一些著名的神经科…

YOLOv5改进系列(20)——添加BiFormer注意力机制(CVPR2023|小目标涨点神器)

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制 YOLOv5改进系列(2)——添加CBAM注意力机制

途乐证券-美股突然跳水400点,美联储释放重磅信号

一份重磅数据来袭。 北京时间8月10日晚间&#xff0c;美国劳工部发布的7月CPI通胀数据显现&#xff0c;美国7月CPI同比涨幅从6月的3%加速至3.2%&#xff0c;为2022年6月以来首次加速上升&#xff0c;但低于预期的3.3%。美国总统拜登榜首时间表明&#xff0c;美国在通胀方面取得…

从零开始搭建个人博客网站(hexo框架)

1.工具及环境搭建 1&#xff09;注册GitHub并且新建一个repositories 2&#xff09;下载node.js以及Git 下载链接&#xff1a; 检验安装是否成功&#xff1a; 【注】&#xff1a;MacOS自带Git&#xff0c;可以直接在终端输入git --version进行检验 3&#xff09;新建一个…

Debian/Ubuntu清理硬盘空间

Debian/Ubuntu清理硬盘空间_debian清理磁盘空间_weixin_43606319的博客-CSDN博客 1. 删除残余的配置文件 通常Debian/Ubuntu删除软件包可以用两条命令 sudo apt-get remove <package-name> sudo apt-get purge <package-name> remove将会删除软件包&#xff0…

Linux Day08

内存申请与释放 前面的内存为实际内存&#xff0c;后面的交换空间为虚拟内存 当申请空间小于等于内存时&#xff0c;先使用内存。 当申请空间d大于内存时&#xff0c;使用内存虚拟内存 1、判断依据 申请1个G的空间 #include<stdio.h> #include<stdlib.h> #inc…

【redis 3.2 集群】

目录 一、Redis主从复制 1.概念 2.作用 2.1 数据冗余 2.2 故障恢复 2.3 负载均衡 2.4 高可用 3.缺点 4.流程 4.1 第一步 4.2 第二步 4.3 第三步 4.4 第四步 5.搭建 5.1 主 5.2 从 6.验证 二、Reids哨兵模式 1.概念 2.作用 2.1 监控 2.2 自动故障转移 2.…

Unreal DataTable使用

目的&#xff1a;在多个地方使用同一份结构体配置 C定义结构体 USTRUCT(BlueprintType) struct FXXX : public FTableRowBase {GENERATED_BODY()UPROPERTY(EditAnywhere, BlueprintReadWrite, Category "XXX")float XXX; }注意&#xff1a; 类的元数据加上 Bluep…

如何在docker部署一个python项目

导语&#xff1a; 我之前已经实现了在服务器上直接部署一个文件&#xff0c;但是那种部署方式有个明显的缺陷&#xff1a;我如果需要在其他机器部署该项目时&#xff0c;需要重新配置项目所依赖的环境。因此我们需要一种只需要配置一次环境依赖&#xff0c;就可以在其他机器上随…

KubeSphere 部署 Zookeeper 实战教程

前言 知识点 定级&#xff1a;入门级如何利用 AI 助手辅助运维工作单节点 Zookeeper 安装部署集群模式 Zookeeper 安装部署开源应用选型思想 实战服务器配置(架构 1:1 复刻小规模生产环境&#xff0c;配置略有不同) 主机名IPCPU内存系统盘数据盘用途ks-master-0192.168.9.9…

【类和对象】收尾总结

目录 一、初始化列表 1.格式要求 (1) 初始化列表初始化 ①括号中是初始值 ②括号中是表达式 (2) 初始化列表和函数体混用 2.特点 ①初始化时先走初始化列表&#xff0c;再走函数体 ②拷贝构造函数属于特殊的构造函数&#xff0c;函数内也可以使用初始化列表进行初始化 …

YOLOv5-7.0实例分割+TensorRT部署

一&#xff1a;介绍 将YOLOv5结合分割任务并进行TensorRT部署&#xff0c;是一项既具有挑战性又令人兴奋的任务。分割&#xff08;Segmentation&#xff09;任务要求模型不仅能够检测出目标的存在&#xff0c;还要精确地理解目标的边界和轮廓&#xff0c;为每个像素分配相应的…

使用Git进行项目版本控制

1、什么是Git&#xff1f; GIT&#xff0c;全称是分布式版本控制系统&#xff0c;git通常在编程中会用到&#xff0c;并且git支持分布式部署&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。分布式相比于集中式的最大区别在于开发者可以提交到本地&#xff0c…

SOLIDWORKS参数化设计表方法

客户痛点&#xff1a;随着人力资源价格的增长&#xff0c;设计人员不足&#xff0c;需要3D建模的数量多&#xff0c;为方便后续的CAM程序。 数据问题&#xff1a;之前是使用二维图纸&#xff0c;标准件/非标准件产品简单&#xff0c;都是单件&#xff0c;图纸发放以二维方式&a…

【C++标准模板库STL】map, unordered_map, set, unordered_set简介与常用函数

文章目录 map是STL中的标准容器&#xff0c;以键值对的形式存储&#xff0c;即为哈希表&#xff0c;并且是有序的unordered_map也是表示哈希表的容器&#xff0c;但是没有顺序&#xff0c;unordered_map查询单个key的时候效率比map高&#xff0c;但是要查询某一范围内的key值时…

【LeetCode每日一题】——128.最长连续序列

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 哈希表 二【题目难度】 中等 三【题目编号】 128.最长连续序列 四【题目描述】 给定一个未…

[保研/考研机试] KY56 数制转换 北京大学复试上机题 C++实现

题目链接&#xff1a; 数制转换https://www.nowcoder.com/share/jump/437195121691734210665 描述 求任意两个不同进制非负整数的转换&#xff08;2进制&#xff5e;16进制&#xff09;&#xff0c;所给整数在long所能表达的范围之内。 不同进制的表示符号为&#xff08;0&a…

正则表达式试炼

我希望在这里列出我很多想写的正则表达式&#xff0c;很多我想写&#xff0c;但是不知道怎么写的。分享点滴案例。未来这个文章会越来越长 案例 我有这样的一批文字&#xff0c;我需要删掉Mozilla/5.0前面的所有内容&#xff0c;如果可以用正则表达式批量匹配到&#xff0c;删…

面向数据科学家的懒惰Python 库

你今天感到昏昏欲睡吗&#xff1f;使用这五个库来提高您的工作效率。 一、介绍 数据科学既鼓舞人心&#xff0c;又具有挑战性。通过绘制各种图表以及微调模型以获得最佳结果来执行数据预处理和清理并从数据中生成见解是相当费力的。 在这篇博客中&#xff0c;我将向您介绍五个 …

YOLO v8目标跟踪详细解读(一)

在此之前&#xff0c;我们已经对yolo系列做出了详细的探析&#xff0c;有兴趣的朋友可以参考yolov8等文章。YOLOV8对生态进行了优化&#xff0c;目前已经支持了分割&#xff0c;分类&#xff0c;跟踪等功能&#xff0c;这对于我们开发者来说&#xff0c;是十分便利。今天我们对…