Visual Studio 2022的MFC框架——theApp全局对象

 

我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下Visual Studio 2022下开发工具的MFC框架知识。 

MFC中的WinMain函数是如何与MFC程序中的各个类组织在一起的呢?MFC程序中的类是如何与WinMain函数关联起来的呢?

面对这个问题,我们来分析一下。

双击我在我的《Visual Studio 2022的MFC框架——应用程序向导》一文中的项目例子中的类视图窗口中的CMfcApp类,跳转到该类的定义文件(Mfc.h)中。可以发现CMfcApp派生于 CWinApp类,后者表示应用程序类。

我们在类视图窗口中双击该类的构造函数,就跳转到该类的源文件(Mfc.cpp)中。在CMfcApp构造函数处设置一个断点,然后调试运行Mfc程序,将发现程序首先停在CMfcApp类的构造函数处,继续运行该程序。

这时程序才进入WinMain函数,即停在先前我在我的《Visual Studio 2022的MFC框架——WinMain函数》一文中的WinMain函数中设置的断点处。

按我们过去在C/C++编程的理解中,WinMain函数是程序的入口函数。也就是说,程序运行时首先应该调用的是WinMain函数,那么为什么这里程序会首先调用CMfcApp类的构造函数呢?

看一下CMfcApp的源文件,可以发现在程序中定义了一个CMfcApp类型的全局对象:theApp。代码如下。

//唯一的 CMfcApp对象CMfcApp theApp;

MFC程序的全局变量都放置在类视图窗口中的“全局函数和变量”分支下,单击该分支即可看到程序当前所有的全局函数和变量。双击某个全局变量,即可定位到该变量的定义处。

在这个全局对象定义处设置一个断点,然后调试运行Mfc程序,将发现程序执行的顺序依次是:

1.theApp全局对象定义处

2.MfcApp构造函数

3.WinMain函数。

为了更好地解释这一过程,我们在项目解决方案下,添加一个新的“Windows控制台应用程序”项目,该项目的名称为:findwm。

接下来,在findwm.cpp文件中输入如下所示的代码。

#include "pch.h"
#include <iostream>using namespace std; int s=100;int main(){cout<<s<<endl;return 0;}

上述代码首先定义了一个int类型的全局变量s, 并给它赋了一个初值100。然后在main函数中将全局变量s的值输出到标准输出cout上。

将该项目设置为启动项目, 然后在main函数处设置一个断点, 调试运行该程序, 将会发现程序在进入main函数时, s的值已经是100了。在程序入口main函数加载时,系统就已经为全局变量或全局对象分配了存储空间,并为它们赋了初始值。

接下来,把全局变量s换成一个全局对象,看看结果如何。修改代码, 新定义一个CPoint类, 并定义该类的一个全局变量pt。

#include"pch.h"
#include<iostream>using namespace std;class CPoint
{
public:CPoint(){}
};CPoint pt;
void main()
{} 

设置三个断点:CPoint构造函数处、pt全局对象定义处和 main函数定义处。选择调试运行 main函数,将会看到程序代码执行的先后顺序。

这时我们将发现findwm程序首先到达pt全局对象定义处;继续运行程序,程序到达CPoint类的构造函数;再继续运行程序,程序到达main函数处。

由此可见,无论是全局变量,还是全局对象,程序在运行时,在加载main函数之前,就已经为全局变量或全局对象分配了内存空间。

对一个全局对象来说,此时就会调用该对象的构造函数构造该对象,并进行初始化操作。

这解释了先前创建的Mfc程序的运行顺序为什么全局变量theApp的构造函数会在 WinMain 函数之前执行。

那么,为什么要定义一个全局对象theApp,让它在WinMain函数之前执行呢?该对象的作用是什么呢?我们回到Mfc项目,并将该项目设置为启动项目。

Win32 SDK应用程序的实例是由实例句柄(WinMain函数的参数hInstance)来标识的。而对MFC程序来说,通过产生一个应用程序类的对象来唯一标识应用程序的实例。每一个MFC程序有且仅有一个从应用程序类(CWinApp)派生的类。每一个MFC程序实例有且仅有一个该派生类的实例化对象,也就是theApp全局对象。该对象就表示了应用程序本身。

