2.2 Windows 编程基础

52
Windows Windows 编编 编编 —— —— 第2 C++ C++ 第第第第 第第第第 03/23/22 03/23/22 编编编编编编编编 编编编 编编编编编编编编 编编编 2.2 Windows 2.2 Windows 第第第第 第第第第 2.2.8 第第第第第第第第第 编 Windows 编 编编编编编编编编编编编编 编编编编编编编编编编编编编编编编 编编编 编编编编编编编编编 ,、。, Windows 编编编编编编 编 编编编编编编编编编编编编编编 编编编编 编编编编 编编编编编编编编编编编编编 编编 编编编编编编编编编 编编编编 ?一,一,一。 1 第第第第第 编编编编编编编编编编编 一一( UINT 编编编编编编 )( WPARAM LPARAM 编编编 编编编编编编编编编编编编编编编编编编编编编编 )。 编编编编编编编编编 编编编编编编编编编编编编编编编编 一。 WM_COMMAND 编编编编WPARAM 编编编编HIWORD 编 wPar am 编编编编 )) ID 编 编编编编编编编编编 ID 编编编编编编编编编编编编编编编编编 编编编编编编编编编编编编编编编编编 编编编编 。,。 2 编编编编编编 编编编编编编编编编编编编编编编编编编编编 一一。( WNDPROC 编编编编编编编编编编编 编编编编编编编编编编编编编编 编编编编编编编编编编 ),。 编编编编 编编编编编编编编 WM_COMMAND 编编编编编编编 编编编编编编编编编编编编编编编编编编 WM_PAINT 编编编编3 编编编编编编编编编编编 M$ 编编编编编编编编编编编编编 编编编编编编编编编编编编编编编编编编编编编 编编编编编编编编编编编编编编 编编编编编编编 ,。, Windows 编 编编编编编编编编编编编编编编编编编编编编编编 编编编编编编编编编编编编编编编编编编 编编编编编编编编编编编编编编编编编编编 。,。

description

2.2 Windows 编程基础. 2.2.8 消息驱动的应用程序 在 Windows 中,应用程序并不直接写屏幕、处理硬件中断或直接对打印机输出。相反,应用程序使用合适的 Windows 函数或者等待一个适当的消息被发出。 什么是消息呢?很难下一个定义,可以从不同的向个方面讲解一下,希望读者看了后有一点了解。 1 .消息的组成 - PowerPoint PPT Presentation

