真正理解微软Windows程序运行机制——窗口机制(第三部分)

news2024/12/26 10:52:21

我是荔园微风,作为一名在IT界整整25年的老兵,今天说说Windows程序的运行机制。经常被问到MFC到底是一个什么技术,为了解释这个我之前还写过帖子,但是很多人还是不理解。其实这没什么,我在学生时代也被这个问题困绕过。而且那个时间学习资料没有那么丰富,网上也没有什么资料,周围也没有懂的人,那个时候理解MFC更困难。甚至在我看来,理解这个比理解人工神经网络更难。

我认为造成这种现象的根本原因就是没有搞清楚Windows程序的运行机制,因为不理解Windows程序的运行机制,所以给理解MFC带来了很大的困难。我决定带所有微软开发技术的初学者一起攻破这个问题,但是一篇文章肯定是讲不清楚的,我们要分好几章来说。需要你有足够的耐心,一起来吧。我们这次来搞清楚什么是Windows程序的窗口机制。

下面我们来创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序实现的步骤我们分三个部分,分别用三篇文章来讲清楚。

第一部分我们先写出WinMain()函数的定义,然后创建一个窗口。第二部分我们再通过消息循环来处理消息,最后第三部分我们编写窗口过程函数。为什么最后要编写窗口过程函数,主要是为了处理传过来的消息,这个就是我们今天要讲的内容。只有以上三个部分完成了,我们就真正能做到创建一个窗口,并在该窗口中响应键盘及鼠标消息,不然少一步都做不到。

现在我们就来介绍如何编写窗口过程函数

在完成之前两篇帖子的内容后,剩下的工作就是编写一个窗口过程函数,用于处理发送给窗口的消息。一个 Windows应用程序的主要代码部分就集中在窗口过程函数中。在 MSDN中可以查到窗口过程函数的声明形式,如下所示:


  LRESULT
  CALLBACK WindowProc(
  HWND hwnd,
  11
  handle to window
  UINT uMsg,
  11
  message identifier
  WPARAM WParam,
  11
  first message parameter
  LPARAM IParam
  11
  second message parameter
  );