还记得子类构造函数的执行过程?当一个子类在构造之前会先调用其父类的构造函数。因此theApp对象的构造函数CMfcApp 在调用之前,会调用其父类CWinApp的构造函数,从而就把我们程序自己创建的类与Microsoft 提供的基类关联起来了。CWinApp的构造函数完成程序运行时的一些初始化工作。

下面让我们看看CWinApp类构造函数的定义。像前面搜索“WinMain”函数那样,找到Microsoft提供的CWinApp类定义的源文件:appcore.cpp,并在编辑环境中打开,其中CWinApp构造函数的代码如下。

CWinApp::CWinApp(LPCTSTR lpszAppName)
{if (lpszAppName != NULL)m_pszAppName=_tcsdup(lpszAppName);elsem_pszAppName =NULL;// initialize CWinThread stateAFX_MODULE_STATE* pModulestate =_AFX_CMDTARGET_GETSTATE();ENSURE (pModulestate);AFX_MODULE_THREAD_STATE* pThreadstate =pModulestate->m_thread;ENSURE(pThreadState);ASSERT(AfxGetThread()== NULL);pThreadstate->m_pCurrentwinThread=this;ASSERT (AfxGetThread() == this);m_hThread = ::GetCurrentThread();m_nThreadID=::GetCurrentThreadId();// initialize CWinApp stateASSERT(afxCurrentWinApp == NULL);pModuleState->m_pCurrentWinApp this;ASSERT (AfxGetApp ()== this);// in non-running state until WinMainm_hInstance= NULL;m_hLangResourceDLL= NULL;m_pszHelpFilePath= NULL;m_pszProfileName= NULL;m_pszRegistryKey= NULL;m_pszExeName= NULL;m_pszAppID= NULL;m_pRecentFileList= NULL;m_pDocManager= NULL;m_atomApp= m_atomSystemTopic= NULL;m_lpCmdLine= NULL;m_pCmdInfo= NULL;m_pDataRecoveryHandler= NULL;// initialize wait cursor statem_nWaitCursorCount =0;m_hcurWaitCursorRestore= NULL;// initialize current printer statem_hDevMode= NULL;m_hDevNames= NULL;m_nNumPreviewPages =0;// initialize DAO statem_lpfnDaoTerm= NULL;// other initializationm_bHelpMode= FALSE;m_eHelpType= afxwinHelp;m_nSafetyPoolsize= 512;m_dwRestartManagerSupportFlags =0;m_nAutosaveInterval = 5 * 60 * 1000;m_bTaskbarInteractionEnabled= TRUE;// Detect the kind of OS:OSVERSIONINFO osvi;osvi.dwosversionInfoSize= sizeof (OSVERSIONINFO);#pragma warning( disable 4996)::GetVersionEx(&osvi);#pragma warning( default 4996)m_bIsWindows7 =(osvi. dwMajorVersion ==6) && (osvi. dwMinorVersion >=1)|| (osvi. dwMajorVersion >6);// Taskbar initialization:m_bComInitialized= FALSE;m_pTaskbarList= NULL;m_pTaskbarList3= NULL;m_bTaskBarInterfacesAvailable= TRUE;
}

上述CWinApp的构造函数中有这样两行代码:

pThreadState->m_pCurrentWinThread= this;pModuleState->m_pCurrentWinApp= this;

m_pCurrentWinThread 对象的类型是 CWinThread,该类是 CWinApp 的父类。

根据C++继承性原理,这个this对象代表的是子类 CMfcApp的对象,即theApp。同时,可以发现CWinApp的构造函数有一个LPCTSTR类型的形参:lpszAppName。但是我们程序中CMfcApp的构造函数是没有参数的。如果基类的构造函数带有一个形参,那么子类构造函数需要显式地调用基类带参数的构造函数。那么,为什么我们程序中的 CMfcapp构造函数没有这么做呢?

如果某个函数的参数有默认值,那么在调用该函数时可以传递该参数的值,也可以不传递直接使用默认值即可。