Transcript of 2.2 Windows 编程基础

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 2.2.8 消息驱动的应用程序在 Windows 中,应用程序并不直接写屏幕、处理硬件中断或直接对打印机输出。相反,应用程序使用合适的 Windows 函数或者等待一个适当的消息被发出。什么是消息呢?很难下一个定义,可以从不同的向个方面讲解一下,希望读者看了后有一点了解。1.消息的组成一个消息由一个消息名称( UINT )和两个参数( WPARAM , LPARAM )组成。当用户进行了输入或是窗口的状态发生了改变时系统都会发送消息到某一个窗口。例如当菜单被选中之后会有 WM_COMMAND 消息发送, WPARAM 的高字中( HIWORD ( wParam ))是命令的 ID 号,对菜单来讲就是菜单 ID 。当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。2 .谁将收到消息一个消息必须由一个窗口接收。在窗口的过程( WNDPROC )中可以对消息进行分析,对自己感兴趣的消息进行处理。例如你希望对菜单选择进行处理,那么你可以定义对 WM_COMMAND 进行处理的代码,如果希望在窗口中进行图形输出就必须对 WM_PAINT 进行处理。3 .未处理的消息到哪里去了M$ 为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理的消息。正因为有了这个默认窗口过程,我们才可以利用 Windows 的窗口进行开发而不必过多关注窗口各种消息的处理。例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 4 .窗口句柄在编写 Windows 应用程序的时候经常使用句柄。句柄是一个唯一的数字,发送一个消息时必须指定一个窗口句柄表明该消息由哪个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。它被用于标识许多类型的对象,如菜单、图标、画笔和刷子、内存分配、输出设备甚至窗口实例。下面有一段伪代码演示如何在窗口过程中处理消息:LONG youWndProc(HWND hWnd, UINT uMessageType, WPARAM wp, LPARAM){switch(uMessageType){// 使用 SWITCH 语句将各种消息分开case(WM_PAINT):doYourWindow(…); // 在窗口需要重新绘制时进行输出break;case(WM_LBUTTONDOWN):doyourWork(…); // 在鼠标左键被按下时进行处理break;default:callDefaultWndProc(…); // 对于其他情况就让系统自己处理break; } }

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 2.消息的来源Windows 应用程序的消息来源有以下 4 种。             输入消息:包括键盘、鼠标,甚至语音手写输入等外围输入设备。             控制消息:用来与 Windows 的控制对象,如列表框、按钮、检查框等进行双向通信。             系统消息:对程序化的事件或系统时钟中断作出反应。             用户消息:这是程序员自定义的,由应用程序发出,在应用程序内部进行处理。3.消息机制接下来谈谈什么是消息机制:系统将会维护一个或多个消息队列,所有产生的消息都会被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统,所以 Windows 可以同时进行多个任务。下面的伪代码演示了消息循环的用法:while(1){id=getMessage(…);if(id= =quit)break;translateMessage(…);}

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 当该程序没有消息通知时 getMessage 就不会返回,也就不会占用系统的 CPU 时间。而 32 位的系统中每运行的程序都会有一个消息队列,所以系统可以在多个消息队列中转换而不必等待当前程序完成消息处理就可以得到控制权。这种多任务系统就称为抢先式的多任务系统, Windows 98/NT 就是这种系统。Windows 所能向应用程序发送的消息多达数百种。但是,对于一般的应用程序来说,只是其中的一部分有意义。举一个例子,如果你的应用程序只使用鼠标,那么如 WM_KEYUP , WM_KEYDOWN 和 WM_CHAR 等消息就没有任何意义,也就是说,应用程序中事实上不需要处理这些消息,对于这些消息,只需要交给 Windows 作默认的处理即可。因此,在应用程序中,我们需要处理的消息只是所有消息中的一小部分。因此,从某种角度上来看, Windows 应用程序是一系列的消息处理代码来实现的。这和传统的过程式编程方法很不一样,编程者只能够预测用户所利用应用程序用户界面对象所进行的操作以及为这些操作编写处理代码,却不知道这些操作在什么时候发生或者是以什么顺序来发生。也就是说,我们不可能知道什么消息会在什么以什么顺序来临。Windows 程序在处理消息时使用了一种叫做回调函数( callback function )的特殊函数。回调函数由应用程序定义,但是,在应用程序中并没有调用回调函数的代码,回调函数是供操作系统或者其子系统调用的,

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 这种调用通常发生在某一消息发生,或者在窗口或字体被枚举时。典型的回调函数有窗口过程、对话框过程和钩子函数。 图给出了一般 Windows 应用程序的执行流程。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 2.2.9 Windows 的函数Windows 向应用程序开发人员提供了数以百计的函数。这些函数的例子包括 DispatchMes-sage() , PostMessage() , RegisterWindowMessage() 以及 SetActiveWindow() 。对于使用基础类库的 C++ 程序员,许多函数自动被运行。在 16 位的 Windows 3.x 下的函数声明包括一个 Pascal 修饰符,这在 DOS 下更为有效 Windows 95 和 Windows NT 下的 32 位应用程序不再使用这个修饰符。如你所知,所有 Windows 函数的参数是通过系统来传递的。函数的参数从最右边的参数开始向左压入栈,这是标准的 C 方式。在从函数返回之前,调用过程必须按原来压入栈的字节数调整栈指针。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 2.2.10 Win32 API 和 SDK说到 Windows 编程,就不能不谈到 Windows API ( Windows Application Programming Interface , Windows 应用程序编程接口),它是所有 Windows 应用程序的根本之所在。注意:简单地说, API 就是一系列的例程,应用程序通过调用这些例程来请求操作系统完成一些低级服务。在 Windows 这样的图形用户界面中,应用程序的窗口、图标、菜单和对话框等就是由 API 来管理和维护的。使用 Win32 API ,应用程序可以充分挖掘 Windows 的 32 位操作系统的潜力。 Microsoft 的所有 32 位平台都支持统一的 API ,包括函数、结构、消息、宏及接口。使用 Win32 API 不但可以开发出在各种平台上都能成功运行的应用程序,而且也可以充分利用每个平台特有的功能和属性。在具体编程时,程序实现方式的差异依赖于相应平台的底层功能的不同。最显著的差异是某些函数只能在更强大的平台上实现其功能。例如,安全函数只能在 Windows NT 操作系统下使用。另外一些主要差别就是系统限制,比如值的范围约束,或函数可管理的项目个数等等。标准 Win32 API 函数可以分以下几类:                     窗口管理。                     窗口通用控制。                     Shell 特性。                     图形设备接口。                     系统服务。                     国际特性。                     网络服务。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 Windows SDK ( Windows Software Development Kit , Windows 软件开发工具包)和Windows API 紧密联系,它是一套帮助 C 语言程序员创建 Windows 应用程序的工具,在 Windows SDK 中包括了以下几个组成部分:                     大量的在线帮助,这些帮助描述了 Windows 编程所可能用到的函数、消息、结构、宏及其他资源。                     各种编程工具,如对话框编辑器及图像编辑器等。                     Windows 库及头文件。                     使用 C 语言编写的示例程序。该工具包的最新版本就是我们正在使用的 Win32 SDK ,在安装了 Visual C++ 的同时, Win32 SDK 也安装到你的计算机上了。尽管 MFC 提供了对 Win32 API 比较完整的封装,但是,在某些情况下,我们更倾向于直接调用 Win32 API ,因为有时候可以获得更高的效率,并且有着更大的自由度。而且,使用 MFC 编写新风格的 Windows 应用程序的工作方式基本上与使用 SDK 编写的同一程序一样,它们往往有着很多的共同之处,只是使用 MFC 更加方便,因为它隐藏了大量的复杂性。前面提到过,面向对象的编程方式是当前最流行的程序设计方法,但是, Win32 API 本身却是基于 C 语言的过程式编程的, SDK 和 MFC 最主要的不同之处也就是以 C 与 C++ 之间的差别,使用 MFC 进行 Windows 应用程序设计需要面向对象的编程思想和方法。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 2.2.11 Windows 应用程序框架Windows 头文件: Windows.hWindows.h 头文件 ( 以及其它相关文件 ) 是所有程序的内在部分。传统上, WINDOWS.H 是所有 C 语言编写的 Windows 应用程序必需的一部分。当在 C++ 中使用基础类库时, Windows.h 包括在 AFXWIN.H 头文件中。1.Windows应用程序的组成在开发 Windows 应用程序的过程中有一些重要的步骤:用 C 语言编写 WinMain() 函数和相关的窗口函数,或者在 C++ 中使用基础类,比如 CWinApp 等创建菜单、对话框和其它资源并把它们放入资源描述文件。( 可选 ) 使用 Vinsual C++ 编译器中的企业编辑器来创建对话框。用项目文件来编译并链接所有的 C/C++ 源程序和资源文件。Windows 应用程序中的组成部分1. WinMain() 函数传统的 DOS 程序以 main 函数作为进入程序的初始入口点,在 Windows 应用程序中, main 函数被 WinMain 函数取而代之, WinMain 函数的原型如下:int WINAPI WinMain (HINSTANCE hInstance, // 当前实例句柄HISTANCE hPrevInstance, // 前一实例句柄LPSTR ipCmdLine, // 指向命令行参数的指针int nCmdShow) // 窗口的显示状态这里出现了一个新的名词“句柄”( handle ),所谓句柄是指一个标识对象的位置,或者是一个对操作系统资源的间接引用。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 查看 Win32 SDK 文档或者浏览 Windows 头文件(如 windef.h , ctype.h 以及 winnt.h 等)可以获得关于其他数据类型的定义,这些定义往往使用了 #define 和 typedef 等关键字。这里解释什么是应用程序的一个实例( instance )。最简单的理解可以用下面的例子来说明:比如说已经在 Windows 中打开了一个“写字板”(可以在“开始”菜单中的“程序” |“附件”下面找到它的快捷方式),现在你需要从另一篇文章里复制一部分内容到你正在写的主篇文章中。那么,你可以再打开一个“写字板”(注意写字板不是一个多文档应用程序,不能像在 Word 中那样打开多个不同的文件),然后从该写字板中复制文件的内容到在前一个写字板内打开的文章中。这里,我们多次运行了同一个应用程序,在这个例子中,我们将所打开的两个写字板叫做该应用程序的两个实例。对于实例的更精确(当然也要比上面的例子要更难懂得多)的定义,在 Win32 SDK 中是这样给出的:实例就是类中一特定对象类型的一个实例化对象( instantiation ),如一个特定的进程或线程,在多任务操作系统中,一个实例指所加载的应用程序或动态链接库的一份拷贝。刚开始时我们也许看不懂这一定义,不过没有关系,慢慢地就理解了。在上面的 WinMain 函数原型中的另一个奇怪的标识符为 WINAPI ,这是一个在 windef.h 头文件中定义的宏,在当前版本 Win32 SDK 中, WINAPI 被定义为 FAR PASCAL 。因此,使用 FAR PASCAL 同使用 WINAPI 具有同样的效果;但是,我们强烈建议你使用 WINAPI 来代替以前常用的 FAR PASCAL;因为 Microsoft 不保证 FAR PASCAL 能够在将来的 Windows版本中正常工作。在目前情况下,和 FAR PASCAL 等价的标识符还有 CALLBACK (用在如窗口过程或对话框过程之类的回调函数前)和 APIENTRY 等。它们对编译器而言都是一回事,最终将被解释为 _stdcall 。在 Windows 环境下编程,会遇到很多这样的情况,注意不要混淆它们。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 一般情况下,我们应该在 WinMain 函数中完成下面的操作:1 .注册窗口类。2 .创建应用程序主窗口。3 .进入应用程序消息循环。接下来我们将依次讨论这些内容。在 Windows 应用程序中,每一个窗口都必须从属于一个窗口类,窗口类定义了窗口所具有的属性,如它的样式、图标、鼠标指针、菜单名称及窗口过程名等。在注册窗口类前,我们先创建一个类型为 WNDCLASS 的结构,然后在该结构对象中填入窗口类的信息,最后将它传递给函数 RegisterClass ,整个过程如下面的代码所示:WNDCLASS wc;//填充窗口类信息wc. style=CS_HREDRAW|CS_VREDRAW;wc. IpfnWndProc=WndProc;wc. cbClsExtra=0;wc. cbWndExtra=0;wc. hInstance=hInstace;wc. hIcon=LoadIcon(NULL, IDI_APPLICATION);wc. hCursor=LoadCursor(NULL, IDC_ARROW);wc. hbrBackground=GetStockOjbect(WHITE_BRUSH);wc. IpszMenuName=NULL;wc. IpszClassName="ex4_1";// 注册窗口类RegisterClass(&wc);

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 下面解释一下结构 WNDCLASS 中各成员的含义:style :指定窗口样式。该样式可以为一系列的屏蔽位按位或,在前面的例子中, CS_HREDRAW 表示当窗口用户区宽度改变时重绘整个窗口,而 CS_VREDRAW则表示当窗口用户区高度改变时重绘整个窗口。对于其他的窗口样式,请参阅 SDK 中关于 WNDCLASS 的联机文档(顺便说一句,请注意该成员的大小写,它是小写的 style ,而不是 Style )。IpfnWndProc :指向窗口过程的指针。关于窗口过程我们将在后面的内容中讲述。在前面的例子中,我们使用名为 WndProc 的窗口过程。cbClsExtra :指定在窗口类结构之后分配的附加字节数。操作系统将这些字节初始化为 0cbWndExtra :指定在窗口实例之后分配的附加字节数。操作系统将这些字节初始化为 0。如果应用程序使用 WNDCLASS 结构注册一个使用资源文件中的 CLASS 指令创建的对话框,那么 cbWndExtra 必须被设置为 DLGWINDOWEXTRA 。hInstance :标识该类的窗口过程所属的实例。hIcon :标识类图标。该成员必须为一个图标资源的句柄。如果该成员为 NULL ,则应用程序必须在用户最小化应用程序窗口时绘制图标。hCursor :标识类鼠标指针。该成员必须为一个光标资源的句柄,如果该成员为 NULL ,当鼠标移进应用程序窗口时应用程序必须显式指定指针形状。hbrBackground :标识类背景刷子。该成员可以为一个用来绘制背景的画刷句柄,或者为标准系统颜色值之一。IpszMenuName :指向一个以 NULL 结尾的字符串的指针,该字符串指定了类菜单的资源名称,如果资源名称的菜单为一个整灵敏所标识,则可以使用 MAKEINTRESOURCE 宏将其转换为一个字符串;如果该成员为 NULL ,则属于该类的窗口无默认菜单。 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 IpszClassName :指向一个以 NULL 结尾的字符串或为一个原子。如果该参数为一个原子,那么它必须是一个使用 GlobalAddAtom 函数创建的全局原子;如果为一个字符串,该字符串定义了成员窗口类名。这里多次提到窗口类这一名词,但是它和前面常说的 C++ 类没有任何联系。窗口类只表示了窗口的类型,它完全不是面向对象意义上的类,因为它不运行面向对象技术中的继承及多态等。在使用 RegisterClass 注册窗口类成功之后,即可以使用该窗口类创建并显示应用程序的窗口。这个过程如下面的代码所示:// 创建应用程序主窗口hWnd=CreateWindow ("ex4_1", // 窗口类名"我的 Win32 应用程序 ", // 窗口标题WS_OVERLAPPEDWINDOW, // 窗口样式CW_UCEDEFAULT, //初始化 x坐标CW_USEDEFAULT, //初始化 y坐标CW_USEDEFAULT, //初始化窗口宽度CW_USEDEFAULT, //初始化窗口高度NULL, //父窗口句柄NULL, // 窗口菜单句柄hInstance, // 程序实例句柄NULL); // 创建参数

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 // 显示窗口ShowWindow(hWnd, SW_SHOW);// 更新主窗口客户区UpdateWindow(hWnd);由于上述代码均加上了详尽的注释,这里仅作一些简单的说明和强调。 CreateWindow 函数的原型是这样的:HWND CreateWindow(LPCTSTR IpClassName, // 指向已注册的类名LPCTSTR IpWindowName, // 指向窗口名称DEORD dwStyle, // 窗口样式int x. // 窗口的水平位置int y. // 窗口的垂直位置int nWidth, // 窗口宽度int nHeight, // 窗口高度HWND hWndParent, //父窗口或所有者窗口句柄HMENU hMenu, // 菜单句柄或子窗口标识符HANDLE hInstance, // 应用程序实例句柄LPVOID IpParam, // 指向窗口创建数据的指针 };在前面的示例中,我们对 x , y , nWidth 和 nHeight 参数都传递了同一个值 CW_USEDEFAULT ,表示使用系统默认的窗口位置和大小,该常量仅对于重叠式窗口(即在 dwStyle 样式中指定了 WS_OVERLAPPEDWINDOW ,另一个常量 WS_TILEDWINDOW 有着相同的值)有效。对于 CreateWindows 函数的其他内容,比如关于 dwStyle 参数所用常量的详细参考请自行参数 Win32 SDK 中的文档。 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 尽管 Windows XP 是一个 32 位的操作系统,但是,其中也保留了很多 16 位的特征,比如说,在 Windows 98 环境下,系统最多只可以有 16384 个窗口句柄。而在 Windows XP 下则无此限。然而,事实上,对于一般的桌面个人机系统来说,我们几乎不可能超过这个限制。创建窗口完成之后, ShowWindows 显示该窗口,第 2 个参数 SW_SHOW 表示在当前位置以当前大小激活并显示由第 1 个参数标识的窗口。然后,函数 UpdateWindows 向窗口发送一条 WM_PAINT 消息,以通知窗口更新其客户区。需要注意的是,由 UpdateWindows 发送的 WM_PAINT 消息将直接发送到窗口过程(在上面的例子中是 WndProc 函数),而不是发送到进程的消息队列,因此,尽管这时应用程序的主消息循环尚未启动,但是窗口过程仍可接到该 WM_PAINT 消息并更新其用户区。在完成上面的步骤之后,进入应用程序的主消息循环。一般情况下,主消息循环具有下面的格式:while(GetMessage(&msg, NULL, 0,0)){TranslateMessage(&msg);DispatchMessage(&msg);}主消息循环由对 3 个 API 函数的调用和一个 while 结构组成。其中 GetMessage 从调用线程的消息队列中获取消息,并将消息放到由第 1 个参数指定的消息结构中。如果指定了第 2 个参数,则 GetMessage 获取属于该参数指定的窗口句柄所标识的窗口消息,如果该参数为 NULL ,则 GetMessage 获取属于调用线程及属于该线程的所有窗口的消息。最后两个参数指定了 GetMessage 所获取消息的范围,如果两个参数均为 0,则 GetMessage 检索并取所有可以得到的消息。 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 在上面的代码中,变量 msg 是一个类型为 MSG 的结构对象,该结构体的定义如下:typedef struct tagMSG { //msgHWND hwnd;UINT message;WPARAM wParam;LPARAM Iparam;DWORD time;POINT pt; } MSG;下面解释各成员的含义:hwnd :标识获得消息的窗口进程的窗口句柄。message :指定消息值。wParam :其含义特定于具体的消息类型。Iparam :其含义特定于具体的消息类型。time :指定消息发送时的时间。pt :以屏幕坐标表示消息发送时的鼠标指针的位置。在 while 循环体中的 TranslateMessage 函数将虚拟按键消息翻译为字符消息,然后将消息发送到调用线程的消息队列,在下一次调用 GetMessage 函数或 PeekMessage 函数时,该字符消息将被获取。 TranslateMessage 函数将 WM_KEYDOWN 和 WM_KEYUP虚拟按键组合翻译为 WM_CHAR 和 WM_DEADCHAR ,将 WM_SYSKEYDOWN 和 WM_SYSKEYUP虚拟按键组合翻译为 WM_SYSCHAR 和 WM_SYSREADCHAR 。需要注意的一点是,仅当相应的虚拟按键组合能够被翻译为所对应的 ASCII 字符时, TranslateMessage 才发送相应的 WM_CHAR 消息。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 如果一个字符消息被发送到调用线程的消息队列,则 TranlateMessage 返回非零值,否则返回零值。与在 Windows 98 操作系统下不同,在 Windows XP 下, TranslateMessage 对于功能键和光标箭头键也返回一个非零值。然后,函数 DispatchMessage 将属于某一窗口的消息发送给窗口的窗口过程。这个窗口由MSG 结构中的 hwnd 成员所标识。函数的返回值为窗口过程的返回值,但是,我们一般不使用这个返回值。这里要注意的是,并不一定是所有属于某一个窗口的消息都发送给窗口的窗口过程,比如对于 WM_TIMER 消息,如果其 IParam 参数不为 NULL 的话,由该参数所指定的函数将被调用,而不是窗口过程。如果 GetMessage 从消息队列中得到一个 WM_QUIT 消息,则它将返回一个假值,从而退出消息循环, WM_QUIT 消息的 wParam 参数指定了由 PostQuitMessage 函数给出的退出码,一般情况下, WinMain 函数返回同一值。下面我们来看一下程序主窗口的窗口过程 WndProc 。窗口过程名是可以由用户自行定义,然后在注册窗口类时在 WNDCLASS 结构中指定。但是,一般来说,程序都把窗口过程命令与为 WnProc 类似的名称,如 MainWndProc 等,并不是一定要这样做,但是这样明显地有利于阅读,因此也是我们推荐的做法。窗口过程具有如下的原型:LPESULT WINAPI WndProc(HWND, UINT, WPARAM, LPARAM);或LPESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 对于编译器而言,两种书写形式都是一样的,它们都等价于:long_stdcall WndProc(void*, unsigned int, unsigned int, long) 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 窗口过程使用了 4 个参数,在它被调用时(再强调一点,一般情况下,窗口过程是由操作系统调用,而不是由应用程序调用的,这就是我们为什么将它们称为回调函数的原因),这 4个参数对应于所发送消息结构的前 4 个成员。下面给出了一个窗口过程的例子://WndProc 主窗口过程LRESULT WINAPI WndProc (HWND hWnd,UINT msg,WPARAM wParam,LPARAM IParam){HDC hDC;Char text[]=" 欢迎进入 Windows 的神奇世界 ";PAINTSTRUCT ps;switch (msg){case WM_PAINT:hDC=BeginPaint(hWnd, &ps);TextOut(hDC, 0,0,text,sizeof(text)-1);EndPaint(hWnd, &ps);return 0;break;

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 case WM_DESTROY;PostQuitMessage(0);break;default;break; }return DefWindowProc(hWnd, msg, wParam, IParam); }在该窗口过程中,我们处理了最基本的两条消息。第一条消息是 WM_PAINT ,当窗口客户区的全部或一分部需要重绘时,系统向该窗口发送该消息。在前面的过程中我们已经提到过,在使用 ShowWindow 函数显示窗口之后,通常随即调用函数 UpdateWindow ,该函数直接向窗口过程发送一个 WM_PAINT 消息,以通知窗口绘制其客户区。在该消息的处理函数中,我们先使用 GetDC 获得窗口的设备句柄,关于设备句柄本书后面将要专门介绍,这里我们只需知道它是用来调用各种绘图方法的。然后调用 GetClientRect 获得当前窗口的客户区矩形。接着调用 CreatePen 创建一个黑色画笔,调用 CreateHatchBrush 创建一个 45 交叉线的填充画刷,并且 SelectObject 函数将它们选入设备描述表中,原有的画笔和画刷被保存到 hPenOld 和 hBrushOld 中,以便以后恢复。完成以上步骤之后,调用 Ellipse 函数以当前客户区大小绘制一个随圆。最后,再一次调用 SelectObject 函数恢复原有的画笔和画刷,并调用 ReleaseDC释放设备描述表句柄。在这个消息的处理代码中,我们涉及到了一些新的概念、数据类型和 API 函数,然而本节并不着重讲述这些内容,读者也不必深究它们,它些代码只是为了完整该示例程序才使用的。对于窗口来说,除了客户区以外的其他内容将由系统进行重绘,这些内容包括窗口标题栏、边框、菜单栏、工具栏以及其他控件,如果包含了它们的话。这种重绘往往发生在覆盖于窗口上方的其他窗口被移走,或者是窗口被移动或改变大小时。因此,对于大多数窗口过程来说, WM_PAINT 消息是必须处理的。 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 另一个对于绝大多数窗口过程都必须处理的消息是 WM_DESTROY ,当窗口被撤消时 *比如用户从窗口的系统菜单中选择了“关闭”,或者单击了右边的小叉,对于这些消息, Windows 的默认处理是调用 DestroyWindow 函数撤消相应的窗口),将会接收到该消息。由于本程序仅在一个窗口,因此在这种情况下应该终止应用程序的执行,因此我们调用了 PostQuitMessage 函数,该函数向线程的消息队列中放入一个 WM_QUIT 消息,传递给 PostQuitMessage 函数的参数将成为 WM_QUIT 消息的 wParam 参数,在上面的例子中,该值为 0。对于其他情况,在上面的示例程序中我们没有必要进行处理, Windows专门为此提供了一个默认的窗口过程,称为 DefWindowProc ,我们只需要以 WndProc 的参数原封不动地调用默认窗口过程 DefWindowProc ,并将其返回值作为 WndProc 的返回值即可。将上面讲述的所有内容综合起来,我们就已经使用 Win32 SDK 完成了一个功能简单,但是结构完整的 Win32 应用程序了。对于使用 Win32 SDK 编写的实用的 Win32 应用程序,它们的结构与引相比要复杂得多,在这些情况下,应用程序也许不仅仅包括一个窗口,而对应的窗口过程中的 switch 结构一般也会是一个异常膨胀的嵌套式 switch 结构。比如庞大的消息处理过程大大增加了程序调试和维护的难度,使用 MFC则有可能在很多程序上减轻这种负担,这便是 MFC 为广大程序员所乐于接受,以至今天成为实际上的工业标准的原因。但是,不管它如何复杂,归根到底,一般情况下,它仍然具有和我们的这个功能简单的 Win32 应用程序一样或类似的结构。为了读者阅读和分析方便,这个程序的完整代码如下:

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 #include <windows.h>// 函数原型int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int);LPESULT WINAPI WndProc(HWND, UINT, WPARAM, LPARAM);//WinMain 函数int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR IpCmdLine,int nCmdShow){HWND hWnd; // 主窗口句柄MSG msg; // 窗口消息WNDCLASS wc; // 窗口类if(!hPrevInstance){//填充窗口类信息wc. style=CS_HREDRAW|CS_VREDRAW;wc. IpfnWndProc=WndProc;wc. cbClsExtra=0;wc. cbWndExtra=0;wc. hInstance=hInstace; 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 wc. hIcon=LoadIcon(NULL, IDI_APPLICATION);wc. hCursor=LoadCursor(NULL, IDC_ARROW);wc. hbrBackground=GetStockOjbect(WHITE_BRUSH);wc. IpszMenuName=NULL;wc. IpszClassName="ex4_1";// 注册窗口类RegisterClass(&wc);}// 创建应用程序主窗口hWnd=CreateWindow ("ex4_1", // 窗口类名"我的 Win32 应用程序 ", // 窗口标题WS_OVERLAPPEDWINDOW, // 窗口样式CW_UCEDEFAULT, //初始化 x坐标CW_USEDEFAULT, //初始化 y坐标CW_USEDEFAULT, //初始化窗口宽度CW_USEDEFAULT, //初始化窗口高度NULL, //父窗口句柄NULL, // 窗口菜单句柄hInstance, // 程序实例句柄NULL); // 创建参数// 显示窗口ShowWindow(hWnd, SW_SHOW); 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 // 更新主窗口客户区UpdateWindow(hWnd);// 开始消息循环while(GetMessage(&msg, NULL, 0,0)){TranslateMessage(&msg);DispatchMessage(&msg);}return msg. wParam;}//WndProc 主窗口过程LRESULT WINAPI WndProc (HWND hWnd,UINT msg,WPARAM wParam,LPARAM IParam){HDC hDC;Char text[]=" 欢迎进入 Windows 的神奇世界 ";

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 PAINTSTRUCT ps;switch (msg){case WM_PAINT:hDC=BeginPaint(hWnd, &ps);TextOut(hDC, 0,0,text,sizeof(text)-1);EndPaint(hWnd, &ps);return 0;break;case WM_DESTROY;PostQuitMessage(0);break;default;break;}return DefWindowProc(hWnd, msg, wParam, IParam);}

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 2.2.12 VC++ 提供的 windows 编程工具Visual C++ 编译器包含几个资源编辑器。单独的编辑器可以通过编译器主菜单中的 Insert Resource 菜单来运行。图形对象都是资源,象图标、光标、消息框、对话框、字体、位图、画笔、刷子等。资源代表应用程序的可执行文件中包含的数据。资源编译器 RC.EXE 是一个 Windows 资源的编译器。资源以及附加的编译器的使用增加了应用程序开发的复杂性。但是它容易在项目工具中使用。项目文件项目文件提供了概览资源和程序代码编译过程的手段,同时也可以使应用程序的可执行版本保持最新。它们跟踪源文件的日期和时间以实现这些增强的功能。项目文件包含了有关特定程序的编译链过程的信息。项目文件是在集成的 C 或 C++ 编辑环境中创建的。项目文件还支持增强的编译和链接。资源当你使用 VisualC++ 编译器提供的资源编辑器时,用自己的图标、指针和位图来定制 Windows 应用程序非常容易。这些编辑器给你提供了一个开发图形资源的完整环境。这些编辑器同时也能帮助你开发菜单和对话框-Windows 下数据输入的基本手段。这些编辑器还能帮你操纵单独的位图、加速键和字符串。资源编辑器每一种编辑器都在 VisualC++ 环境中提供,都是编译器的一个集成的部分。这样,每种编辑器都是在 Windows 下运行的完全集成的资源开发工具。你可以通过选择 Insert Resource来启动每一种编辑器。 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 2.2.13 32 位编程特点本节假定用户是刚接解 32 位 Windows 编程的新手,那么,有必要将一些相关的概念术语弄清楚,同时,也要把Windows98 , Windows NT 和 16 位的 Windows 3.x 相区别开来。这些最重要的概念包括进程和线程的管理以及新的 32 位平坦内存模式。在介绍 32 位内存管理之前,我们有必要介绍一下进程和线程这两个术语。注意:进程是装入内存中正在执行的应用程序,进程包括私有的虚拟地址空间、代码、数据及其他操作系统资源,如文件、管道以及对该进程可见的同步对象等。进程包括了一个或多个在进程上下文内运行的线程。线程是操作系统分配 CPU 时间的基本实体。线程可以执行应用程序代码的任何部分,包括当前正在被其他线程执行的那些。同一进程的所有线程共享同样的虚拟地址空间、全局变量和操作系统资源。在一个应用程序中,可以包括一个或多个进程,每个进程由一个或多个线程构成。线程通过“休眠”( sleeping ,暂停所有执行并等待)的方法,来做到与进程中的其他线程所同步。在线程休眠前,必须告诉Windows ,该线程将等待某一消息的发生。当该消息发生时, Windows 发给线程一个唤醒调用,,线程继续执行。也就是说,线程与消息一起被同步,除此之外,也可以由特殊的同步对象来进行线程的同步,这些同步对象包括:互斥:不受控制的或随意的线程访问在多线程应用程序中可能会引起很大的问题。这里所说的互斥是一小部分代码,它时刻采取对共享数据的独占控制以执行代码。互斥常被应用于多进程的同步数据存取。信号量:信号量与互斥相似,但是互斥只允许在同一时刻一个线程访问它的数据,而信号量允许多个线程在同一时刻访问它的数据。 Win32 不知道哪一个线程拥有信号量,它只保证信号量使用的资源量。 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 临界区:临界区对象也和互斥相似,但它仅被属于单个进程的线程使用。临界区对象提供非常有效的同步模式,同互斥一样,每次在同一时间内只有一个线程可以访问临界区对象。消息:消息对象用于许多实例中去通知休眠的线程所等待的消息已经发生,消息告诉线程何时去执行某一个给定的任务,并可以使多线程流平滑。将所有的这些同步对象应用于控制数据访问使得线程同步成为可能,否则,如果一个线程改变了另一个线程正在读的数据,将在有可能导致很大的麻烦。在 Win32 环境下,每个运行的在进程内的线程还可以为它自己的特定线程数据分配内存,通过 Win32 提供的线程本地存储( TLS ) API ,应用程序可以建立动态的特定线程数据,在运行时这些数据联系在一起。下面我们来看在 32 位应用程序地址空间中的内存分配和内存管理。常见的内存分配可以划分为两类:帧分配( frame allocation )和堆分配( heap allocation )。两者的主要区别在于帧分配通常和实际的内存块打交道,而堆分配在一般情况下则使用指向内存块的指针,并且,帧对象在超过其作用域时会被自动地删除,而程序员必须显式地删除在堆上分配的对象。在帧上分配内存的这种说法来源于“堆栈帧”( stack frame )这个名词,堆栈帧在每当函数被调用时创建,它是一块用来暂时保存函数参数以及在函数中定义的局部变量的内存区域。帧变量通常被称作自动变量,这是因为编译器自动为它们分配所需的内存。帧分配有两个主要特征,首先,当我们定义一个局部变量时,编译器半在堆栈帧上分配足够的空间来保存整个变量,对于很大的数组和其他数据结构也是这样;其次,当超过其作用域时,帧变量将被自动删除。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 对于局部函数变量,其作用域转变在函数退出时发生,但如果使用了嵌套的花括号,由帧变量的作用域将有可能比函数作用域小。自动删除这些帧变量非常重要。对于简单的基本数据类型(如整形或字节变量)、数组或数据结构,自动删除只是简单地回收被这些变量所占用的内存。由于这些变量已超出其作用域,它们将再也不可以被访问。对于 C++ 对象,自动删除的过程要稍微复杂一些。当一个对象被定义为一个帧变量时,其构造函数在定义对象变量时被自动地调用,当对象超出其作用域时,在对象所占用的内存被释放前,其析构函数先被自动地调用。这种对构造函数和析构函数的调用看起来非常的简便,但我们必须对它们倍加小心,尤其是对析构函数,不正确的构造和释放对象将可能对内存管理带来严重的问题。在帧上分配对象的最大优越性在于这些对象将会被自动删除,也就是说,在你在帧上分配对象之后,不必担心它们会导致内存渗漏( memory leak )。但是,帧分配也有其不方便之处,首先,要帧上分配的变量不可以超出其作用域,其中,帧空间往往是有限的;因此,在很多情况下,我们更倾向于使用下面要讲述的堆分配来代替这里所讲述的帧分配来为那些庞大的数据结构或对象分配内存。堆是为程序所保留的用于内存分配的区域,它与程序代码和堆栈相隔离。在通常情况下, C程序使用函数 malloc 和 free 来分配和释放堆内存。调试版本( Debug version )的 MFC提供了改良版本的 C++ 内建运算符 new 和 delete 用于在堆内存中分配和释放对象。使用 new 和 delete 代替malloc 和 free 可以从类库提供的增强的内存管理调试支持中得到好处,这在检测内存漏损时非常有用。而当你使用 MFC 的发行版本( Release version )来创建应用程序时, MFC 的发行版本并没有使用这种改良版本的 new 和 delete 操作符,取而代之的是一种更为有效的分配和释放内存的方法。与帧分配不同,在堆上可分配的对象所占用的内存的总量只受限于系统可有的所有虚拟内存空间。 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 下面我们简单地介绍Win32 内存管理模式。在 Microsoft Win32 应用程序编程接口中,每一个进程都有自己多达 4GB 的虚拟地址空间。内存中低位的 2GB (从 0x00到 0xFFFFFFF )可以为用户所用,高位的 2GB (从 0x80000000到 0xFFFFFFFFF )为内核所保留。进程所使用的虚拟地址并不代表对象在内存中实际的物理地址。事实上,内核为每一个进程维护了一个页映射,页映射是一个用于将虚拟地址转换为对应的物理地址的内部数据结构。每一个进程的虚拟地址空间都要比所有进程可用的物理内存 RAM (随机存取存储器)的总和大得多。为了增加物理存储的大小,内核使用磁盘作为额外的存储空间。对于所有正在执行的进程来说,总的存储空间的量是物理内存 RAM 和磁盘上可以为页面文件所用的自由空间的总和,这里页面文件指用来增加物理存储空间的磁盘文件。每个进程物理存储空间和虚拟地址(也称为逻辑地址)空间以页的形式来组织,页是一种内存单元,其大小依赖于计算机的类型。对于 x86 计算机来说,宿主页大小为 4KB ,但我们不能假定对所有运行 Windows 操作系统的计算机,其页大小均为 4KB 。为了使内存管理具有最大的灵活性,内核可以将物理内存中的页移入或移出磁盘上的页面文件。当一个页被移入物理内存时,内核更新受到影响进程的页映射。当内核需要物理内存空间时,它将物理内存中最近最少使用的页移入页面文件。对于应用程序来说,内核对物理内存的管理是完全透明的,应用程序只对它自己的虚拟地址空间进行操作。进程可以使用函数 GlobalAlloc 和 LocalAlloc 来分配内存。在 Win32 API 的 32 位线性环境中,本地堆和全局堆并没有区别,因此,使用这两个函数来分配内存对象也没有任何区别。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 由 GlobalAlloc 和 LocalAlloc 函数分配的内存对象位于私有的占用页中,这些页允许进行读写存取。私有内存不可以为其他进程所访问。与在 Windows 3.x 中不同,使用带有 GMEM_DDESHARE 标志的 GlobalAlloc 函数分配的内存事实上并没有被全局共享。保留该标志位仅是为了向妆兼容和为一些应用程序增强动态数据交换( DDE , dynamic data exchange )的性能而使用。应用程序如果因其他目的需要共享内存,那么必须使用文件映射对象。多个进程可以通过映射同一个文件映射对象的视来提供命名共享内存。我们在这里将不讨论文件映射和共享内存的问题。通过使用函数 GlobalAlloc 和 LocalAlloc ,可以分配能够表示为 32 位的任意大小的内存块,所受的惟一限制是可用的物理内存,包括在磁盘上的页面文件中的存储空间。这些函数,和其他操作全局和本局内存对象的全局和本地函数一起被包含在 Win32 API 中,和 Windows 的 16 位版本相兼容。但是,从 16 位分段内存模式到 32 位虚拟内存模式的转变将使得一些函数和一些选项变得不必要甚至没有意义。比如说,现在不再区分近指针和远指针的区别,因为无论在本地还是在全局进行分配都将返回 32 位虚拟地址。函数 GlobalAlloc 和 LocalAlloc 都可以分配固定或可移动的内存对象。可移动对象也可以被标记为可丢弃的( discardable )。在早期的 Windows 版本中,可移动的内存对象对于内存管理非常重要,它们允许系统在必要时压缩堆为其他内存分配提供可用空间。通过使用虚拟内存,系统能够通过移动物理内存页来管理内存,而不影响使用这些页的进程的虚拟地址。当系统移动一个物理内存页时,它简单地将进程的虚拟页映射到新的物理页的位置。可移动内存在分配可丢充内存仍然有用。当系统需要额外的物理存储时,它使用一种称作“最近最少使用”的算法来释放非锁定的可丢弃内存。可丢弃内存可以用于那些不是经常需要和易于重新创建的数据。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 当分配固定内存对象时, GlobalAlloc 和 LocalAlloc 返回 32 位指针,调用线程可以立即使用该指针来进行内存存取。对于可移动内存,返回值为一个句柄。为了得到一个指向可移动内存的指针,调用线程可以使用 GloballoLock 和 LocalLock 函数。这些函数锁定内存使得它不能够被移动或丢弃,除非使用函数 GlobalReAlloc 或 LocalReAlloc 对内存对象进行重新分配。已锁定内存对象的内存块保持锁定状态,直至锁定计数减到 0,这时该内存块可以被移动或丢弃。由 GlobalAlloc 和 LocalAlloc 所分配的内存的实际大小可能大小所要求的大小。为了得到已分配的实际内存数,可以使用函数 GlobalSize 和 LocalSize 。如果总分配量大于所要求的量,进程则可以使用所有的这些量。函数 GlobalReAlloc 和 LocalReAlloc 以字节为单位改变由 GlobalAlloc 和 LocalAlloc 函数分配内存对象的大小或其属性。内存对象的大小可以增大,也可以减小。函数 GlobalFree 和 LocalFree 用于释放由 GlobalAlloc , LocalAlloc , GlobalReAlloc 或LocalReAlloc 分配的内存。其他的全局和本地函数包括 GlobalDiscard , LocalDiscard , GlobalFlags , LocalFlags, GlobalHandle 和 LocalHandle 。 GlobalDiscard 和 LocalDiscard 用于丢弃指定的可丢弃内存对象,但不使其句柄无效。该句柄可能通过函数 GlobalReAlloc 或 LocalReAlloc 与新分配的内存块相关联。函数 GlobalFlags 或 LocalFlags 返回关于指定内存对象的信息。这些往往包括对象的锁定计数以及对象是否可丢弃或是否已被丢弃。函数 GlobalHandle 或 LocalHandle 返回与指定指针相关联的内存对象的句柄。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础 Win32 进程可以完全地使用标准的 C 库函数 malloc 和 free 等来操作内存。在 Windows 的早期版本中使用这些函数,将可能带来问题隐患,但是使用 Win32 API 的应用程序中则不会。举例来说,使用 malloc 分配固定指针将不能使用可移动内存的优点。由于系统可以通过移动物理内存页来自由地管理内存,而不影响虚拟地址,因此内存管理将不再成为问题。类似地,远指针和近指针之间不再有差别。因此,除非你希望使用可丢弃内存,否则完全可以将标准的 C 库函数用于内存管理。Win32 API 提供了一系列的虚拟内存函数来操作或决定虚拟地址空间中页的状态。许多应用程序使用标准的分配函数 GlobalAlloc , LocalAlloc 和 malloc 等就可以满足其需要。然而,虚拟内存函数提供了一些标准分配函数所不具有的功能,它们可以进行下面的这些操作:                     保存进程虚拟地址空间中的一段,保留地址空间并不为它们分配物理存储,而只是防止其他分配操作使用这段空间。它并不影响其他进程的虚拟地址空间。保留页防止了对物理存储的不必要的浪费,然而它允许进程为可能增长的动态数据结构保留一段地址空间,进程可以在需要的时候为这些空间分配物理存储。                     占用进程虚拟地址空间中的保留页的一部分,以使得物理存储(无论是 RAM还是磁盘空间)只对正在进行分配的进程可用。                     指定允许读写存取、只读存取或不允许存取的占用页区域。这和标准的分配函数总是分配允许读写存取的页不同。                     释放一段保留页,使得调用线程在随后的分配操作中可以使用这段虚拟地址。                     取消对一段页的占用,释放它们的物理存储,使它们可以以其他进程在随后的分配中使用。                     在物理内存 RAM 中锁定一个或多个占用页,以免系统将这些页交换到页面文件中。                     获得关于调用线程或指定线程的虚拟地址空间中的一段页的信息。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础                      改变调用线程或指定线程的虚拟地址空间中指定占用页段的存取保护。                     虚拟内存函数对内存页进行操作,函数使用当前计算机的页大小来对指定的大小和地址进行舍入。                     可以使用函数 GetSystemInfo 来获得当前计算机的页大小。函数 VirtualAlloc 可以完成以下操作:                     保留一个或多个自由页。                     占用一个或多个保留页。                     保留并占用一个或多个自由页。你可以以指针所保留或占用的页作为起始地址,或者让系统来决定。函数将指定的地址舍入到合适的页边界。保留页是不可访问的,但占用页可以使用标志位 PAGE+READWRITY , PAGE_READONLY 和 PAGE_NOACCESS 来分配。当页被占用时,从页面文件中分配存储空间,每个页仅在第一次试图对其进行读写操作时被初始化并加载到物理内存中。可以用一般的指针引用来访问由 VirtualAlloc 函数占用的页。函数 VirtualFree 可完成下面的操作:解除对一个或多个页的占用,改变其状态为保留。解除对页的占用并释放与之相关的物理存储,使其为其他进程可用。任何占用页块都可以被解除占用。释放一个或多个保留页块,改变其状态为自由。释放页块使这段保留空间可以为进程分配空间使用。保留页只能通过释放由函数 VirtualAlloc 最初保留的整个块来释放。同时解除对一个或多个占用页的占用并释放它们,将其状态改变为自由。指定的块必须包括由 VirtualAlloc 最初保留的整个块,而且这些页的当前状态必须为占用。

