绪论Windows编程基础课件.ppt
绪论(Windows 编程基础),1 Windows 操作系统的特点1.1 直观的图形化用户界面 Windows 应用程序的外观是由诸如窗口、菜单、工具栏、状态栏、滚动条、对话框等标准图形元素构成的。程序运行中的人机交互操作也都是通过这些标准图形元素进行的。我们将这样的用户界面称为图形化用户界面 GUI(Graphics User Interface)。GUI 使得应用程序的用户界面统一、友好、美观。,1.2 丰富的设备无关操作 Window 程序的输出显示均为图形操作(包括文本操作)。各类复杂的图形操作都是通过与物理设备无关的图形设备接口GDI(Graphics Device Interface)完成的。每个图形操作都是在一个特定的图形设备上下文(Device Context)中完成的。也就是说,通过设备上下文句柄,能够调用图形设备接口 GDI 所提供的相应图形操作进行格式统一而具有特定功能的图形绘制操作,而这些图形操作又可以通过对应的物理图设备驱动的支持,在指定的设备上实现相应的图形输出。下图形象地示意了这种 GDI 图形接口的实现机制:,GDI虚拟设备,1.3 完善的多任务运行环境 Windows 是一个多任务的操作系统。这种多任务表现在多个不同进程(包括同一程序的多个进程实例)的同时运行和同一进程中的多个线程的同时运行。这些同时运行的多任务对系统资源的共享表现在:CPU:通过系统分时实现多任务共享同一 CPU。屏幕:通过可以重叠或平铺分布的窗口实现多个任务的视窗可以共享同一屏幕,用户可以通过切换不同任务的窗口为活动窗口,在不同任务之间进行切换。内存:通过虚拟内存管理实现多任务共享有限的内存资源。多任务之间可以进行手工和自动的数据交换和通信。,其中 Windows 虚拟内存管理的实现如下:进程和内存空间下面给出的图是在 Windows 95 平台上,执行同一个 EXE 文件的两个不同进程时的虚拟内存映射图。,进程A,进程B,Windows 系统代码,Windows DLL,内存映射文件,硬盘,MFC42.DLL,用户.DLL,交换文件,EXE文件,对于每个进程来说,只有低端的 2GB(0-0 x7FFFFFFF)的地址空间是真正属于进程私有的。其中最低端的 4MB 内存空间是禁止访问的。进程运行期间所需要的堆栈、堆和可读/写的全局内存以及应用程序的 EXE 文件和 DLL 文件都被映射到这 2GB 空间内。而高端的 2GB 空间对所有的进程都是一样的,在这一区间存放着所有进程共享的 Windows 核心执行过程,虚拟设备驱动程序(VxD)和文件系统代码以及一些重要的表(如映射页表)都被映射到最高端 1GB(0 xC0000000-0 xFFFFFFFF)空间中。Windows DLL 和内存映射文件在 0 x80000000-0 xBFFFFFFF 的内存空间中。,由于低端的 2GB 内存空间分配给特定的进程,所以一个进程想要改变另一个进程的堆栈、全局内存或者堆空间的内容是不可能的。EXE 和 DLL 代码存放空间都有只读标记,因此,它们被映射到多个进程是没有问题的。然而在最高端的 1GB 空间有重要的 Windows 可读数据,因此,这部分内存很容易受到错误程序的攻击,例如毁坏系统表。在 0 x80000000-0 xBFFFFFFF 地址空间中存放的一个进程的映射文件也可能被另一个进程破坏。,在 Windows NT 中这些问题不会发生,因为在 Windows NT 中,进程只允许访问低端的 2GB 空间,并且这 2GB 的最高端和最低端的 64KB 空间是不允许访问的。同时高端的 2GB 空间中所存放的内容完全受保护。这就是为何提倡使用 Windows NT 的原因之一(Windows 2000 和 Windows XP 有类似的安全机制)。虚拟内存如何工作 为什麽要使用虚拟镜像技术 计算机不可能有数百个 GB 的 RAM(物理内存)和数百个 GB 的磁盘空间能满足多进程(每个进程 4GB)的需要。每个进程的 4GB 空间不会全部使用,更不会同时使用。,如何实现虚拟镜像技术,32 位线性地址分三段,页表目录、页表和页内偏移量。每页 4KB 空间。页以 4KB 为边界,即页的首地址必须是 4KB 的整倍数。每个进程可以获得的虚拟内存空间为 4GB,每一个物理 地址的形成可以解析如下:页表地址=页表目录地址+偏移量(第 22-31 位),共 有1K 个页表。页地址=页表地址+偏移量(地址第 12-21 位),共有 1K*1K=1M 页。物理地址=页地址+偏移量(地址第 0-11 位),共有 1M*4KB=4GB 内存单元,每个页表入口都包含存在位(表示页是否在物理 RAM 中)和读/写位(表示页中内容是否可读/写或只读)。当需要访问此页内容时,根据“存在”位确定是否需要将此 页的内容从磁盘读入到此物理页中。如果页中内容有一段 时间未被访问,则根据虚拟管理的优化算法确定是否将页 中内容交换到磁盘中或直接放弃,使物理页空间可以被新 进程的页使用。在收回页面使用时,根据页的“读/写”位来 确定是将页中内容交换到磁盘中(例如,进程中所有可读/写数据),或直接放弃(例如,程序 EXE 代码和 DLL 代 码,进程中的常量)。,用户可以使用的内存操作函数 使用VirtualAlloc 进行内存的保留和占用,该函数的原型:LPVOID VirtualAlloc(LPVOID lpAddress,DWORD dwSize,DWORD flAllocationType,DWORD flProtect)使用 VirtualFree 收回 VirtualAlloc 保留和占用的内存空间。VirtualFree 的原型:BOOL VirtualFree(LPVOID lpAddress,DWORD dwSize,DWORD dwFreeType);使用GlobalAlloc 函数在Windows 运行时堆中分配空间。HGLOBAL GlobalAlloc(UINT uFlags,SIZE_T dwBytes);使用GlobalLock锁定GlobalAlloc 分配的内存空间一次,并获取操作句柄。每锁定一次LockCount+1。LPVOID GlobalLock(HGLOBAL hMem);,使用GlobalUnlock 将内存空间解锁一次,LockCount1,当LockCount为0时,所分配的内存空间不再被锁定。BOOL GlobalUnlock(HGLOBAL hMem);未锁定的内存地址(句柄)可以用GlobalFree 函数释放。HGLOBAL GlobalFree(HGLOBAL hMem);使用new 分配内存空间。使用delete 释放由new 分配的内存空间。内存映射文件:将一个地址范围直接映射到相应的文件。当进程访问相应的内存页时,系统将分配 RAM,并从磁盘 中读入数据或将内存中数据写入磁盘,它可以用于进程间 共享。,访问资源:资源是包含在 EXE 和 DLL 代码中的,因此会 占用虚拟内存空间,而且这些空间在进程的生存期内是 不会被改变的,这就使得我们很容易读取一个资源。获 取资源的函数原型:HGLOBAL LoadResource(HMODULE hModule,HRSRC hResInfo);参数:hModule 包含所取资源的模块句柄,NULL 表示从进程中取资源。hResInfo 所取资源的句柄。返回一个全局内存句柄 HGLOBAL 可以安全地把它当作访问存放资源的内存空间的索引。例如:,LPVOID lpvResource=(LPVOID):LoadResource(NULL,:FindResource(NULL,MAKEINTRESOURCE(IDB_REDBLOCK),RT_BITMAP);其中FindResource 用于确定一个指定的资源位置,其原型:HRSRC FindResource(HMODULE hModule,LPCTSTR lpName,LPCTSTR lpType);参数:hModule 包含所取资源的模块句柄,NULL 表示从进程中 取资源。,lpName 所取资源的名字串。本例中是使用宏定义 MAKEINTRESOURCE 将资源标识 IDB_REDBLOCK 转换为资源名。pType 所取资源的类型。本例中使用的 RT_BITMAP 表示所取资源是一个位图资源。,1.4 灵活的消息处理机制 队列化消息输入Windows 操作系统将应用程序控制运行所需要各类信息以消息的形式放在一个消息队列中,这个队列由操作系统管理。应用程序只通过读取消息队列中的不同消息控制运行。Windows 操作系统有一个系统消息队列,每个应用程序有一个自己的消息队列,应用程序的消息来源:输入消息:如键盘、鼠标输入。这类消息通过系统消息队列被送入应用程序消息队列。,控件消息:用于与 Windows 的控件对象进行双向通信,实现控件状态的变化。这类消息不通过系统消息队列,可以通过应用程序消息队列,也可以直接发送到控件对象上。系统消息:由系统管理事件(例如系统时钟)引起的消息。这类消息中有些要通过系统消息队列送到应用程序消息队列,如 DDE(Dynamic Data Exchange)消息;而另一些消息直接送入应用程序消息队列,如创建窗口消息。用户消息:由程序员自己定义在应用程序中发出,并在应用程序中响应处理的消息。这类消被直接送入应用程序消息队列。,支持队列特征的消息驱动模型Windows 操作系统主要包括三个基本内核元件:GDI(Graphics Device Interface):负责虚拟图形设备的操作,例如,屏幕绘图和打印。KERNEL:支持与操作系统密切相关的功能(如进程加载,文本切换,文件I/O,内存管理线程管理等)。USER:为所有的用户界面对象提供接收和管理所有输入消息、系统消息,并把它们发给相应窗口的消息队列。,事件驱动的程序设计MS-DOS 应用程序主要是采用顺序的、关联的、过程驱动的程序设计方法。因此,过程的执行顺序是由程序直接控制,并强制用户以某种不可更改的模式进行工作,交互性差。Windows 应用程序则是采用了由事件发生来控制程序运行逻辑的设计方法,由于事件发生是随机的,没有预定的顺序,所以用户就可以按照各种需要的、合理的顺序来安排程序的流程。每个事件发生都会在对应的队列中放入一条消息。程序开始运行后,总是从消息队列中读取消息等待事件的发生,并根据消息做出相应的响应后,返回等待事件发生的状态,直至响应了程序退出消息导致程序运行结束退出。,支持应用程序间数据交换Windows 支持应用程序间通过以下途径进行数据交换:动态数据交换 DDE(Dynamic Data Exchange),剪切板(Clipboard),对象链接和嵌入 OLE(Object Linked and Embeded),组件对象模型 COM(Component Object Model)和分布式组件对象模型 DCOM(Distributed Component Object Model)。,1.5 简便的动态链接库(DLL)应用 库是为应用程序提供各种功能和资源的最重要的途径,其中动态链接库对于支持多任务的功能和资源共享和提高内存的使用效率更为有效。Windows 平台为动态链接库的创建、安装和调用提供了有效的支持,使得动态链接库的应用更加简便、可行。所以动态链接库的开发和应用成为应用程序开发的重要手段之一。,2 Windows 应用程序的特点2.1 事件驱动方式的程序设计模式 这种程序是由许多完成特定功能的子流程组成,在程序启动运行之后,没有一个固定的执行流程,而是由用户的操作的结果(事件)确定(驱动)子流程的执行,包括程序的结束。2.2 窗口程序设计模式 Windows 应用程序的基本单位不是过程和函数,而是窗口。这些窗口都具有标准的 Windows 窗口界面风格,都是由一系列的具有标准风格的界面对象元素(如:窗口、图标、标题栏、菜单、工具栏、滚动条、状态栏、对话框、控件、消息框等)组合而成的,下图是典型的 Windows 程序窗口界面。Windows 应用程序的运行可视为这些窗口按事件驱动方式相互作用的结果。,2.3 面向对象的程序设计模式Windows 应用程序符合典型的面向对象结构的程序。程序为用户提供的所有可视操作界面在程序内部都可视为一个 Windows对象,用户对这些可视对象的操作通过事件驱动模式触发相应Windows 对象的可调用方法。Windows 程序的执行过程本身就是窗口和其他对象频繁创建、处理和消亡的过程。程序执行过程中的消息发送可以理解为一个窗口对象向别的窗口对象请求对象服务的过程。因此,用面向对象的方法进行 Windows 程序设计与开发是极其方便、合理的。,为了便于设计创建具有风格一致的窗口界面,微软公司在Win32 函数库和 C+类库中为创建和操作上述标准界面元素提供了大量的函数和 C+类。学习和掌握有关这些标准界面元素的描述、创建的数据结构和程序结构、C+类及其关系,是创建一个 Windows 程序的重要基础之一,对于理解这些标准界面元素封装在类库中的类的工作原理也是十分必要的,同时也有助于创建自己特殊风格的界面元素以适应特定程序所需要的特定风格的界面。,2.4 资源共享MS-DOS 是单任务操作系统,DOS 应用程序在运行时独占系统的全部资源,如显示器、内存,在程序结束时才释放资源。Windows 是一个多任务的操作系统,同时运行的各个应用程序必须共享系统为程序运行提供的资源。系统资源是有限的,如果使用资源的应用程序在资源使用完毕后不释放,就会造成系统资源的枯竭,从而导致程序的运行异常,或干扰其他程序的运行,甚至导致死机。因此 Windows 应用程序共享资源的基本模式如下:向 Windows 系统请求资源;,使用资源;释放资源给 Windows,以供别的程序使用。对于内存或其他硬件设备(键盘、鼠标、计数器、屏幕、串/并接口等)资源,一般不允许应用程序直接管理和访问,而由 Windows 系统控制,以便向所有的应用程序提供公平的资源,保证不中断的运行。如果应用程序确实需要对某些硬件进行直接访问,则应当通过 Windows 提供的特定 API 函数实现安全的访问。,2.5 程序和资源分开在 DOS 程序中,界面设计编码工作和功能设计编码都是在源程序中完成的。在 Windows 应用程序中,实现界面的可视对象(如菜单、对话框、位图等)都被从源程序中分离出来,放在资源文件(.rc)中,并通过资源编译器将这些资源编译后,再链接到应用程序的可执行文件或动态链接库文件中。程序与资源分离的优点:降低内存需求:资源可以不随着应用程序一起全部装入内 存,只有当这些资源被使用时才被装入自己的数据段,并不 驻留在应用程序的数据段;当内存紧张时,可废弃这些资源(从内存中退出),待使用时再次自动装入。,便于统一管理和重复利用 应用程序与界面有一定的独立性,有利于软件的国际化,3 Windows 程序结构随着对软件的功能、性能的要求不断提高和快速、正确、经济地创建软件这一目标的追求,在 Windows 操作系统中为创建应用程序提供的支持也在不断发展。所以使用不同的编程支持创建 Windows C+程序的方法有多种,最常用的方法是选择直接使用 Windows API 函数编写的 Win 32 窗口程序,或使用基类库 MFC 编写的面向对象的窗口程序,或使用.NET Framework 的 编写 CLR 窗口程序。显然,这三种方法反映了 Windows 操作系统的软件创建支持的发展过程,并且在当前的 Windows 操作系统中均可以使用。其中 Win 32 窗口程序和 MFC 窗口程序为非托管类 Windows 程序,CLR 窗口程序为托管类 Windows 程序。,其中,托管类 Windows 程序是本课程学习和掌握的重点;而非托管类程序只进行简单介绍和了解。由于使用的编程支持不同,不同类型的 Windows C+窗口程序在源代码的形式结构上有较大的区别,但都遵循着 Windows 应用程序共同运行结构。对于初学者,要深刻理解程序的形式结构和运行结构,首先从 Win 32 窗口程序入手是十分必要的。,3.1 Win 32 窗口程序 Win 32 窗口程序是由主函数和窗口函数两个基本部分组成:主函数该函数确定了 Win 32 窗口进程从执行开始直至执行结束的主流程。主函数名不允许改变。主函数的典型代码包括:定义主窗口类结构变量,并初始化;使用已经初始化的窗口类结构变量注册主窗口类别;创建已经成功注册的主窗口;显示并更新主窗口;启动消息循环,不断地接收消息,并分发到相应的窗口 函数去处理,直至收到关闭主窗口消息,结束程序执行。,窗口函数窗口函数是由用户定义,由系统调用的回调函数。它的用途是处理窗口消息,以完成程序需求的各种特定的功能。在 Windows 应用程序的运行过程中,通常总是先创建一个主窗口,然后根据执行功能的需要,可能会有子窗口的创建和撤消(如用于交互操作的对话框的创建和撤消)。每个窗口都应有一个对应的窗口函数,用于处理对应窗口消息的响应操作,因此,在一个 Windows 应用程序中通常会有多个窗口函数,但主窗口函数只有一个,换言之,Windows 应用程序至少应有一个主窗口函数。主窗口函数必须在创建前通过主窗口类变量进行注册,而子窗口函数一般是在主窗口函数中被注册、创建和撤消的。,从上述分析可以在以下几个方面归纳窗口函数的特点:窗口函数的作用是处理由消息循环发送来的各类消息。窗口函数是回调函数,即由系统通过消息调用。窗口函数名可以随意定义。程序中的每一个窗口(响应处理某些消息)必须有一个 对应的窗口函数。一个程序中可以有多个窗口函数,但主窗口函数只能有 一个,该窗口函数必须通过主窗口类的定义和注册与主 窗口相关联。,资源定义文件在 Windows 应用程序中,除了主函数和窗口函数外,通常还需要定义应用程序中需要的资源。资源包括应用程序所能够使用的一类预定义资源中的一个对象,例如,字符串资源、加速键表、菜单、工具栏、对话框、位图、光标、图标、版本信息和用户自定义资源等。在 Windows 程序设计中,全部资源都被单独分离出来放在扩展名为.rc 的资源文件中。在编译过程中,该资源文件经资源编译器编译后,生成.res 二进制资源文件。在程序的链接过程中,.res 将与.obj 文件一起链接成可执行文件.exe。,3.1.1 生成一个典型 Win32 窗口程序在 Visual Studio 2005/2008 集成开发环境中进行如下操作:创建一个使用 Visual C+的 Win32 Project 程序项目,例如,名为“MyWin”的项目,点击 按钮弹出如下操作界面:点击 按钮,即可完成该 Win32 程序项目的创建。,查看项目的目录和相应的主要文件在项目目录中生成了一个以项目命名的项目管理文件,例如本例中的 MyWin.sln;同时还创建了一个存放项目各类源文件的子目录例如本例中的 MyWin 子目录。在该子目录中生成的项目源文件主要包括:以项目命名的头文件、源文件和资源描述文件,例如,本例的 MyWin.h、MyWin.cpp 和 MyWin.rc;定义资源 ID 的文件 resource.h;预编译文件 stdafx.h 和 stdafx.cpp。,查看和分析源程序中关键代码主源文件 MyWin.cpp 中除主函数外,还包含有四个函数,它们的原型如下:ATOMMyRegisterClass(HINSTANCE hInstance);BOOLInitInstance(HINSTANCE,int);LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);INT_PTR CALLBACK About(HWND,UINT,WPARAM,LPARAM);其中和分别用于完成程序主窗口的定义、注册和创建、显示、更新。主函数在顺序调用这两个函数后,进入程序消息循环的执行过程中,直至程序接收到退出消息后,结束程序执行。,int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow)MyRegisterClass(hInstance);if(!InitInstance(hInstance,nCmdShow)return FALSE;/Main message loop:,while(GetMessage(以便在消息循环中,通过消息触发被系统调用。,回调函数 About 是用于响应该程序的菜单条目“About”发出消息的窗口函数,与该函数相关联的对话框窗口的创建和显示是在 WndProc 响应相应菜单条目消息时,由 API 函数 完成的:DialogBox(hInst,MAKEINTRESOURCE(IDD_ABOUTBOX),hWnd,About);从上述代码的分析,不难看出,根据程序设计功能的需要可以添加实现相应功能的操作函数。如果函数的操作与窗口相关联,则函数需要定义具有回调机制的窗口函数,同时可能需要在资源描述文件中添加所需要的新资源或修改已有资源。新添加的函数可以包含在主源文件中,也可以包含在功能类型划分的新增源文件中。,例如,在本例中添加从键盘接收文本信息,并将成功接收的文本信息显示在窗口的中央位置的功能。为此,需要在原有程序代码的基础上进行如下编辑工作:添加一个接收输入文本信息的对话框模板 IDD_INPUTBOX:其中用于接收输入文本信息的文本编辑框控件的 ID 定义为 IDC_TEXT。,在源文件 MyWin.cpp 中添加一个创建和使用对话框模板 IDD_INPUTBOX 的函数 Input 的声明和定义:INT_PTR CALLBACK Input(HWND,UINT,WPARAM,LPARAM);/Message handler for input box.INT_PTR CALLBACK Input(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)UNREFERENCED_PARAMETER(lParam);switch(message)case WM_INITDIALOG:return(INT_PTR)TRUE;case WM_COMMAND:switch(LOWORD(wParam),case IDOK:GetDlgItemText(hDlg,IDC_TEXT,szInputText,80);EndDialog(hDlg,LOWORD(wParam);InvalidateRect(GetParent(hDlg),NULL,TRUE);return(INT_PTR)TRUE;case IDCANCEL:EndDialog(hDlg,LOWORD(wParam);InvalidateRect(GetParent(hDlg),NULL,TRUE);return(INT_PTR)TRUE;break;return(INT_PTR)FALSE;,在菜单资源 IDC_MYWIN 中添加发出输入文本操作消息的菜单条目 IDM_INPUT:在源文件 MyWin.cpp 中添加一个接收、存放输入文本信息的字符串数组变量:wchar_t szInputText80;在窗口函数 WndProc 的 switch case 结构中添加选择菜单条目 IDM_INPUT 发出命令消息的处理分支:,case WM_COMMAND:switch(wmId)case IDM_INPUT:DialogBox(hInst,MAKEINTRESOURCE(IDD_INPUTBOX),hWnd,Input);break;在窗口函数 WndProc 的窗口重画消息 WM_PAINT 处理分支中添加在窗口中央位置显示输入文本信息的代码:,case WM_PAINT:hdc=BeginPaint(hWnd,查看和分析资源文件中定义的资源本例程包括了以下资源:一个加速(Accelerator)键表 IDC_MYWIN 用于定义程序运行可以使用的加速键。一个对话框(Dialog)模板 IDD_ABOUTBOX 用于描述 About 对话框窗口的所有图形特征。一个大图标(Icon)资源 IDI_MYWIN 和一个小图标资源 IDI_SMALL 提供程序运行的不同状态时所需要的图标图形。一个菜单(Menu)资源 IDC_MYWIN 用于描述主窗口中缺省菜单界面的图形和逻辑层次结构。,一个字符串表(String Table)资源用于定义程序运行时的图形界面中各种需要的字符串。上述所有已经存在的资源都可以根据程序功能的设计需要,以图形方式进行编辑、修改,还可以根据需要增加新的资源。,编译、链接并运行程序项目的编译和链接状态有调试(Debug)和发布(Release)两种。调试(Debug)状态会在项目目录中创建一个 debug 子目录,存放可执行文件;同时在源文件子目录中创建一个 Debug 子目录,存放所有编译、链接过程的中间文件。发布(Release)状态会在项目目录中创建一个 release 子目录,存放可执行文件;同时在源文件子目录中创建一个 Release 子目录,存放所有编译、链接过程的中间文件。,Win 32 窗口程序的编译和链接工作流程如下图所示:,C+编译,资源编译,链接,文本编辑,资源编辑,3.1.2 Win32 窗口程序的运行结构,_tWinMain()MSG msg;WNDCLASS wc;RegisterClass(,WndProc(hwnd,msg,wParam,lParam)switch(msg)case WM_CREATE:case WM_COMMAND:case WM_PAINT:case WM_CLOSE:case WM_DESTROY:default:return DefWindowProc();return 0;,人机交互,操作系统产生,3.2 MFC 窗口程序MFC 窗口程序是典型的面向对象的程序,程序是由实现程序设计功能的类定义代码和创建类对象代码组成。与 Win 32 窗口程序不同,MFC 窗口程序中没有像主函数那样确定进程从执行开始直至执行结束的主流程的代码。MFC 为程序中的类定义提供丰富的基类,它们可以程序中直接使用或派生出新类后使用。这些基类分为两种:一种基类具有消息映射能力,这些类和其派生类的对象能够响应各种由于用户交互操作,系统管理或程序运行所产生并发给它们的消息,从而完成该消息所请求实现的操作;而另一种基类不具有消息映射能力。程序中构成程序运行结构框架的类均为具有消息映射能力的类。典型的 MFC 窗口程序的结构框架一般可以分为三种:,多文档视图(MDI)框架、单文档视图(SDI)框架和基于对话框(Dialog Based)框架。以单文档视图(SDI)框架为例,构成程序框架有四个类,它们的 MFC 基类是:CWinApp、CFrameWnd、CView 和 CDocument。与这些框架基类在 MFC 的类定义结构中的派生层次关系如下图所示:图中各类的功能如下:,CObject,CObject:提供了对象动态性、诊断性和持续性支持,因此该类是所有需要这三大特性的 MFC 类的根基类。CCmdTarget:CObject 的直接派生类,它增加了响应命令消息的能力。因此它是所有需要响应消息的 MFC 类的直接或间接基类,当然是所有程序框架类的直接基类。CWinTread:CCmdTarget 的直接派生类,它增加了支持线程创建、启动、运行和退出功能。线程是进程的最小单位,因此该类是应用程序类 CWinApp 的直接基类。CWnd:CCmdTarget 的直接派生类,它增加了支持窗口定 义、创建、显示更新、注销和窗口消息响应的功能。因此它是 MFC 的各种窗口类(包括主窗口类 CFrameWnd)的直接或间接基类。,CDocument:CCmdTarget 的直接派生类,它增加了支持文 档的各种操作。CWinApp:CWinTread 的直接派生类,它增加了进程的创 建、启动、消息循环的启动和进程的退出。CFrameWnd:CWnd 的直接派生类,它增加了进程主窗口所需要的菜单、子窗口管理等各种功能。CView:CWnd 的直接派生类,它增加了与 CDocument 类的关联操作。CWinApp、CFrameWnd、CView 和 CDocument 是构造典型的文档视图结构程序项目的框架类。但 MFC 窗口程序并不直接使用这些类构成程序框架,而使用它们的派生类,其优点是:,继承了基类的基本功能;继承了已经确定的由基类关联建造的应用程序框架;可以方便地增加不同应用程序框架类的特定新功能,特别重要的是借助虚函数和动态链编实现的动态多态性。在 CWinApp 中封装了与应用程序相关的进程启动、消息循环启动、消息的获取和分发和程序退出等功能。所有具有消息映射能力的类定义中都包含一个消息映射表。消息映射表既可以方便地在消息与处理消息的类成员函数之间建立起对应关系,同时在类的派生层次中建立了消息映射的传递机制。这就保证了消息能够在类的整个定义层次都能得到正确地对应关系。,通过类的消息映射表实现消息的处理是 CCmdTarget 类的成员函数 OnCmdMsg 完成的,该成员函数被声明为虚函数。由于所有具有消息映射能力的类都是 CCmdTarget 类的直接或间接派生类,所以 CCmdTarget:OnCmdMsg 可以在派生类被重新定义。这就使编程者在该虚函数在 CCmdTarget 类定义的基本功能基础上,还可以根据需要设计程序运行期间消息在各个类对象之间的传递和响应路径。例如单文档视图框架程序中消息的传递和响应的缺省路径是:视图类-文档类-框架窗口类-应用程序类就是通过重新定义 CFrameWnd:OnCmdMsg 确定的。,不难看出,通过上述编程机制就可以使 MFC 窗口程序中所有需要处理消息类都能简洁、易于理解的方式定义消息处理操作和建立消息与处理操作的对应关系,同时又可以保证消息能在需要响应消息的类对象之间按合理地顺序传递并得到处理。,3.2.1 生成一个典型 MFC 窗口程序在 Visual Studio 2005/2008 集成开发环境中 创建一个使用 Visual C+的单文档视图 MFC Application 程序项目,例如,名为“Hello”的项目:,点击 按钮弹出如下操作界面:点击 按钮,即可完成该 MFC 程序项目的创建。,查看项目的目录和相应的主要文件在项目目录中生成了一个以项目命名的项目管理文件,例如本例中的 Hello.sln;同时还创建了一个存放项目各类源文件的子目录例如本例中的 Hello 子目录。在该子目录中生成的项目源文件主要包括:Hello.h、Hello.cpp 应用程序类定义和实现文件;MainFrm.h、MainFrm.cpp 主框架窗口类定义和实现文件;HelloDoc.h、HelloDoc.cpp 文档类定义和实现文件;HelloView.h、HelloView.cpp 视图类定义和实现文件;resource.h 定义资源 ID 的文件;,Hello.rc 资源描述文件;stdafx.h、stdafx.cpp 预编译文件。在该子目录还建立了子目录 res 用于存放资源辅助文件。,查看和分析源程序中关键代码与类定义相关的头文件和源文件都具有相同的,典型的类定义代码结构。这里不对这些代码进行普遍地分析,而只对类的消息映射表和个别成员函数进行分析。应用程序类 HelloApp在头文件 Hello.h 中:class CHelloApp:public CWinApp DECLARE_MESSAGE_MAP()/消息映射表声明;extern CHelloApp theApp;/应用程序类全局对象声明,在源文件 Hello.cpp 中:消息映射表的定义代码如下:BEGIN_MESSAGE_MAP(CHelloApp,CWinApp)ON_COMMAND(ID_APP_ABOUT,&CHelloApp:OnAppAbout)/Standard file based document commands ON_COMMAND(ID_FILE_NEW,&CWinApp:OnFileNew)ON_COMMAND(ID_FILE_OPEN,&CWinApp:OnFileOpen)/Standard print setup command ON_COMMAND(ID_FILE_PRINT_SETUP,&CWinApp:OnFilePrintSetup)END_MESSAGE_MAP()应用程序类的全局对象定义代码如下:,CHelloApp theApp;应用程序类的初始化虚函数的重新定义代码如下:BOOL CHelloApp:InitInstance()CWinApp:InitInstance();/Register the applications document templates.Document/templates serve as the connection between documents,frame/windows and views CSingleDocTemplate*pDocTemplate;,pDocTemplate=new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CHelloDoc),RUNTIME_CLASS(CMainFrame),/main SDI frame window RUNTIME_CLASS(CHelloView);if(!pDocTemplate)return FALSE;AddDocTemplate(pDocTemplate);/Parse command line for standard shell commands,DDE,file/open CCommandLineInfo cmdInfo;ParseCommandLine(cmdInfo);,/Dispatch commands specified on the command line.Will/return FALSE if app was launched with/RegServer,/Register,/Unregserver or/Unregister.if(!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;,从上述代码,可以看出应用程序类的初始化虚函数的作用完成进程开始时的初始化工作,然后进入进程的消息循环(程序运行流程的主体)。应用程序类的初始化虚函数的调用和后续所进行的操作都包含在一个隐含的 MFC 全局函数 AfxWinMain 中。该函数在由系统隐含调用的主函数中被调用:_tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,_in LPTSTR lpCmdLine,int nCmdShow)/call shared/exported WinMainreturn AfxWinMain(hInstance,hPrevInstance,lpCmdLine,nCmdShow);,主框架窗口类 CMainFrame在头文件 MainFrm.h 中:class CMainFrame:public CFrameWnd DECLA