MFC 程序执行流程

目录

MFC 程序启动

MFC 入口函数

程序执行流程总结


在Win32课程中WinMain由程序员自己实现,那么流程是程序员安排,但到了MFC中,由于MFC库实现WinMain,也就意味着MFC负责安排程序的流程。

MFC 程序启动

程序的启动,构造theApp对象,调用父类CWinApp的构造函数。

  • 将theApp对象的地址保存到线程状态信息中
  • 将theApp对象的地址保存到模块状态信息中
  • 进入WinMain函数,调用AfxWinMain函数

在应用类下断点,并且打开函数调用堆栈

摁下F11,CMyWinApp(){} 构造函数调用,会先调用 CMyWinApp 类的父类 CWinApp 类构造函数,在调用爷爷类 CThread

爷爷类 CThread 只是做一些初始化的操作,不做关注,直接看 CWinApp 的构造函数

这两个都是类,都定义在全局:

  1. AFX_MODULE_STATE                      当前程序模块状态信息
  2. AFX_MODULE_THREAD_STATE     当前程序线程状态信息

  //获取当前程序模块状态信息
  AFX_MODULE_STATE* pModuleState = AfxGetModuleState();

  //获取当前程序线程状态信息
  AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;

  // 将 &theApp 保存到 当前程序线程状态信息 的一个成员中
  pThreadState->m_pCurrentWinThread = this;

一个断言(assertion),用于在代码中进行调试和错误检测。在这行代码中,它检查AfxGetThread()返回的线程指针是否等于当前的线程指针this,如果不相等,则会触发断言失败,打印相关的错误信息并中断程序的执行。

ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);

 AfxGetThread()  函数 返回的为&theApp

CWinThread* AFXAPI AfxGetThread()
{
	// check for current thread in module thread state  
	AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
	CWinThread* pThread = pState->m_pCurrentWinThread;
	return pThread;
}

这两行代码看不明白?

可以在前面补上this->,发现 this 是 CMyWinApp 的对象,这两个成员变量应该是继承父类来的

获取当前线程的伪句柄,获取当前线程的ID

	m_hThread = ::GetCurrentThread();
	m_nThreadID = ::GetCurrentThreadId();

所以 AfxGetApp() 返回&theApp

	ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
	pModuleState->m_pCurrentWinApp = this;
	ASSERT(AfxGetApp() == this);

总结:

  1. CWinApp,封装了应用程序、线程等信息
  2. CMyWinApp继承自CWinApp 负责程序的运行
  3. CMyWinApp theApp;  程序就开始执行,并且调用构造函数,依次为祖宗类到本类,只需要关注父类 CWinApp 构造函数即可
  4. CWinApp::CWinApp(LPCTSTR lpszAppName),在调用时会自动传递参数并且为空,主要逻辑未带如下
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
    m_pszAppName = NULL; // 应用程序的名称 赋值为空


    
    /* 下面是初始化当前的执行线程,也就是主线程了  */
    AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();     // 获取当前程序模块状态信息
	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread; // 当前程序线程状态信息,这个类也是当前程序模块信息类的成员类                                    

	pThreadState->m_pCurrentWinThread = this;                       // 把&theapp赋值给当前程序线程信息的成员编程
	m_hThread = ::GetCurrentThread();                               // 获取当前线程的伪句柄
	m_nThreadID = ::GetCurrentThreadId();                           // 获取当前线程的id



    /* 初始化应用类状态 */
    pModuleState->m_pCurrentWinApp = this;                          // 把&theapp赋值给当前程序线程信息的成员编程
    

    /*剩下的就是初始化一些其他的成员变量*/
}

其他调用到的一些函数:

  • AfxGetModuleState() :调用此函数获取一个指针,指向AFX_MODULE_STATE
  • AfxGetThread():调用此函数获取一个指针,指向表示当前执行线程的 CWinThread 对象,&theapp
  • GetCurrentThread():获取当前线程伪句柄
  • AfxGetApp():调用此函数获取一个指针,指向AFX_MODULE_STATE,&theapp

综上所述,应用程序类的构造函数主要做一些初始化的操作
 

 

MFC 入口函数

进入入口函数WinMain

  • 获取应用程序类对象theApp的地址
  • 利用theApp地址调用InitApplication,初始化当前应用程序的数据
  • 利用theApp地址调用InitInstance函数初始化程序,在函数中我们创建窗口并显示。
  • 利用theApp地址调用CWinApp的Run函数进行消息循环
  • 如果没有消息,利用theApp地址调用OnIdle虚函数实现空闲处理
  • 程序退出利用theApp地址调用ExitInstance虚函数实现退出前的善后处理工作

给函数 InitInstance 下断点

查看调用堆栈

补充一点:应用程序句柄和窗口句柄的关系?

应用程序句柄(HINSTANCE)用于标识一个特定的应用程序实例,它通常在应用程序启动时由操作系统分配。应用程序句柄主要用于访问应用程序的资源,例如图标、位图、对话框模板等。

窗口句柄(HWND)用于标识一个窗口,它是在窗口被创建时由操作系统分配的。窗口句柄允许应用程序与窗口进行交互,比如显示、隐藏、关闭、重绘等操作。