返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.2 Windows2.2 Windows 编程基础 编程基础      函数 VirtalLock允许进程将一个或多个占用页锁定在物理内存 RAM 中,防止系统将它们交换到页面文件中。这保证了一些要求苛刻的数据可以不通过磁盘访问来存取。将一个页锁定入内存是很危险的,因为这限制了系统管理内存的能力。由于可能会将可执行代码交换到页面文件中,可执行程序使用 VirtualLock 将有可能降低系统性能。函数 VirtualUnlock解除 VirtualLock 对内存的锁定。函数 VIrtualQuery 和 VirtualQueryEx 返回关于以进程地址空间中某一指定地址开始的一段连续内存区域的信息。 VirtualQuery 返回关于调用线程内存的信息。 VirtualQueryEx 返回指定进程内存的信息,这通常用来支持调试程序,这些程序常常需要知道关于被调试进程的信息。页区域以相对于指定地址最接近的页边界为界。一般来说,这通过具有下述属性的后续页来进行扩展:所有页具有相同的状态,或为占用,或为保留,或为自由。如果初始页不为自由,区域中的所有页都属于通过调用 VirtualAlloc保留的同一个最初页分配。所有页的存取保护相同,或为 PAGE_READONLY ,或为 PAGE_READWRITE ,或为 PAGE_NOACCESS 。函数 VirtualProtect允许进程修改进程地址空间中任意占用页的存取保护。举例来说,一个进程可以分配读写页来保存易受影响的数据,然后将存取改变为只读或禁止访问,以避免无意中被重写。典型地, VirtualProtect 用于使用 VirtualAlloc 分配的页,但事实上,它也可以用于通过其他分配函数占用页。然而, VirtualProtect 改变整个页的保护状态,而由其他函数返回的指针并非总是指向页边界。函数 VirtualProtectEx 类似于 VirtualProtect ,但函数 VirtualProtectEx 可以改变指定进程的内存的保护状态。改变这些内存的保护状态在调试程序访问被调试进程的内存时非常有用。 返回返回

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础

