微软MFC技术中的消息循环

news2024/11/25 14:47:07

 我是荔园微风,作为一名在IT界整整25年的老兵,今天来聊聊微软MFC技术中的消息循环。

在很多windows程序中,都有如下代码:

//Win32应用程序入口函数WinMain()
int WINAPI WinMain(
  HINSTANCE hInstance,      //指向当前实例的句柄
  HINSTANCE hPrevInstance,      //指向先前实例的句柄
  LPSTR lpCmdLine,      //命令行
  int nCmdShow         //显示状态
)
{
  WNDCLASS wndclass;      //创建一个窗口类 
  wndclass. cbClsExtra=0;     //窗口类无扩展
  wndclass. cbWndExtra=0;    //窗口实例无扩展
  wndclass. hbrBackground=(HBRUSH)GetStockObject (BLACK_BRUSH);  //窗口背景为黑
  wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);    //窗口采用箭头光标
   wndclass. hIcon=LoadIcon(NULL, IDI_APPLICATION);  //窗口的最小化图标为缺省
  wndclass. hInstance=hInstance:  //当前实例句柄
  wndclass. lpfnWndProc=WinHouProc    //定义窗口处理函数
  wndclass.lpszClassName="VC++";      //窗口类为“VC++”
  wndclass.lpszMenuName=NULL;        //窗口无菜单
  wndclass.style=CS_HREDRAW|CS_PARENTDC;          //设置窗口类型
  RegisterClass(&wndclass);        //注册窗口类
 
  HWND hwnd;
  hwnd=CreateWindow("VC++","消息机制",WS_OVERLAPPEDWINDOW,0,0, 600, 400, NULL, NULL, hInstance, NULL);       //创建窗口
 
  ShowWindow(hwnd, SW_SHOWNORMAL);         //显示窗口
  UpdateWindow(hwnd);                 //更新窗口
 
  MSG msg;
  while(GetMessage 6msg, NULL,0,0))       //获取消息
  {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
   }
  return 0;
}

在上面代码的第30行中,提到了循环。只有消息不断地被循环。才会实时得到窗口的当前状态。否则按照程序的顺序执行结构,此窗口只会闪下就被关闭,根本不会等待用户输入的任何命令。因此消息循环非常必要,即使窗口不会闪一下就关,它的状态也一直是初始状态,因为没有消息的循环,窗口的当前状态没法被线程捕获。首先从设置断点、函数传递角度来看下MFC程序启动时是如何进入消息循环。

下面就分两种情况介绍消息循环机制。

非对话框程序的消息循环机制

打开我之前帖子中所建的单文档 MFC程序,名字为Message。在源文件 Message.cpp中找到下面这一行代码,并在此处设置断点。

CMessageApp theApp;

按F11键单步执行后,会发现非对话框程序的运行点进入函数顺序为如下。

第一步  CMessageApp theApp;

第二步  _tWinMain()

第三步  Afx WinMain

第四步  CMessageApp::Initlnstance()

第五步  CWinThread::Run()
  
而非对话框程序的消息循环是从 CWinThread::Run()开始。来看下 MFC自带的thrdcore.cpp源文件中包含 CWinThread::Run()函数的部分代码如下。

MFC自带程序 CWinThread::Run()函数示例:thrdcore.cpp