在关系上,应用程序句柄和窗口句柄可以是相互独立的。一个应用程序实例可以拥有多个窗口,每个窗口都有自己的窗口句柄,但它们共享相同的应用程序句柄。因此,应用程序句柄通常用于全局资源的管理,而窗口句柄用于特定窗口的操作。

查看 wWinMain 函数,函数功能主要实现是交给 AfxWinMain 实现的

下面是 AfxWinMain的伪代码

这两代码都是获取&theApp

    CWinThread* pThread = AfxGetThread();
    CWinApp* pApp = AfxGetApp();

AfxWinInit 函数与初始化MFC有关

	if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
		goto InitFailure;

利用theApp对象调用应用程序类成员虚函数 初始化

	if (pApp != NULL && !pApp->InitApplication())
		goto InitFailure;

利用theApp对象调用应用程序类成员虚函数 创建并显示窗口

这就和我们在 CMyWinApp 中重写的虚函数对上了,后面的是未初始化成功的处理

    if (!pThread->InitInstance())
	{
		if (pThread->m_pMainWnd != NULL)
		{
			TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
			pThread->m_pMainWnd->DestroyWindow();
		}
		nReturnCode = pThread->ExitInstance();
		goto InitFailure;
	}

利用theApp对象调用应用程序类成员虚函数 消息循环

nReturnCode = pThread->Run();

接下来进入 Run() 函数看看

m_pMainWnd 是 CWinThread 类的一个成员变量,是指向线程的主窗口对象的指针

也就是说 if 语句块是处理错误的,return 处调用父类的Run函数

int CWinApp::Run()
{
	if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
	{
		// Not launched /Embedding or /Automation, but has no main window!
		TRACE(traceAppMsg, 0, "Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.\n");
		AfxPostQuitMessage(0);
	}
	return CWinThread::Run();
}

接下来就是真的消息循环的位置

突然看到这里有点懵,AfxGetThreadState() 函数,全局函数,调用此函数可获取指向表示当前正在执行的线程的 CWinThread 对象的指针。必须从所需的线程内调用。

_AFX_THREAD_STATE* pState = AfxGetThreadState();

检查线程消息队列中是否存在已发布的消息,

如果没有消息,做空闲处理,利用theApp对象调用应用程序类成员虚函数 空闲处理

		while (bIdle &&
			!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))
		{
			// call OnIdle while in bIdle state
			if (!OnIdle(lIdleCount++))
				bIdle = FALSE; // assume "no idle" state
		}

PumpMessage实际上调用的是  AfxInternalPumpMessage() 函数

程序结束前,利用theApp对象调用应用程序类成员虚函数 善后处理。

if (!PumpMessage())
    return ExitInstance();

当窗口收到 WM_QUIT消息,就返回会FASLE,否则返回TRUE

	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;
	}
	if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
	{
		::TranslateMessage(&(pState->m_msgCur));
		::DispatchMessage(&(pState->m_msgCur));
	}
  return TRUE;

程序执行流程总结

程序启动后,会调用构造函数初始化应用程序类的对象,以及其父类(主要)

  • 初始化 当前模块程序信息 以及 当前模块程序线程信息
  • 其他成员变量的初始化

调用 AfxWinMain 函数,执行程序流程

  • 初始化 MFC 以及 应用程序
  • 调用 InitInstance 完成窗口的注册创建显示
  • 调用爷爷类 CWinThread 的Run函数,进入消息循环

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

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

相关文章

红枣期货个股(红枣期货个股:投资方向分析)

红枣期货个股介绍 红枣是我国传统的绿色健康食品,具有营养丰富、味道独特的特点,深受消费者喜爱。红枣产业链较长,包括种植、采摘、加工、销售等环节,其中期货是红枣产业不可或缺的一环。红枣期货个股作为期货交易市场上的重要投…

mybatis中oracle的sql没走索引导致特别慢(未加jdbcType的)

如果直接跑sql是能走索引很快,在mybatis中不能,可能就是jdbcType的原因。 比如,我有一个属性A,在表里面是VARCHAR2类型,但是在mybatis中的sql是#{a},缺少jdbcTypeJdbcType.VARCHAR,就会导致myba…

risc-v system instruction

ECALL ecall 指令以前叫做 scall,用于执行环境的变更,它会根据当前所处模式触发不同的执行环境切换异常, 用来执行需要更高权限才能执行的功能;简单来说,ecall 指令将权限提升到内核模式并将程序跳转到指定的地址。操作系统内核和应用程序其实…

AD采集卡设计方案:630-基于PCIe的高速模拟AD采集卡

基于PCIe的高速模拟AD采集卡 一、产品概述 基于PCIe的一款分布式高速数据采集系统,实现多路AD的数据采集,并通过PCIe传输到存储计算服务器,实现信号的分析、存储。 北京太速科技,产品固化FPGA逻辑,适配2路…

ShardingSphere-JDBC 和 ShardingSphere-Proxy,你选择哪一个