class CWinApp : public CWinThread
{DECLARE DYNAMIC (CWinApp)public://Constructorexplicit CWinApp(LPCTSTR lpszAppName=NULL);.....

可以看到,CWinApp构造函数的形参确实有一个默认值(NULL)。这样,在调用CWinApp类的构造函数时,就不用显式地去传递这个参数的值。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

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

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

相关文章

UWB高精度人员定位系统源码,微服务+java+ spring boot+ vue+ mysql技术开发

工业物联网感知预警体系&#xff0c;大中小企业工业数字化转型需求的工业互联网平台 工厂人员定位系统是指能够对工厂中的人员、车辆、设备等进行定位&#xff0c;实现对人员和车辆的实时监控与调度的系统&#xff0c;是智慧工厂建设中必不可少的一环。由于工厂的工作环境比较…

Linux(基础篇一)

Linux基础篇 Linux基础篇一1. Linux文件系统与目录结构1.1 Linux文件系统1.2 Linux目录结构 2. VI/VIM编辑器2.1 vi/vim是什么2.2 模式间的转换2.3 一般模式2.4 插入模式2.4.1 进入编辑模式2.4.2 退出编辑模式 2.5 命令模式 3. 网络配置3.1 网络连接模式3.2 修改静态ip3.3 配置…

优维产品最佳实践第5期:什么是持续集成?

谈到到DevOps&#xff0c;持续交付流水线是绕不开的一个话题&#xff0c;相对于其他实践&#xff0c;通过流水线来实现快速高质量的交付价值是相对能快速见效的&#xff0c;特别对于开发测试人员&#xff0c;能够获得实实在在的收益。 本期EasyOps产品使用最佳实践&#xff0c…

先进API生产力工具eqable HTTP,一站式开发调试工具推荐

简介 Reqable是什么? Regable Fiddler/Charles Postman Reqable是HTTP一站式开发调试国产化解决方案&#xff0c;拥有更便捷的体验&#xff0c;更先进的协议&#xff0c;更高效的性能和更精致的界面。 Reqable是一款跨平台的专业HTTP开发和调试工具&#xff0c;在全平台支持…

【业务功能篇83】微服务SpringCloud-ElasticSearch-Kibanan-docke安装-应用层实战

五、ElasticSearch应用 1.ES 的Java API两种方式 Elasticsearch 的API 分为 REST Client API&#xff08;http请求形式&#xff09;以及 transportClient API两种。相比来说transportClient API效率更高&#xff0c;transportClient 是通过Elasticsearch内部RPC的形式进行请求…

基于OpenCV实战(基础知识二)

目录 简介 1.ROI区域 2.边界填充 3.数值计算 4.图像融合 简介 OpenCV是一个流行的开源计算机视觉库&#xff0c;由英特尔公司发起发展。它提供了超过2500个优化算法和许多工具包&#xff0c;可用于灰度、彩色、深度、基于特征和运动跟踪等的图像处理和计算机视觉应用。Ope…

水果flstudio好用吗?中文版FL21最新版本如何下载

FL Studio21版是一款功能强大的音乐制作软件&#xff0c;广泛应用于电子音乐、流行音乐、电影配乐等领域。它提供了丰富多样的音频合成和编辑工具&#xff0c;使音乐制作变得更加灵活多样。无论是初学者还是专业音乐制作人&#xff0c;都可以通过直观的界面和丰富的音频特效来实…

论文阅读:DIN-SQL: Decomposed In-Context Learning of Text-to-SQL withSelf-Correction

NL2SQL是将自然语言转化为SQL的任务&#xff0c;该任务隶属于NLP的子任务&#xff0c;NL2SQL在AIGC时代之前&#xff0c;以seq2seq、BERT等系列的模型在NL2SQL的主流数据集上取得了不错的效果&#xff0c;2022年底&#xff0c;ChatGPT爆火&#xff0c;凭借LLM强大的逻辑推理、上…

NLP的tokenization

GPT3.5的tokenization流程如上图所示&#xff0c;以下是chatGPT对BPE算法的解释&#xff1a; BPE&#xff08;Byte Pair Encoding&#xff09;编码算法是一种基于统计的无监督分词方法&#xff0c;用于将文本分解为子词单元。它的原理如下&#xff1a; 1. 初始化&#xff1a;将…

bh002- Blazor hybrid / Maui 保存设置快速教程

1. 建立工程 bh002_ORM 源码 2. 添加 nuget 包 <PackageReference Include"BootstrapBlazor.WebAPI" Version"7.*" /> <PackageReference Include"FreeSql" Version"*" /> <PackageReference Include"FreeSql.…

设计模式(8)外观模式

一、 1、使用背景&#xff1a;降低访问复杂系统的内部子系统时的复杂度&#xff0c;简化客户端之间的接口。 2、定义&#xff1a; 为子系统中的一组接口定义一个一致的界面&#xff0c;此模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。完美地体现…

Linux 内核与架构速查

Linux 内核与架构速查 博主博客 https://blog.uso6.comhttps://blog.csdn.net/dxk539687357 本文主要记录查询 Linux 计算机的内核与架构&#xff0c; 用于下载对应架构的第三方软件。 一、介绍 如上图所示&#xff0c; 有时候我们下载一些第三方软件&#xff0c; 软件会有很…

如何快速在vscode中实现不同python文件的对比查看

总体而言&#xff1a;两种方式。一种是直接点击vscode右上角的图标&#xff08;见下图&#xff09;。 另一种方式就是使用快捷键啦“**Ctrl\**”&#xff0c;用的时候选中想要对比的python文件&#xff0c;然后快捷键就可以达到下图效果了&#xff1a; 建议大家直接使用第二…

【位运算】算法实战

文章目录 一、算法原理常见的位运算总结 二、算法实战1. leetcode面试题01.01. 判断字符是否唯一2. leetcode268 丢失的数字3. leetcode371 两整数之和4. leetcode004 只出现一次的数字II5. leetcode面试题17.19. 消失的两个数字 三、总结 一、算法原理 计算机中的数据都以二进…

JAVA switch case 穿透问题

1&#xff0c;前提 其实开发中很少会用到switch &#xff0c;一般更倾向于if-else&#xff0c; 但是最近接手的项目&#xff0c;前人写的代码都用switch &#xff0c; 但是我一直以来对switch 的理解就跟if一样&#xff0c; 然后项目运用的时候才发现这玩意居然还有穿透问题 …

14-数据结构-二叉树的创建以及前中后遍历,以及结点和叶子节点的计算(C语言)

概述&#xff1a; 二叉树&#xff0c;这里采用孩子链表存储法&#xff0c;即一个数据域和两个左右孩子指针域。随后递归进行遍历即可。在创建二叉树的时候&#xff0c;先创建各个二叉树结点&#xff08;这里的结点采用动态分配&#xff0c;因此结点为指针变量&#xff09;&…

(三)Linux中卸载docker(非常详细)

docker 卸载 使用yum安装docker 如需卸载docker可以按下面步骤操作&#xff1a; 1、停止docker服务 systemctl stop docker 2、查看yum安装的docker文件包 yum list installed |grep docker 3、查看docker相关的rpm源文件 rpm -qa |grep docker 4、删除所有安装的docke…

4.1011

目录 四次挥手中收到乱序的FIN包会如何处理&#xff1f; 在 TIME_WAIT 状态的 TCP 连接&#xff0c;收到 SYN 后会发生什么&#xff1f; 四次挥手中收到乱序的FIN包会如何处理&#xff1f; 如果FIN报文比数据包先道道客户端&#xff0c;此时FIN是一个乱序报文&#xff0c;此时…

Postgresql部署及简单操作

目录 1、介绍 2、什么是PostgreSQL 3、PostgreSQL 的特点 4、数据库定为 5、环境准备 6、编译安装 6.1 安装依赖包 6.2 下载安装包 6.3 创建用户 6.4 创建 postgresql数据目录并授权 6.5 上传压缩包并解压 6.6 编译postgresql源码 6.7 配置环境变量 6.8 初始化数…

LeetCode--HOT100题(40)

目录 题目描述&#xff1a;543. 二叉树的直径&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;543. 二叉树的直径&#xff08;简单&#xff09; 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最…