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

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 onceclass CMainFrame : public CFrameWnd
{protected: // 仅从序列化创建CMainFrame() noexcept;DECLARE_DYNCREATE(CMainFrame)// 特性
public:// 操作
public:// 重写
public:virtual BOOL PreCreateWindow(CREATESTRUCT& cs);// 实现
public:virtual ~CMainFrame();
#ifdef _DEBUGvirtual void AssertValid() const;virtual void Dump(CDumpContext& dc) const;
#endifprotected:  // 控件条嵌入成员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();//获取当前应用程序线程IDm_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.rhkb.cn/news/86414.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

进销存管理系统(小杨国贸)springboot采购仓库财务java jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 进销存管理系统&#xff08;小杨国贸&#xff09;spri…

【Linux】网络通信

【Linux】网络通信 文章目录 【Linux】网络通信1、网络基础1.1 计算机网络1.2 网络模型TCP & UDP1&#xff09;IP地址2&#xff09;端口3&#xff09;TCP协议与UDP协议的比较 1.3 网络传输1.3.1 传输逻辑1.3.2 传输条件1.3.3 传输流程 1.4 地址管理 2、网络编程2.1 基本概念…

【面试八股文】每日一题:谈谈你对集合的理解

每日一题-Java核心-谈谈你对集合的理解【面试八股文】 谈谈你对集合的理解 Java集合是Java编程语言中用于存储数据的容器。它提供了一系列的类和接口&#xff0c;用于操作和管理数据集合。Java集合框架主要包括以下几个重要的接口和类&#xff1a; List&#xff1a;List是一个有…

ubuntu 如何命令行打开系统设置(Wifi,网络,应用程序...)

关于GNOME GNOME 是一个自由、开放源代码的桌面环境&#xff0c;它运行在 Linux 和其他类 UNIX 操作系统上。它是 GNU 项目的一部分&#xff0c;旨在为 Linux 操作系统提供一个现代化、易于使用的用户界面。 GNOME 桌面环境包括许多应用程序&#xff0c;例如文件管理器、文本编…

Centos7源码安装redis

1、下载redis Index of /releases/ 2、解压redis tar -xvf redis-6.2.9.tar.gz 3、进入解压后的目录 cd redis-6.2.9/4、指定内存分配器为 libc make MALLOClibc 5、进入src目录&#xff0c;安装 cd src && make install6、运行 ./redis-server 7、添加开机…

BI技巧丨利用Index计算半累计

在实际的业务场景中&#xff0c;特别是财务模块和库存管理模块&#xff0c;经常需要我们针对每个月的期初期末进行相关指标计算&#xff0c;这也是我们之前曾经提到的Calculate基础应用——半累计计算。 现在我们也可以通过微软新推出的Index开窗函数来解决这一问题。 INDEX函…

Linux 终端操作命令(3)内部命令用法

Linux 终端操作命令 内部命令用法 A- alias NAME alias - Define or display aliases. SYNOPSIS alias [-p] [name[value] ... ] DESCRIPTION Define or display aliases. Without arguments, alias prints the list of aliases in the reusable form al…

Seata - 入门笔记

1、事务 访问并可能更新数据库中数据库中各种数据线的一个程序执行单元 原子性&#xff1a;事务是一个不可分割的工作单位&#xff0c;一个事务要么都做要么都不做 一致性&#xff1a;必须是使数据库从一个一致性到另一个一致性的状态&#xff0c;中间状态不能被观察到 隔离…

JDBC快速入门操作

一、jdbc简介 JDBC是java用于连接数据库的api&#xff0c;数据库软件有多种&#xff0c;像MySQL,SQLsever&#xff0c;Oracle等数据库&#xff0c;这些数据库都是由不同的团队开发的&#xff0c;所以相同的功能的api的名字不同&#xff0c;当一个后端工程需要切换一个数据库软件…

tomcat的多实例,动静分离(web服务基础结束)

多实例 多实例就是在一台服务器上有多个tomcat的服务&#xff08;核心是改端口&#xff09; 实验&#xff1a;多实例 安装步骤 1.安装好 jdk 2.安装 tomcat cd /opt tar zxvf apache-tomcat-9.0.16.tar.gz mkdir /usr/local/tomcat mv apache-tomcat-9.0.16 /usr/local/tomca…

ChatGPT将会成为强者的外挂?—— 提高学习能力

目录 前言 一、提高学习力 &#x1f9d1;‍&#x1f4bb; 1. 快速找到需要的知识 2. 组合自己的知识体系 3. 内化知识技能 二、提问能力❗ 三、思维、创新能力 &#x1f31f; 1. 批判性思维 1.1 八大基本结构进行批判性提问 1.2 苏格拉底的提问分类方法 2. 结构化思…

【Java可执行命令】(十八)可视化监控和管理工具 jconsole:获取 JVM的内存使用情况、线程活动、GC 行为等重要指标的可视化工具 ~

Java可执行命令之jconsole 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.2 注意事项 4️⃣ 应用场景&#x1f33e; 总结 1️⃣ 概念 jconsole 是 Java Development Kit (JDK) 自带的一款图形化监控和管理工具。它旨在提供一个简单而强大的界面&#xff0c;用于监视和管…

一百五十一、Kettle——Linux上安装的kettle8.2开启carte服务以及配置子服务器

一、目的 kettle8.2在Linux上安装好可以启动界面、并且可以连接MySQL、Hive、ClickHouse等数据库后&#xff0c;准备在Linux上启动kettle的carte服务 二、实施步骤 &#xff08;一&#xff09;carte服务文件路径 kettle的Linux运行的carte服务文件是carte.sh &#xff08;二…

大语言模型之一 Attention is all you need ---Transformer

大语言模型已经在很多领域大显身手&#xff0c;其应用包括只能写作、音乐创作、知识问答、聊天、客服、广告文案、论文、新闻、小说创作、润色、会议/文章摘要等等领域。在商业上模型即产品、服务即产品、插件即产品&#xff0c;任何形态的用户可触及的都可以是产品&#xff0c…

AWS——04篇(AWS之Amazon S3(云中可扩展存储)-02——EC2访问S3存储桶)

AWS——04篇&#xff08;AWS之Amazon S3&#xff08;云中可扩展存储&#xff09;-02——EC2访问S3存储桶&#xff09; 1. 前言2. 创建EC2实例 S3存储桶3. 创建IAM角色4. 修改EC2的IAM 角色5. 连接EC2查看效果5.1 连接EC25.2 简单测试5.2.1 查看桶内存储情况5.2.2 复制本地文件…

PHP利用PCRE回溯次数限制绕过某些安全限制实战案例

目录 一、正则表达式概述 有限状态自动机 匹配输入的过程分别是&#xff1a; DFA&#xff08;确定性有限状态自动机&#xff09; NFA&#xff08;非确定性有限状态自动机&#xff09; 二、回溯的过程 三、 PHP 的 pcre.backtrack_limit 限制利用 例题一 回溯绕过步骤 &…

Springboot整合Druid

导入依赖 <!-- druid连接--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency> #配置数据源 spring:datasource:driver…

JavaScript中的交互的方式alert,prompt,confirm的用法

一.alert的用法 1.alert 它会显示一条信息,弹出的这个带有信息的小窗口被称为模态窗。“modal” 意味着用户不能与页面的其他部分&#xff08;例如点击其他按钮等&#xff09;进行交互&#xff0c;直到他们处理完窗口。在上面示例这种情况下 —— 直到用户点击“确定”按钮。 …

信息安全:防火墙技术原理与应用.

信息安全&#xff1a;防火墙技术原理与应用. 防火墙是网络安全区域边界保护的重要技术。为了应对网络威胁&#xff0c;联网的机构或公司将自己的网络与公共的不可信任的网络进行隔离&#xff0c;其方法是根据网络的安全信任程度和需要保护的对象&#xff0c;人为地划分若干安全…

本地项目如何连接git远程仓库

在本地新建项目后&#xff0c;如何连接git远程仓库呢&#xff1f;步骤如下&#xff1a; 第一步&#xff0c; 首先我们在git上新建仓库&#xff0c;设置模板可勾选Readme文件。&#xff08;readme文件的创建是为了介绍所写代码的一些详细信息,为了之后更好的维护。&#xff09;…