参考文章 总结: 只使用Java,ShardingSphere-JDBC更好有异构语言的话,ShardingSphere-Proxy 更好混用也挺香

Flink系列之:监控Checkpoint

Flink系列之:监控Checkpoint 一、概览二、概览(Overview)选项卡三、历史记录(History)选项卡四、历史记录数量配置五、摘要信息(Summary)选项卡六、配置信息(Configuration&#xff…

100GPTS计划-AI动漫AnimeArtisan

地址 https://poe.com/AnimeArtisan https://chat.openai.com/g/g-LM6ObVhfF-anime-artisan 测试 风景类: 阳光、蓝天、白云、大海、海滩、森林、瀑布、山峰、雪山 日常类: 睡觉、跑步、学习、工作、做家务、看书、听音乐、运动、购物、煮饭 人物类: 女孩、男孩、老人、儿童…

『 Linux 』重新理解挂起状态

文章目录 🦄 前言新建状态 🐋挂起状态 🐋唤入唤出 🐋进程与操作系统间的联系 🐋 🦄 前言 『 Linux 』使用fork函数创建进程与进程状态的查看中提到了对挂起状态的一个理解; ​ 挂起状态相比于其…

爬虫练习-获取imooc课程目录

代码: from bs4 import BeautifulSoup import requests headers{ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0, }id371 #课程id htmlrequests.get(https://coding.imooc.com/class/chapter/id.html#Anchor,head…

kubernetes 学习笔记

1. Kubernetes 介绍 1.1 应用部署方式的演变 在部署应用程序的方式上,主要经理了三个时代: 传统部署:互联网早期,会直接将应用程序部署在物理机上。虚拟化部署:可以在一台物理机上运行多个虚拟机,每个虚…

高可用接入层技术演化及集群概述

集群概述 集群的介绍及优势 集群:将多台服务器通过硬件或软件的方式组合起来,完成特定的任务,而这些服务器对外表现为一个整体。集群的优势 高可靠性:利用集群管理软件,当主服务器故障时,备份服务器能够自…

Cesium 加载 Geoserver WMS 图层以及条件查询和切换图层样式

Cesium 加载 Geoserver WMS 图层以及条件查询和切换图层样式 图层样式核心代码完整代码:在线示例 Cesium 加载 Geoserver WMS 图层,在实际项目中常常会遇到,需要对图层进行过滤,这里介绍一下过滤方法。 Cesium Geoserver 图层条件…

AirPodsPro3爆料汇总,2025年发布?

不止是iPhone,苹果的AirPods Pro系列耳机也是非常受用户青睐的一款产品,相信不少果粉都非常期待它的升级换代。 第一代AirPods Pro于2019年10月发布,第二代AirPods Pro于2022年9月发布,按照这个时间线来看的话,第三代A…

《Linux C编程实战》笔记:进程操作之创建进程

进程是一个动态的实体,是程序的一次执行过程。进程是操作系统资源分配的基本单位。 以下是一些概念,我就直接抄书了 进程是操作系统的知识,简单理解的话,你写的代码运行起来算一个进程? 创建进程 每个进程由进程ID号…

在vue项目中,数据已经在页面渲染,但在后续操作时获取不到数据

如下图 产生这个问题的原因 异步问题 如何解决 方法一:可以将其存放在一个setTimeout里面(利用一个极小的延迟来获取数据),如下图 效果 方法二:将操作放入axios里面,如下图

昂首资本发现原油价差这样用,难怪银行这么富

难怪银行这么富,原来是发现一个稳定产生利益的投资策略。虽然这个利润可能看起来比较少。但是昂首资本需要提醒各位投资者的是:首先,这个策略几乎没有风险。第二,这是一个可以复制的投资策略。 下面昂首资本就通过原油的价差进行实…

开发信怎么写回复率高?写外贸邮件的技巧?

如何打造高回复率的开发信?有效的开发信模板推荐? 如何写一封能够引起客户兴趣并提高回复率的开发信变得至关重要。开发信是建立联系、促进销售和扩大业务的关键工具之一。蜂邮EDM将探讨一些关键策略,帮助你提高开发信的回复率,确…

爬虫图片验证码处理

图片验证码处理 目前&#xff0c;很多网站为了防止爬虫爬取&#xff0c;登录时需要用户输入验证码。下面我们学习如何在爬虫程序中识别验证码。 其中包含验证码。 页面中的验证码图片对应一个<img>元素&#xff0c;即一张图片&#xff0c;浏览器加载完登录页面后&#…

Spring 6(二)【IOC原理】

前言 IOC 是Spring的两大核心概念之一&#xff0c;它是一种思想&#xff0c;需要极其熟练的掌握。 今日摘录&#xff1a; 低能无聊的人太多。说他们勤勉&#xff0c;不过是因困为不会合理分配时间&#xff1b;说他们积极&#xff0c;不过是逃避其他困难工作而已。即便说工作只…

DNN二分类模型

import os import datetime#打印时间 def printbar():nowtime datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S)print("\n"""*8 "%s"%nowtime)#mac系统上pytorch和matplotlib在jupyter中同时跑需要更改环境变量 os.environ["KMP_DUP…