int CWinThread::Run ()
{
  ASSERT_VALID(this)

   BOOL bIdle=TRUE;    //空闲状态标志量
  LONG lIdleCount=0;

  for (;;)  //无限循环,一直获取和发送消息,直到接收到WM_QUIT消息
   {
      //阶段1:检查看是否可以做空闲工作
      while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
       {
         //如果空闲则呼叫OnIdle()函数
        if (!OnIdle(lIdleCount++))
            bIdle=FALSE;
       }
       //阶段2:启动消息处理
      do
       {
         //当接收WM_QUIT,则退出
         if (!PumpMessage())
             return ExitInstance();

         //当获取"正常"消息时,重新设置空闲状态量
        if (IsIdleMessage(&m_msgCur))
         {
             bIdle=TRUE;
              lIdleCount=0;
           }
       }while ( ::PeekMessage (&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
  }
  ASSERT (FALSE);
}

大家可以从自己VC++平台安装路径下找到thrdcore.cpp源文件。如“C:\ProgramFiles\Microsoft Visual Studio\VC98\MFC\SRC"

在上面代码中,第8行表示这是一个无限循环,程序会一直处于获取和发送消息的处理状态中,除非接收到WM_QUIT消息。如第22行所示。for循环中有两个阶段,下面就这两个阶段进行分析。这2个阶段是顺序执行的,总是先执行阶段1后阶段2。

(1)阶段1的while循环是判断当前线程是否空闲并且消息队列中是否有消息需要处理,如果线程当前空闲且无消息可处理,则处于空闲状态,并一直判断;否则直接进入阶段2,进行消息处理。

(2)阶段2是判断是否收到消息,如果获取消息,继续执行;否则,退出循环。而此do-while 循环中首先判断 PumpMessage()的返回值,如果获取到 WM_QUIT消息,退出程序,否则向下执行,当设置断点调试一个非对话框程序时,单步执行第22行时,会进入PumpMessage()函数体中。如下所示:

BOOL CWinThread::PumpMessage()
{
  return AfxInternalPumpMessage ();
  //此函数内容相当于老版本的PumpMessage()内容
}

单步执行到此函数体时,再按F5键继续运行程序,运行点会一直停留在此处。因此,非对话框的消息机制就是在一个无限循环中不断从消息队列中获取消息、窗口消息被发送到相应的窗口处理过程。

对话框程序的消息循环机制

同上面所讲,打开名为MFC_Message的基于对话框程序。在源文件MFC_Message.cpp中找到下面这一行代码,并在此处设置断点。

CMFC_MessageApp theApp;

单步调试该程序后,会发现对话框程序的运行点进入函数顺序与非对话框程序的不同。非对话框程序的消息循环是从CWinThread::Run()开始,而对话框程序根本不会进入CWinThread::Run()函数。因此对话框程序的运行点进入函数顺序如下。

第一步  CMFC_MessageApp    theApp;

第二步  CMFC_MessageApp:: InitInstance()

第三步  CMFC_MessageDlg dlg;

第四步  dlg.DoModal()

因为它们的应用程序对象的InitInstance()不一样。可以通过下面两段代码进行比较。

非对话框MFC程序InitInstance()函数示例:Message.cpp

BOOL CMessageApp::InitInstance()
{
  ......
  CSingleDocTemplate* pDocTemplate;     //定义一个单文档模板
  pDocTemplate=new CSingleDocTemplate(  //定义模板的各种属性
    IDR_MAINFRAME,     //菜单
     RUNTIME_CLASS (CMessageDoc),  //文档类
    RUNTIME_CLASS(CMainFrame),  //框架类
     RUNTIME_CLASS(CMessageView));  //视图类

  if( !pDocTemplate)
      return FALSE;  //如果创建不成功返回FALSE
  AddDocTemplate (pDocTemplate);      //将此模板对象加入模板中
 
  CCommandLineInfo cmdInfo;
  ParseCommandLine(cmdInfo);
 
  if ( !ProcessShellCommand (cmdInfo))
  return FALSE;      //程序启动时创建新文档失败返回FALSE
 
   m_pMainWnd->Showwindow (SW_SHOW);  //显示窗口
  m_pMainWnd->Updatewindow ();     //更新窗口
  return TRUE:
}

对话框MFC程序 Initinstance()函数例: MFC_Message.cpp

BOOL CMFC_MessageApp::InitInstance()
{
   ......
  CMEC_MessageDlg dlg;  //声明一个对话框类对象
  m_pMainWnd =&dlg;  //将其指针赋给窗口类对象
  INT_PTR nResponse =dlg.DoModal     //延模对话框,消息循环在这里开始
   ......
 
  return FALSE;
}


非对话框MFC程序InitInstance()函数示例代码中,执行完CMessageApp::InitInstance()函数,其第22行为返回正确;而对话框MFC程序InitInstance()函数示例代码执行完CMFC_MessageApp::InitInstance()函数,其第10行为返回错误。同时,设置断点进行单步调试,会发现对话框程序的消息循环发生在DoModal()函数里,如对话框MFC程序InitInstance()函数示例代码的第07行所示。而此函数中有一个RunModalLoop()函数用于消息循环。

总结

前面分别讲解了非对话框和对话框程序的消息循环,接下来总结一下消息循环是什么。下面分2个方面来对对话框和非对话MF程序进行对比分析:

(一)发生消息循环的函数位置。

非对话框MFC程序的消息循环发生在CWinThread::Run()中,而对话框MFC程序的消息循环则发生在DoModal()中。

(二)接收消息的对象。

非对话框MFC程序的消息是与窗口交换的,而对话框MFC程序的消息则是与对话框进行交互的。

提示:因为对话框不是严格意义上的窗口,所以对话框MFC程序运行时不会进入_tWinMain()函数。

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

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

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

相关文章

全链路Python环境迁移

全链路Python环境迁移 在当前的Python环境中,安装一些库以后,如果换了一套Python环境,难道再来一次不停的pip install?当然不是。 第一步,使用pip freeze(冻结)备份当前Python库的环境 pip f…

《微服务架构设计模式》第六章 使用事件溯源开发业务逻辑

内容总结自《微服务架构设计模式》 使用事件溯源开发业务逻辑 一、传统持久化技术的问题二、什么是事件溯源三、好处和弊端四、实现事件存储库五、与Saga结合六、总结 一、传统持久化技术的问题 1、对象与关系的"阻抗失调" 所谓的对象与关系的“阻抗失调”是一个古…

ThreadLocal与InheritableThreadLocal及线程池的影响

在web开发中使用了ThreadLocal本地线程存储拦截器解析的用户信息,方便在下文代码中调用,但是在springboot中使用Async开启异步操作时,就会造成,子线程无法拿到父本地线程数据。拿到一些脏数据。 1.InheritableThreadLocal 在这个…

常用技巧总结

本文总结了本人在日常工作学习中遇到的问题及其解决方法,没有固定的涉及领域 目的就是为了在下一次遇到类似问题的时候方便查找,从而快速解决问题 本文不定时更新~ 目录 Windows使用 如何实现桌面图标随意排列 文件资源管理器相关 显示隐藏文件 修改…

深度学习06-深度卷积生成对抗网络(DCGAN)

文章目录 概述原理简介专业术语零填充转置卷积 生成动漫图像算力选择数据集目录规划源代码数据源加载定义配置类定义模型 训练可视化绘制损失绘制生成器图像变化 其他项目CycleGANstargan 概述 GAN(Generative Adversarial Network)是一种生成模型&…

flutter开发实战-日志logger写入文件及print

flutter开发实战-日志logger写入文件及print 在开发中,需要日志logger写入文件,方便日后查看出现的问题。这里记录之前的实现方案。 使用的日志插件是logger 一、引入日志插件 在工程中pubspec.yaml引入logger logger: ^1.4.0二、代码实现 使用比较…

听GPT 讲K8s源代码--pkg(一)

在 Kubernetes 代码仓库中,pkg/api和pkg/apis目录都包含用于定义 Kubernetes API 对象的代码,但它们的作用略有不同。 pkg/api目录包含 Kubernetes 的旧版本 API 对象定义,这些定义在 Kubernetes 1.7 版本之前使用。这些对象定义已经过时&…

第四课:Figma 图标设计

图形绘制小技巧 绘制正圆 O shift 正方体 R shift,右侧属性面板可调整描边显示方向,描边类型; 直线 L shift 带角度的箭头 shift L ,按住 shift 键调整以 45 度角调整箭头方向,右侧属性面板可调节箭头方向和线条…

文献里的分子对接方法2

珍珠贝肉水解物中的新型抗氧化肽以及它们的抗氧化活性机制。 关于摘要: 自由基和衰老以及很多疾病都有关联。 抗氧化肽具有良好的抗氧化活性和吸收性,是抗氧化剂研究的热点之一。 这篇文献的研究中,纯化珍珠贝肉水解液、经过蛋白质组学鉴定…

大模型与端到端会成为城市自动驾驶新范式吗?

摘要: 最近可以明显看到或者感受到第一梯队的城市自动驾驶量产已经进入快车道,他们背后所依靠的正是当下最热的大模型和端到端的技术。 近期,城市自动驾驶量产在产品和技术上都出现了新的变化。 在产品层面,出现了记性行车或者称…

【python】逻辑中断(and or)

今天学习javascript的时候竟然有一个额外收获:逻辑中断。而且我实验了一下,逻辑中断同样适用于python。 0 and 2返回: 0 1 and 2返回: 2 0 and 2返回的是0,而1 and 2返回的是2。就是因为在0那里出现了逻辑中断。 解…

Git的使用以及在IDEA2022中使用Git

目录 前言 一、Git下载与安装 二、Git常用命令 1.全局设置 2.获取Git仓库-在本地初始化Git仓库 3.获取Git 仓库-从远程仓库克隆 4.Git工作区、暂存区、版本库 概念以及工作区中文件状态 (1)工作区、暂存区、版本库 (2) G…

centos7.9 rc.local启动失败

最近在虚拟机上整个centos7.9,想要把程序设置成开机自启动,发现rc.local状态是failed,重启服务器也没用。 自己之前写过两篇相关的博客 centos7重启后/etc/rc.local中的脚本没有执行_lanren312的博客-CSDN博客 centos7开机运行java的sh脚本…

Sui 8192:如何使用Sui对象撼动游戏领域

Ethos通过其Sui 8192游戏不仅展示了对象在Sui上的力量,还展示了基于对象的游戏如何有力地撼动游戏行业。每个玩家在移动游戏中的方块时都会铸造一个新的对象,类似于铸造NFT,这为每个独立玩过的游戏提供了永久上链的机会。 Sui 8192游戏的规则…

10.4.4 终端机的环境设置: stty, set

在 tty1 ~ tty6 这六个命令行的终端机(terminal) 环境中登陆,登陆的时候我们可以取得一些字符设置的功能。举例来说,我们可以利用倒退键 (backspace,就是那个←符号的按键) 来删除命令列上的字符…

强化学习模型

目录 引言 1 强化学习的理论基础 2 强化学习的实践 3实战案例:自动驾驶

【InnoDB 存储引擎】InnoDB 数据页格式(详细版,数据页格式对于理解索引详细的原理很重要)

文章目录 1 InnoDB 数据页结构1.1 File Header2.2 Page Header2.3 Infimum 和 Supremum Record2.4 User Record 和 Free Space2.5 Page Directory(InnoDB 数据页结构最重要的部分)2.6 File Trailer 2 参考资料 1 InnoDB 数据页结构 我们已经知道页是 In…

PS 2023 24.7 Beta Ai 如何解决橙色错误弹窗问题?

距离Adobe软件公司首次将图像编辑及数字绘画软件Photoshop推出到大众面前已经过去35年,最近该公司又再次书写了属于Photoshop的历史新篇章。 Adobe 发布的 Photoshop(Beta)新增「创意填充(Generative Fill)」功能&…

jmter连接mysql数据库取值

测试情况下需要大量的测试数据进行模拟测试,如何使用接口插入大量数据,使用jmter进行插入 步骤如下: 第一步:\lib\ext导入mysql-connector-java-5.1.44-bin.jar驱动 F:\TOOLS\apache-jmeter-5.4.1\apache-jmeter-5.4.1\lib\ext …

Vue的入门学习

Vue 1 Vue概述 通过我们学习的htmlcssjs已经能够开发美观的页面了,但是开发的效率还有待提高,那么如何提高呢?我们先来分析下页面的组成。一个完整的html页面包括了视图和数据,数据是通过请求 从后台获取的,那么意味…