窗口过程函数的名字可以随便取,如WinSunProc,但函数定义的形式必须和上述声明的形式相同。
  提示:系统通过窗口过程函数的地址(指针)来调用窗口过程函数,而不是名字。
  WindowProc函数的4个参数分别对应消息的窗口句柄、消息代码、消息代码的两个附加参数。一个程序可以有多个窗口,窗口过程函数的第1个参数hwnd就标识了接收消息的特定窗口。
  在窗口过程函数内部使用switch/case i语句来确定窗口过程接收的是什么消息,以及如何对这个消息进行处理。我们看下面的代码:
  WinMain.cpp
  1. LRESULT CALLBACK WinSunProc(
  2.
  HWND hwnd,
  // handle to window
  3.
  UINT uMsg,
  //
  message identifier
  4.
  WPARAM wParam,
  //
  first message parameter
  5.
  LPARAM IParam
  //
  second message parameter
  6.)
  7.{
  8.
  switch(uMsg)
  9.
  {
  10.
  case WM CHAR:
  11.
  char szChar[20];
  12.
  sprintf_s(szChar, sizeof(szChar), "char code is &d", wParam);
  13.
  MessageBox(hwnd, szChar, "char",0);
  14.
  break;
  15.
  case
  WM LBUTTONDOWN:
  16.
  MessageBox(hwnd,"mouse clicked","message",O);
  17.
  HDC hdc;
  18.
  hdc=GetDC(hwnd);
  19.
  TextOut (hdc,0,50,"程序员之家",strlen("程序员之家") ;
  20.
  ReleaseDC(hwnd,hdc);
  21.
  break;
  22.
  case
  WM PAINT:
  23.
  HDC hDC;
  24.
  PAINTSTRUCT ps;
  25.
  hDC=BeginPaint(hwnd, &ps);
  26. TextOut(hDC,0,0,"http://www.phei.com.cn", strlen("http://www.phei.com.cn");
  27.
  EndPaint(hwnd,&ps);
  28.
  break;
  29.
  case
  WM CLOSE:
  30.
  if (IDYES=MessageBox (hwnd,"是否真的结束?","message", MB_YESNO)31.
  {
  32.
  DestroyWindow(hwnd);

 33.
  }
  34.
  break;
  35.
  case
  WM DESTROY:
  36.
  PostQuitMessage(0);
  37.
  break;
  38.
  default:
  39.
  return DefwindowProc(hwnd, uMsg, wParam,1Param);
  40.
  }
  41.
  return
  0;
  42.}
  10~14行代码:当用户在窗口中按下一个字符键,程序将得到一条WM_CHAR消息(通过调用 TranslateMessage函数转换得到),在其 wParam 参数中含有字符的ASCII码值。
  char
  MessageBox函数(其用法请读者查看MSDN,并结合本章程序来学char code is 97
  习)弹出一个包含了显示信息的消息框,如果我们按下字母“a”键(注意大小写),程序将弹出如图2.4所示的消息框。
  确定
  15-21行代码:当用户在窗口中按下鼠标左键时,将产生WM图2.4消息框
  LBUTTONDOWN 消息。为了证实这一点,我们在 WM LBUTTONDOWN消息的响应代码中,调用MessageBox函数弹出一个提示信息,告诉用户“点击了鼠标”。接下来,我们在窗口中(0,50)的位置处输出一行文字。要在窗口中输出文字或者显示图形,需要用到设备描还表(Device Context),简称DC。DC.是一个包含设备(物理输出设备,如显示器,以及设备驱动程序)信息的结构体,在Windows平台下,所有的图形操作都是利用DC来完成的。
  关于 DC,我们可以用一个形象的比喻来说明它的作用。现在有一个美术老师,他让他的学生画一幅森林的图像,有的学生采用素描,有的学生采用水彩画,有的学生采用油画,每个学生所画的图都是森林,然而表现形式却各不相同。如果让我们来画图,老师指定了一种画法(例如用水彩画),我们就要去学习它,然后才能按照要求画出图形。如果画法(工具)经常变换,我们就要花大量的时间和精力去学习和掌握画法。在这里,画法就相当于计算机中的图形设备及其驱动程序。我们要想画一幅图,就要掌握我们所用平台的图形设备和它的驱动程序,调用驱动程序的接口来完成图形的显示。不同图形设备的驱动程序是不一样的,对于程序员来说,要掌握各种不同的驱动程序,工作量就太大了。因此,Windows就给我们提供了一个DC,让我们从学生的角色转变为老师的角色,只要下命令去画森林这幅图,由DC去和设备驱动程序打交道,就能完成图形的绘制。至于图形的效果,就要由所使用的图形设备来决定了。对于老师来说,只要画出的是森林图像就可以了。对于程序员来说,充当老师的角色,只需要获取DC(DC也是一种资源)的句柄,利用这个句柄去作图就可以了。
  使用DC,程序不用为图形的显示与打印输出做分别处理了。无论是显示,还是打印,都直接在DC上操作,然后由DC映射到这些物理设备上。
  第17行代码:定义了一个类型为HDC的变量hdc。
  第18行代码:用hdc保存GetDC函数返回的与特定窗口相关联的DC的句柄。为什么DC要和窗口相关联呢?想像一下,我们在作图时,需要有画布,而利用计算机作图,24

第2章Windows程序内部运行机制
  窗口就相当于画布,因此,在获取DC的句柄时,总是和一个指定的窗口相关联。
  第19行代码:TextOut函数利用得到的DC句柄在指定的位置(x坐标为0,y坐标为第20行代码:在执行图形操作时,如果使用GetDC函数来得到DC的句柄,那么在50)输出一行文字。
  完成图形操作后,必须调用ReleaseDC函数来释放DC所占用的资源,否则会引起内存泄漏。
  第22~28行代码:对WM_PAINT消息进行处理。当窗口客户区的一部分或者全部变为“无效”时,系统会发送WM_PAINT消息,通知应用程序重新绘制窗口。当窗口刚创建的时候,整个客户区都是无效的。因为这个时候程序还没有在窗口上绘制任何东西,当调用 UpdateWindow 函数时,会发送 WM PAINT消息给窗口过程,对窗口进行刷新。当窗口从无到有、改变尺寸、最小化后再恢复,窗口的客户区都将变为无效,此时系统会给应用程序发送WM_PAINT消息,通知应用程序重新绘制。
  提示:窗口大小发生变化时是否发生重绘,取决于WNDCLASS结构体中style成员是否设置了CS_HREDRAW和CS_VREDRAW标志。
  第25行,调用BeginPaint函数得到DC的句柄。BeginPaint函数的第1个参数是窗口的句柄,第二个参数是PAINTSTRUCT结构体的指针,该结构体对象用于接收绘制的信息。
  在调用 BeginPaint时,如果客户区的背景还没有被擦除,那么BeginPaint 会发送WM_ERASEBKGND消息给窗口,系统就会使用 WNDCLASS结构体的 IhbrBackground成员指定的画刷来擦除背景。
  第26行,调用TextOut函数在(0,0)的位置输出一个网址“http:/www.phei.com.cn"。
  当发生重绘时,窗口中的文字和图形都会被擦除。在擦除背景后,TextOut函数又一次执行,在窗口中再次绘制出“http:/www.phei.com.cn”。这个过程对用户来说是透明的,用户并不知道程序执行的过程,给用户的感觉就是你在响应 WM PAINt消息的代码中输出的文字或图形始终保持在窗口中。换句话说,如果我们想要让某个图形始终在窗口中显示,就应该将图形的绘制操作放到响应WM_PAINT消息的代码中。
  那么系统为什么不直接保存窗口中的图形数据,而要由应用程序不断地进行重绘呢?
  这主要是因为在图形环境中涉及的数据量太大,为了节省内存的使用,提高效率,而采用了重绘的方式。
  在响应 WM PAINT消息的代码中,要得到窗口的 DC,必须调用 BeginPaint函数。
  BeginPaint 函数也只能在 WM_PAINT 消息的响应代码中使用,在其他地方,只能使用GetDC来得到DC的句柄。另外, BeginPaint 函数得到的DC,必须用 EndPaint函数去释放。
  29~34行代码:当用户单击窗口上的关闭按钮时,系统将给应用程序发送一条WM_CLOSE消息。在这段消息响应代码中,我们首先弹出一个消息框,让用户确认是否结束。如果用户选择“否”,则什么也不做;如果用户选择“是”,则调用DestroyWindow函数销毁窗口,DestroyWindow 函数在销毁窗口后会向窗口过程发送 WM_DESTROY 消息。注意,此时窗口虽然销毁了,但应用程序并没有退出。有不少初学者错误地在WM_DESTROY消息的响应代码中提示用户是否退出,而此时窗口已经销毁了,即使用户选择不退出,也没有什么意义了。所以如果你要控制程序是否退出,应该在WM_CLOSE消息的响应代码中完成。
  25


对WM CLOSE消息的响应并不是必须的,如果应用程序没有对该消息进行响应,系统将把这条消息传给 DefWindowProc函数(参见第39行),那么 DefWindowProc函数则调用 DestroyWindow 函数来响应这条 WM_CLOSE消息。
  35-37行代码:DestroyWindow函数在销毁窗口后,会给窗口过程发送 WM_DESTROY消息,我们在该消息的响应代码中调用PostQuitMessage函数(第36行)。PostQuitMessage函数向应用程序的消息队列中投递一条WM_QUIT消息并返回。我们在第2.4.3节介绍过,GetMessage函数只有在收到 WM_QUIT消息时才返回 0,此时消息循环才结束,程序退出。
  要想让程序正常退出,我们必须响应WM_DESTROY消息,并在消息响应代码中调用PostQuitMessage,向应用程序的消息队列中投递 WM_QUIT消息。传递给 PostQuitMessage函数的参数值将作为 WM_QUIT 消息的wParam参数,这个值通常用作 WinMain函数的返回值。
  38、39行代码:DefWindowProc函数调用默认的窗口过程,对应用程序没有处理的其他消息提供默认处理。对于大多数的消息,应用程序都可以直接调用 DefWindowProc函数进行处理。在编写窗口过程时,应该将 DefWindowProc 函数的调用放到 default 语句中,并将该函数的返回值作为窗口过程函数的返回值。
  读者可以试着将第38、39行代码注释起来,运行一下,看看会有什么结果。提示:
  在运行之后,在Windows中启动任务管理器(同时按下键盘上的“Ctrl+Alt+Del”键),切换到进程标签,查看程序是否运行。

各位小伙伴,我们就说到这里,下次我们再深入研究windows程序的运行机制。

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

 

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

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

相关文章

帆软报表二次开发 树组件节点上添加自己的按钮

我这里的需求是在树组件的子节点上加一个复制按钮。 先看效果 这是一颗多选树,用的是bi.tree_value_chooser_pane 这个组件,这个组件默认支持多选,它默认的效果为: 这个组件没有提供机制来自定义节点的渲染。 通过查找源码发现这个组件内部构建树是通过BI.$.fn.zTree来构…

C语言进阶---数据的存储

1、整形存储和大小端 本章重点: 1、数据类型详细介绍 2、整型在内存中的存储:原码、反码、补码 3、大小端字节序介绍及判断 4、浮点型在内存中的存储解析 1.1、数据类型介绍 char 1byte //字符数据类型 short 2byte //…

【MyBatis】3、一文介绍如何用 MyBatis 进行多表级联查询

目录 一、设置新插入记录的主键&#xff08;id&#xff09;到参数对象中二、PageHelper 分页插件三、多表关系四、一对一五、一对多六、多对多 一、设置新插入记录的主键&#xff08;id&#xff09;到参数对象中 <insert id"insert01" parameterType"Studen…

【二叉树---堆】

二叉树---堆 一、树的概念及结构1. 树的概念2.树的相关概念3.树的表示 二、二叉树1.二叉树的概念2.特殊的二叉树3.二叉树的性质 三、堆1.堆的概念及结构2.堆的实现&#xff08;1&#xff09;函数的声明&#xff08;2&#xff09;函数的实现&#xff08;3&#xff09;测试堆的基…

chatgpt赋能python:Python多次运行的优势及方法

Python多次运行的优势及方法 随着Python在计算机领域的广泛应用&#xff0c;越来越多的企业和个人开始使用Python进行编程。在Python编程中&#xff0c;多次运行同一份代码不仅是常见的需求&#xff0c;而且有着众多的优势。 Python多次运行的优势 调试 在编程过程中&#…

chatgpt赋能python:Python安装EGG——一个简单的指南

Python安装EGG——一个简单的指南 如果你使用Python有一段时间了&#xff0c;你可能会遇到需要安装扩展包&#xff08;Package&#xff09;的情况。在Python中&#xff0c;这些扩展包的文件格式通常是.egg&#xff08;Easy Installable GZip&#xff09;。在本文中&#xff0c…

chatgpt赋能python:Python如何安装CSV模块

Python如何安装CSV模块 CSV是一种广泛使用的文件格式&#xff0c;用于存储表格数据。Python已经内置了CSV模块&#xff0c;可以轻松地读取和写入CSV文件。在这篇文章中&#xff0c;我们将关注如何安装CSV模块并开始与CSV文件进行交互。 什么是CSV&#xff1f; CSV是Comma Se…

chatgpt赋能python:如何安装pyecharts

如何安装pyecharts Pyecharts是一个基于echarts的数据可视化工具&#xff0c;它是Python语言的一个库&#xff0c;可以通过Python编程语言进行数据可视化&#xff0c;并且能通过交互式的方式展示出来。 在本文中&#xff0c;我们将介绍如何安装pyecharts&#xff0c;如果您是…

chatgpt赋能python:Python宏定义:编写高效、灵活和可维护的代码

Python宏定义&#xff1a;编写高效、灵活和可维护的代码 Python是一种非常流行的编程语言&#xff0c;它的简洁性、易读性以及灵活性使得它成为了众多开发者的首选。Python是一个强大的语言&#xff0c;它可以大大提高您的编程效率。Python的宏定义是一种非常有用的技巧&#…

【VB6|第18期】基于libxl导出Excel之导出失败的解决方案

日期&#xff1a;2023年6月12日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

康耐视Visionpro-插入脚本的标准流程 _ 简单方式

机器视觉海康Visionmaster-字符缺失缺陷检测 支持脚本添加的工具如下&#xff1a;添加的脚本只针对当前工具容器有效 Step - 1 &#xff1a; 制作ToolBlock&#xff0c;添加输入/输出等 Step - 2 &#xff1a; 新建Script Step - 3 &#xff1a; 检查界面终端 Step - 4 &am…

【黑马程序员 C++教程从0到1入门编程】【笔记4-2】C++核心编程(类和对象——运算符重载)(左操作数、右操作数)(仿函数)

文章目录 4 类和对象&#xff08;类属性【成员属性】&#xff0c;类函数【成员函数】&#xff09;4.5 运算符重载&#xff08;对已有的运算符重新进行定义&#xff0c;赋予其另一种功能&#xff0c;以适应不同的数据类型&#xff09;4.5.0.1 可重载运算符和不可重载运算符4.5.0…

案例:创建一个学生管理系统(PXSCJ1)的数据库(SQL)

1、新建数据库&#xff1a;PXSCJ1 --创建数据库CREATE DATABASE PXSCJ1 --创建并确认属性&#xff1a;XSB、KCB、CJB&#xff08;以下代码用于2、3、4、5题&#xff09; use PXSCJ1 create table XSB (学号 char(6) primary key check(学号 like [0][8][1][12][0-9][0-9])…

SM3_Robotics,轴组函数调用

1轴组状态&#xff1a; AXIS_GROUP_REF_SM3 (FB) 2使能&#xff1a; MC_GroupEnable &#xff08;使能&#xff09; 默认在&#xff1a; MC_GroupDisable &#xff08;轴组关闭&#xff09;位置 1&#xff1a;用 MC_GroupEnable &#xff08;使能&#xff09;进入 Gro…

chatgpt赋能python:Python怎么定义主函数:完整指南

Python怎么定义主函数&#xff1a;完整指南 Python是当今最流行的编程语言之一&#xff0c;因为它提供了简单易学、高效率、高度可读性和可维护性的代码编写方式。在Python中定义主函数是一个重要的编程技能&#xff0c;使您能够将Python程序变成可执行的Python应用程序。在本…

chatgpt赋能python:Python多次输入——如何自动化处理数据输入

Python多次输入——如何自动化处理数据输入 作为一名有10年Python编程经验的工程师&#xff0c;我曾遇到过很多需要重复输入数据的情况。这不仅浪费时间&#xff0c;而且容易出错&#xff0c;影响我们的工作效率和准确性。作为程序员&#xff0c;我们需要借助Python的自动化技…

openGauss5 企业版之SQL语法和数据结构

文章目录 1.openGauss SQL 语法2. 数据类型2.1数值类型2.2 布尔类型2.3 字符类型2.4 二进制类型2.5日期/时间类型2.6 几何类型2.7 网络地址类型2.8 位串类型2.9 文本搜索类型2.10 UUID数据类型2.11 JSON/JSONB类型2.11 HLL数据类型2.12 范围类型2.13 索引2.14 对象标识符类型2.…

【MySQL】SQL的高阶用法

文章目录 函数聚合函数Count()Max()Min()Sum()Avg() 其他常用函数时间函数字符串函数数学函数 条件查询使用关系运算符查询使用IN关键字查询使用BETWEEN AND关键字查询使用空值查询使用AND关键字查询使用OR关键字查询使用LIKE关键字查询(模糊查询)使用LIMIT限制查询结果的数量使…

用ChatGPT生成测试数据

大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 在之前的文章 用ChatGPT写一个数据采集程序 中&#xff0c;我们演示了如何用 ChatGPT 辅助编写代码。 除了直接让ChatGPT写代码&#xff0c;我们也可以让它生成一些开发中使用的测试数据。 比如在开发和测试时&…