• 2.3.1 MFC2.3.1 MFC 的概念的概念 • 2.3.2 MFC2.3.2 MFC 的结构体系的结构体系 • 2.3.3 MFC2.3.3 MFC 中常用类简介中常用类简介 • 2.3.4 MFC2.3.4 MFC 应用程序框架的运行机制应用程序框架的运行机制

返回返回

MFC 是一种重要的编程方法,它是微软公司的特定的应用程序包装接口,贯穿了微软的软件产品的设计思想。首先我们应该大致了解 MFC 是什么,它有什么特点。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础

返回返回

2.3.1 MFC 的概念如果你曾经使用过传统的 Windows 编程方法开发应用程序,你会深刻地体会到,即使是开发一个简单地 Windows 应用程序也需要对 Windows 的编程原理有很深刻的认识,同时也要手工编写很多的代码。因为程序的出错率几乎是随着代码长度的增加呈几何级数增长的,这就使得高度程序变得非常困难。所以传统的 Windows 编程是需要极大的耐心和丰富的编程经验的。近几年来,面向对象技术无论是在理论还是实践上都在飞速地发展。面向对象技术中最重要的就是“对象”的概念,它把现实世界中的气球、自行车等客观实体抽象成程序中的“对象”。这种“对象”具有一定的属性和方法,这里的属性指对象本身的各种特性参数。如气球的体积,自行车的长度等,而方法是指对象本身所能执行的功能,如气球能飞、自行车能滚动等。一个具体的对象可以有许多的属性和方法,面向对象技术的重要特点就是对象的封装性,对于外界而言,并不需要知道对象有哪些属性,也不需要知道对象本身的方法是如何实现的,而只需要调用对象所提供的方法来完成特定的功能。从这里我们可以看出,当把面向对象技术应用到程序设计中时,程序只是在编写对象方法进才需要关心对象本身的细节问题,大部分的时间是放在对对象的方法的调用上,组织这些对象进行协同工作。 MFC 的英文全称是 Microsoft Fundation Classes ,即微软的基本类库, MFC 的本质就是一个包含了许多微软公司已经定义好的对象的类库,自从 1993年美国微软公司推出 Visual C++ 1.0后便配套推出了微软基础类库 MFC 1.0( Microsoft Founddation Class )。在 16 位过程化编程之风(以 SDK 编程为标志)还异常猛烈的当时, MFC 1.0却第一个拉开了以面向对象的程序设计方法编制多任务、多进程的 GUI 应用软件系统的序幕。微软公司在 16 位平台上几乎没有停留多久,便陆续推出了 MFC 1.5 , MFC 2.0(含支持 32 位的 Win32 ), MFC3.0, MFC 4.0。 1997 年 5月随着 MFC 1.5 , MFC 2.0(含支持 32 位的 Win32 ), MFC3.0, MFC 4.0。 1997 年 5月 随着 Visual C++ 5.0的问世,人们看到了支持 Web 应用和 ActiveX的 MFC 6.0基础类库系统。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 我们知道,虽然我们要编写的程序在功能是千差万别的,但从本制裁上来讲,都可以化归为用户界面的设计,对文件的操作,多媒体的使用,数据库的访问等等一些最主要的方面。这一点正是微软提供 MFC 类库最重要的原因,在这个类库中包含了 100多个程序开发过程中最常用到的对象。在进行程序设计的时候,如果类库中的某个对象能完成所需的功能,这时我们只要简单地调用已有对象的方法就可以了。我们还可以利用面向对象技术中很重要的“继承”方法从类库中的已有对象派生出我们自己的对象,这时派生出来的对象除了具有类库中对象的特性和功能之外,还可以由我们自己根据需要加上所需的特性和方法,产生一个更专门的,功能更为强大的对象。当然,你也可以在程序中创建全新的对象,并根据需要不断完美对象的功能。正是由于 MFC 编程方法充分利用了面向对象技术的优点,它使得我们编程时极少需要关心对象方法的实现细节,同时类库中的各种对象的强大功能足以完成我们程序中的绝大部分所需功能,这使得应用程序中程序员所需要编写的代码大为减少,有力地保证了程序良好的可调试性。最后要指出的是, MFC 类库在提供对象的各种属性和方法都是经过地谨慎地编写和严格地测试,可靠性很高,这就保证了使用 MFC 类库不会影响程序的可靠性和正确性。 MFC 是很庞大的。例如,版本 7.0中包含了大约 200个不同的类。万幸的是,你在典型的程序中不需要使用所有的函数。事实上,你可能只需要使用其中的 10多个 MFC 中的不同类就可以建立一个非常漂亮的程序。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 该层次结构大约可分为几种不同类型的类:                     应用程序框架                     图形绘制的绘制对象                     文件服务                     异常处理                     结构 List , Array 和 Map                     Internet 服务                     OLE2                     数据库                     通用类MFC 7.0基本类库的基类名为 CObject 。在派生时分成若干组代表某个应用方向,一个组还有数量不等的类。 MFC 7.0基础类库除了可包含在这一幅派生结构图内的全部类以外还有一部分由 Win32 转来的结构、运行对象支持类、简单数据类等独立的类所构成。由于 MFC 7.0庞大的基础类库,你是不可能逐一地对每个类的成员(数据和函数)做详尽地了解。因此你可在理解其基本结构的基础上重点掌握常用类的使用方法,至于其他类可在具体使用时通过Visual C++ 的联机帮助来了解其成员数据和函数的应用。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 2.3.2 MFC 的结构体系MFC 是一个庞大的体系,它所包含的各种类结合构成了“应用程序框架”,并提供了用户接口的标准实现方法。建立Windows 应用程序基于该框架,编程就是在其中填充应用程序所特有的部分。在 Microsoft Visual Studio.MET 中,开发以 AppWizard 创建的一个初应用程序为基础,然后用 AppStudio 设计接口部分,再利用 ClassWizard 将这部分连接到代码中,最后用类库实现应用程序特定的逻辑。MFC 中类可划分为基类、应用程序结构类、窗口类、 OLE 类、数据库类等 10大类,而且在其中的一些大类的基础上又派生出许多子类。基类只包括两个: CObject 和 CRuntimeClass 。其中, CObject 被自然数为基类之父,它是大多数 MFC 类的最终基类,具有串行化支持并可以在运行时获取类有关的信息。基类再下来,主要包括以下几种类: MFC 应用程序结构类,窗口、对话框和控件类,输出(设备文本)和绘图类,简单数据类型类,数组、列表和映射类,文件和数据库类, Internet 和网络类。 OLE 类以及高度和异常类。             MFC 应用程序结构类包含以下这些。     应用程序和线程支持类:包括 CWinApp 和 CWinThread , CWinApp派生 Windows应用程序对象的基类, CWinThread 是线程的基类。     命令发送类: CCmdTarget 和 CCmdUI 比较常见。     文档类:其中 CDocument 作为应用程序文档的基类经常会用到。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础      视图类(结构):其中 CView 。查看文档数据的应用程序视图的基类会经常用到。     框架窗口类:它包括 CFrameWnd 类,是 SDI 应用程序的主框架窗口的基类,而 CMDIFrameWnd 是 MDI 应用程序主框架窗口的基类。     文档模板类:它包括 CDocTemplate ,作为文档模板的基类是经常要用到的。     窗口、对话框控件类里面主要包括框架窗口类、视图类、对话框类、控件类、控件栏类等。             比较常用的简单数据类型类有: CString 、 CTime 、 CSize 、 CPoint 和 CRict 等。             比较常用的数组、列表和映射类型的类有: CArray 、 CList 、 CMap 等。             文件和数据库类型类有: CFile 、 CArchive 、 CDaoWorkSpace 和 CDatabase。             Internet 和网络类型类有: CHttpFilter 、 CAsyncSockect 等。             OLE 类型的类有: COleDocument 、 COleDilalog 、 COleControlModule 等。调试和异常类型类有: CDumpContext 、 CException 等。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 2.3.3 MFC 中常用类简介以下我们将介绍几种常用的 MFC 类。1. CWinApp类CwinApp 类的继承结构是: CObject——CCmdTarget——CWinThread——CWinApp 。用 MFC 类库创建程序时,都需要创建它的派生类。CwinApp 是一个应用程序基类,通过它来耿承Windows 应用程序对象,应用程序对象提供了初始化应用程序(以及它的每一个实例)和运行应用程序所需的成员函数。每个使用微软基本类库( MFC )的应用程序都只能包含一个从 CWinApp继承的对象。当 Windows 调用 WinMain 函数时(注:任何 Windows 应用程序在启动时,第 1 个调用的程序就是 WinMain 函数),这个对象在其他 C++ 全局对象都已经生成并且可用之后才被创建, WinMain 函数是由微软本基类库提供的,其中将 CWinApp 对象定义为全局的。当从 CWinApp继承应用程序类的时候,应重载 InitInstance 成员函数以创建应用程序的主窗口对象。2. CWnd类 CWnd 类 是所有窗口类基类,它直接有 CCmdTarget 类派生,所有 MFC 窗口都是从 CWnd 类派生的,包括框架窗口、视图、对话框和控件。 CWnd 类封装了一个一个窗口句柄,提供操纵窗口所需要的基本功能,并定义了消息处理机制。 CWnd 对象与 Windows 的窗口有所不同,但是两者有紧密联系。 CWnd 对象是由 CWnd的构造函数和析构函数创建或销毁的,而 Windows 的窗口是 Windows 的一种内部数据结构,它是由 CWnd 的 Create 成员函数创建的,而由 CWnd 的虚拟析构函数销毁。 CWnd 类的 DestroyWindow 函数销毁Windows 的窗口,但不是销毁对象。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 CWnd 类和消息映射机制隐藏了 Windows 窗口管理函数 WndProc ,接收到的 Windows 通知消息通过消息映射被自动发送到适当的 CWnd 类的 OnMessage 成员函数。可以派生CWnd 类使程序员能够为应用程序创建相关的 Windows 子窗口:先从 CWnd继承一个类,然后在派生类中加入成员变量以保存与应用程序有关的数据。在派生类中实现消息处理成员函数和消息映射,以指定当消息被发送到窗口时应该如何动作。在这之后,可以经过两个步骤来创建一个子窗口:首先,调用构造函数 CWnd 以创建一个 CWnd 对象,然后调用 Create 成员函数以创建子窗口并将它连接到 CWnd 对象。当用户关闭子窗口时,应销毁 CWnd 对象,或者调用 DestroyWindow 成员函数以清除窗口并销毁它的数据结构。在微软基本类库中,从 CWnd派生了许多其他类以提供特定的窗口类型。这些类有许多,包括 CFrameWnd 、 CMDIFrameWnd 、 CMDIChildWnd 、 CView 和 CDialog ,被用来进一步派生。从 CWnd派生的控件类,如 CButton ,可以被直接使用,也可以被进一步派生出其他类来。其中有一些重要的派生类值得注意:( 1 ) CFrameWnd 类:单文档应用程序主框架窗口的基类,也是其他框架窗口类的基类。( 2 ) CMDIFrameWnd 类:多文档应用程序主框架窗口的基类。( 3 ) CMDIChildWnd 类:所有对话框窗口的基类。 ( 4 ) Cview 类:视图类的基类,其继承结构为 CObject——CCmdTarget——CWnd——Cview ,该类及其派生类用以显示程序的文档内容。在应用程序中,通常文档类代表应用程序已打开的一个文件,而视图类提供该文档的数据的可视化表示形式并接受用户的交互。文档类与视图类的这种密切配合与协调构成了 MFC 应用程序经典的文档 /视图体系结构。 ( 5 ) CButton 类:按钮控件的基类,可视为控件类的代表。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 3. CDocument类Cdocument 类是所有文档类的基类,它直接由 CCmdTarget 类派生,该类及其派生类用以管理应用程序的文档。文档类( CDocument )表示通常用于 Open 命令打开和使用 Save 命令保存的数据类型。它为用户定义的文档提供了基本的函数功能。 CDocument 支持标准操作,如创建、装载和保存文件等,而框架( Frame )则用 CDocument 定义的界面来操作文档。一个应用程序可支持多种类型的文档,例如文本文档和工作表等,每种类型都有一个与之相关的文档模板( CDocTemplate ),这个文档模板指定了该类文档的结构,所使用的资源(如菜单、图标和加速符号表)、用于显示的视图类型和框架窗口,其中的联系是通过在文档中保存相应文档模板对象指针实现的。用户通过与文档相联系的 CView 对象来与文档交互(这在下面的章节中会详细谈到)。视图在框架窗口内生成一个文档图像,并解释作用于该文档上的用户输入。一份文档可以有多个相关的视图——当用户在文档上打开一个窗口时,框架将创建一个视图并将其与文档连接。 文档作为窗口标准命令例程的一部分,接收标准用户界面组件(如 File | Save 命令)的命令。文档在活动视图之后接收命令。如果文档未能处理指定的命令,则将其交给管理它的文档模板。当文档数据被修改时,各个视图都必须反映这些修改。 CDocument 提供了 updateAllViews 成员函数为视图通知这些变化。框架在关闭之前会提示用户必须存储修改后的文件。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 在一个典型的应用程序中生成一个文档,必须做到以下几点:为每种类型的文档从 CDocument 中派生一个类;添加保存在文档数据的成员变量;为阅读和修改文档数据提供成员函数,文档的视图是这些成员函数最重要的用户;在文档类中重载 CObject::Serialize 成员函数,以实现自己定义的磁盘读写模式。在存在邮件支持( MAPI )的情况下, CDocument 类还支持通过邮件发送文档。4. CString类 CString 没有基类。一个 CString 对象由可变长度的一队字符组成。 CString 使用类似于Basic 的语法提供函数和操作符。连接和比较操作符以及简化的内存管理使 CString 对象比普通字符组容易使用。 CString 是基于 TCHAR 数据类型的对象。如果在程序中定义了符号 _UNICODE ,则 TCHAR 被定义为类型 wchar_t ,即 16 位字符类型,否则, TCHAR 被定义为 char ,即 8 位字符类型。在 UNICODE 方式下, CString 对象是 16 位的,在非 VNICODE方式下, CString 对象是 8 位的。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 2.3.4 MFC 应用程序框架的运行机制在 DOS 下,程序的执行是从 main 函数开始的。在 Windows 下,对应的函数是 WinMain。但是,如果浏览 My 程序的所有方法和全局函数,是找不到 WinMain 函数的。 MFC考虑到典型的 Windows 程序需要的大部分初始化工作都是标准化的,因此把WinMain 函数隐藏在应用程序的框架中,编译时自动将该函数链接到可执行文件中。程序员可以重写 WinMain函数,但一般不需要这么做。下面的程序代码给出了 WinMain 函数的代码。其中, _tWinMain 函数在 \DevStudio\Vc\Mfc\src\AppModul.cpp 中定义,它所调用的 AfxWinMain 函数在同一目录下的 WinMain.cpp中定义。名字是 _tWinMain 函数而不是 WinMain ,是考虑到对不同字符集的支持,在 tchar.h 中有 _tWinMain 的宏定义。在 ANSI 字符集下编译时, _tWinMain 就变成 WinMain ,在 Unicode 下编译时, _tWinMain 就变成 wWinMain 。注意: Unicode 是具有固定宽度,统一的文本和字符的编码标准。由于 Unicode采用的是16 位编码,因此可以包含世界各地的收写系统的字符和技术符号(如中文也在 Unicode 之中),从而克服了 ASCII 码在表示多语言文本上的不足之处,扩大了 ASCII 码 7位编码方案的好处。 Unicode 同等地对待所有的字符,并且在表示各种语言的任何字符时既不需要换码序列( escape )也不需要控制代码。 Win32 和 Visual C++ 很好地支持 Unicode 字符集。//tWinMain 函数定义export WinMain to force linkage to this moduleextern int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSR IpCmdLine, int nCmdShow);#ifdef_MAC

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 extern "C" int PASCAL#elseextern "C" int WINAPI#endif_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR IpCmdLine, int nCmdShow){//call shared/exported WinMainreturn AfxWinMain(hInstance, hPrevInstance, IpCmdLine, nCmdShow);}AfxWinMain 函数定义://////////////////////////////////////////////////////////////////////////////////////////////////////////Standard WinMain implementation//Can be replaced as long as 'AfxWinInit' is called firstint AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR IpCmdLine, int nCmdShow){ASSERT(hPrevInstance= =NULL);int nReturnCode= -1;CwinApp* pApp=AfxGetApp();

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 //AFX internal initializttionif(!AfxWinInit(hInstance, hPrevInstance, IpCmdLine, nCmdShow))goto InitFailure;//App global initializations(rare)ASSERT_VALID(pApp);if(!pApp->InitAppolication())goto InitFailure;ASSERT_VALID(pApp);//Perform specific initializationsif (!pApp->InitInstance()){if(pApp->m_pMainWnd !=NULL){TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");pApp->m_pMainWnd->DestroyWindow();}nReturnCode=pApp->ExitInstance();goto InitFailure;}ASSERT_VALID(pApp);nReturnCode=pApp->Run();

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 ASSERT_VALID(pApp);InitFailure:#ifdef_DEBUG//Check for missing AfxLockTempMap callsif(AfxGetModuleThreadState()->m_nTempMapLock !=0){TRACE1("Warning: Temp map lock count non-zero9(%1d). \n",AfxGetModuleThreadState()->m_nTempMapLock);}AfxLockTempMaps();AfxUnlockTempMaps(-1);#endifAfxWinTerm();return nReturnCode;}应用程序执行时, Windows 自动调用应用程序框架内部的 WinMain 函数。如程序所示, WinMain 函数会查找该应用程序的一个全局构造对象,这个对象是由 CWinApp派生类构造的,有且只有一个。它是一个全局对象,因此在程序启动时,它就已经被构造好了。随后, WinMain 将调用这个对象的 InitApplication 和 InitInstance 成员函数,完成应用程序实例的初始化工作。随后, WinMain 调用 Run 成员函数,运行应用程序的消息循环。在程序结束时, WinMain 调用 AfxWinTerm 函数,做一些清理工作。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 注意:每个应用程序必须从 CWinApp 派生出自己的应用程序类,并定义一个全局的对象。该应用程序类包含了 Windows 下应用程序的初始化、运行和结束过程。基于框架建立的应用程序必须有一个(且只能有一个)从 CWinApp 派生的类的对象。在 My 程序中,我们从 CWinApp 中派生出一个 CMyApp 类,并定义了一个全局对象 theApp, CMyApp 类在 My.cpp 中定义。要访问应用程序类构造的对象,可以调用全局函数 AfxGetApp() 。 AfxGetApp() 返回一个指向全局对象的指针。可以通过对它进行强制类型转换,转换为我们派生的应用程序类。比如:CMyApp* pApp=(CMyApp*)AfxGetApp();在 CMyApp 应用程序类中,我们还重载了 CWinApp 的成员函数 InitInstance 。 InitInstance 函数主要完成以下工作:设置注册数据库,载入标准设置(最近打开文件列表等)、注册文档模板。其中注册文档模板过程中隐含地创建了主窗口。接着,处理命令行参数,显示窗口,然后返回、进入消息循环。下面给出了 My 程序的 InitInstance 函数代码。//CMyApp initializationBOOL CMyApp::InitInstance(){AfxEnableControlContainer();//Standard initialization//If you are not using these reatures and wish to reduce the size//of your final executable, you should remove from the following//the specific initialization routines you do not need.

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 #ifdef_AFXDLLEnable3dControls(); //Call this when using MFC in a shared DLL#elseEnable3dControlsStatic(); //Call this when linking to MFC statically#endif//Change the registry key under which our settings are stored.//You should modify this to be something appropriate//such as the name of your company or organization.SetRegistryKey(_T("Local AppWizard-Generated Applications"));LoadStdProfileSettings(); //Load standard INI file options(including MRU)//Register the application's document templates. Document templates//serve as the connection between documents, frame windows and views. CsingleDocTemplate* pDocTemplate;pDocTemplate=new CsingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CmyDoc),RUNTIME_CLASS(CmainFrame), //main SDI frame windowRUNTIME_CLASS(CmyView));AddDocTemplate(pDocTemplate);//Parse command line for standard shell commands, DDE, file openCcommandLineInfo cmdInfo;

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 ParseCommandLine(cmdInfo);//Dispatch commands specified on the command lineif(!ProcessShellCommand(cmdInfo))return FALSE;//The one and only window has been initialized, so show and update it,M_pMainWnd->ShowWindow(SW_SHOW);m_pMainWnd->UpdateWindow();return TRUE;}在 CWinApp 的派生类中,必须重载 InitInstance 函数,因为 CWinApp 并不知道应用程序需要什么样的窗口,它可以是多文档窗口和单文档窗口,也可以是基于对话框的。Run 成员函数WinMain 在初始化应用程序实例后,就调用 Run 函数来处理消息循环。 Run 成员函数不断执行消息循环,检查消息队列中有没有消息。如果有消息, Run 将其派遣,交由框架去处理,然后返回继续消息循环。如果没有消息, Run 将调用 OnIdle 来做用户或框架可能需要在空闲时才做的工作,像后面我们讲到的用户接口更新消息处理等。如果既没有消息要处理,也没有空闲时的处理工作要做,则应用程序将一直等待,直到有事件发生。当应用程序结束时, Run 将调用 ExitInstance 。

《 《 WindowsWindows 编程编程》——》——第第 22 章 章 C++C++ 编程基础编程基础

04/20/2304/20/23民政学院软件学院 蒋国清民政学院软件学院 蒋国清

2.3 MFC2.3 MFC 基础基础 消息循环的流程图如图所示

关闭应用程序用户可以通过选择 File→Exit 菜单或单击主窗口的关闭按钮,关闭主框架窗口,来终止应用程序。此时,应用程序类首先删除m_pMainWnd 主框架窗口对象,然后退出 Run 函数,进而退出 WinMain ,在退出 WinMain 后删除 TheApp 对象。