MFC提供了一个小工具Tracer.exe来帮助调试Windows-Based的程序,Tracer可以在Output或Console窗口中显示MFC Library的内部操作信息,以及应用程序的Warning和Error消息,你可以按照需要来查看它们。Tracer可以经常对所出现的问题发出警告,并可以提供错误的详细解释。
OutPut窗口是指在调试运行状态下,Visual Studio最下方(缺省状态)窗口,OutPut窗口有“调试”“组建”“在文件1中查找”等分窗口。
上述知识是从下面的文章中得到的。具体参见上面一段红色文字(是从该文中摘出来的)。
Visual C++ 基本原理
// Name: Easyright
// Date: 8-1-2003
// Homepage: http://www.easyright.net
// Email: [email protected]
FAQ
问:阅读以下文章需要具备哪些知识?
答:只要会开机就行了,如果大家有C++和面向对象(Object-Oriented)的基础知识,会有事半功倍的效果。
问:必须具备哪些软件?
答:Windows 98, Windows NT, Windows 2000中的任意一种,另外再加上Visual C++ 5.0或6.0。由于我没有测试过Windows 95和Visual C++ 5.0以下的版本,所以不知道他们可不可用。
问:为什么用Visual C++?
答:因为VC功能强而多。
基本概念
心情随笔:其实这一章是最枯燥的,概念又多,本来我不想写这一章的,但为了照顾初学者,我觉得还是有必要讲一 下。由于这一章是属于”入门篇”的,所以大家只需要了解以下内容就行了,不需要深入研究其原理,到了”高级篇”时,我们还会重新仔细分析其原理的,希望大 家不会被这一章的内容吓跑,有问题就去本站的留言本留言吧。
首先我们要了解以下概念:
应用程序(Application),他就是由指令(Instruction)组成的可以运行的文件。
进程(Process),有时和应用程序的意思一样,但在通常的情况下,进程是指一个正在运行的应用程序,正因为这样,进程由以下部分组成:
1、一个可以执行的程序
2、位于内存(Memory)中的私有地址空间
3、系统资源(System Resource),例如文件(File), 管道(Pipe), 通讯端口(Communications Port), 信号(Semaphore)
4、至少还要有1个线程(Thread), 线程是最基本的执行单位。
因为多个进程是可以同时存在时,所以Windows操作系统(Operating System)必须给进程提供保护,以防止他们冲突。
物理内存(Physical Memory),即你的计算机的实际内存,例如我现在用的电脑的内存是128M,物理内存的容量是达不到程序的要求的,于是就产生了虚拟内存(Virtual Memory)。
虚拟内存(Virtual Memory), 不是真正的内存,它通过映射(Map)的方法,使可用的虚拟地址(Virtual Address)达到4G(2的32次方),每个应用程序可以被分配2G的虚拟地址,剩下的2G留给操作系统自己用。在Windows NT中,应用程序可以有3G的虚拟地址。简单的说,虚拟内存的实现方法和过程是:
1、当一个应用程序被启动时,操作系统就创建一个新进程, 并给每个进程分配了2G的虚拟地址(不是内存,只是地址);
2、虚拟内存管理器(Virtual Memory Manager)将应用程序的代码(Code)映射到那个应用程序的虚拟地址中的某个位置,并把当前所需要的代码读取到物理地址中。注意,虚拟地址和应用程序代码在物理内存中的位置是没有关的;
3、如果你有使用动态链接库(Dynamic-Link Library,即DLL)的话,DLL也被映射到进程的虚拟地址空间,在有需要的时候才被读入物理内存;
4、其他项目(例如数据,堆栈等)的空间是从物理内存分配的,并被映射到虚拟地址空间中;
5、应用程序通过使用它的虚拟地址空间中的地址开始执行,然后虚拟内存管理器把每次的内存访问映射到物理位置。
如果大家看不明白上面的步骤也不要紧(似乎超出了入门篇的范围),但大家要明白以下两点:
1、应用程序是不会直接访问物理地址的;
2、虚拟内存管理器通过虚拟地址的访问请求,控制所有的物理地址访问;
使用虚拟内存的好处是:简化了内存的管理,并可以弥补物理内存的不足;可以防止在多任务(Multitasking)环境下的各个应用程序之间的冲突。
线程(Thread),是最基本的执行单位,CPU时间就是分配给每个线程的。每个进程一开始时只有一个线 程,但每个线程都可以产生出其他线程,前者叫做父线程(Parent Thread),后者叫做子线程(Child Thread)。每个执行的线程都有自己的虚拟输入队列(Virtual Input Queue),用来处理来自硬件、处理器(Processor)或操作系统的消息(Message)。这些队列都是异步的,也就是说,当处理器发送一个消 息给另外一个线程的队列时,发送函数不用等待其他线程处理该消息就可返回,而接收消息的线程可以等到该线程准备好时再访问并处理接收到的消息。
多线程(Multithread),如果一个进程中有多个线程同时存在,就叫做多线程了。
多任务(Multitasking),即多个程序看起来好像是在同时执行,其实并不是同时的,只不过因为时间太短,人类感觉不出来而已。其原理是操作系统分配给每个线程一个非常短(大约百分之秒)的时间片,每个线程轮流切换执行,这个过程叫做场境转换(Context Switching)。
场境转换(Context Switching),是指:
1、运行一个线程直到该线程的时间片用完,或者这个线程必须等待其他的资源;
2、保存这个线程的场境;
3、取出其他线程的场境;
4、只要有线程在等待执行,就会不停的重复以上过程。
Raw Input Thread(RIT), 是指用来接收所有由键盘和鼠标产生的事件(Event)的线程,它是一个特殊的系统线程,每当RIT接收到处理器发出的硬件(Hardware)事件,它 就把那些事件放到相应线程的虚拟输入队列中。因此,应用程序的线程通常是不用等待它的硬件事件的。
事件驱动(Event-Driven)编程,Windows-based的应用程序运行后,就会一直等待,直 到有用户发布命令(例如:按一个按钮或选中一个菜单)之类的事件发生,这就叫做事件驱动编程(Event-Driven Programming)。它同DOS下的应用程序的最大区别就是:DOS下的应用程序是通过命令行加参数的方法来控制应用程序的执行,而Windows -based的应用程序是通过图形用户界面(GUI)来控制应用程序的执行。用户所产生的事件,在程序里就会转化为消息,不同的事件产生不同的消息,从而 可以产生不同的响应。
终于讲完这一节了,大家看得明白吗?如果不明白的话,那就一字一句的从头到尾再看一遍吧。如果还不明白,那就请跳过这一节吧,我在后面的章节中还会逐步解释这些概念的。在本章的最后一节我将会举一个具体的程序来说明Windows-based应用程序的结构和组成元素。
以下是本节出现的专业名词
应用程序 = Application
指令 = Instruction
进程 = Process
内存 = Memory
系统资源 = System Resource
文件 = File
管道 = Pipe
通讯端口 = Communications Port
信号 = Semaphore
线程 = Thread
物理内存 = Physical Memory
虚拟内存 = Virtual Memory
映射 = Map
虚拟地址 = Virtual Address
虚拟内存管理器 = Virtual Memory Manager
代码 = Code
动态链接库 = Dynamic-Link Library,即DLL
数据 = Data
堆栈 = Stack
多任务 = Multitasking
父线程 = Parent Thread
子线程 = Child Thread
多线程 = Multithread
场境转换 = Context Switching
虚拟输入队列 = Virtual Input Queue
处理器 = Processor
操作系统 = Operating System
消息 = Message
队列 = Queue
Raw Input Thread = RIT
事件 = Event
硬件 = Hardware
事件驱动 = Event-Driven
事件驱动编程 = Event-Driven Programming
图形用户界面 = GUI
Windows下的程序的结构和组成元素
Windows下的程序的基本组成元素是代码, 用户界面资源(User Interface Resource)和动态链接的库模块(Library Module)。
代码,是应用程序的主要内容,Windows下的应用程序必须要有两个函数:
1、WinMain,它为操作系统提供了进入点(Entry Point),是所有Windows-Based应用程序都必须要有的函数。它也用来创建初始Window和启动Message检索;
2、Window Procedure,它用于处理所有从操作系统发送到Window的Message,每一个Window都有一个相关联的Window Procedure。Window Procedure用来决定Window的Client Area(即客户窗口,例如Notepad中用来写字的空白部分)显示什么以及如何响应用户的输入。Window Procedure处理Message时,既可以用专门添加的代码来处理Message,也可以直接把Message传递给默认的Window Procedure——DefWindowProc。一个Windows-Based应用程序可以包含多个不同名的Window Procedure。
用户界面资源,菜单(Menu),对话框(Dialog box)等图形用户界面的元素,就叫做资源。它们被当成模板(Template)储存在相应的可执行文件或DLL文件的只读(Read-Only)区域,当有需要时,Windows就调用这个资源区域并动态创建所需要的GUI元素。主要有以下几种资源:
Accelerator(快捷键表), 储存快捷键和相应的命令
Bitmap(位图),一种图形格式
Diablo Box,包含对话框的控件(Control), 布局和属性的细节
Icon(图标),一种特殊的位图
Menu(菜单),包含菜单及其选项的文本和布局
String Table(字符串表),储存字符串及其ID
Toolbar(工具栏),包含工具栏的布局和按钮的位图
Version(版本),储存程序的状态信息,例如程序名,作者,版权,版本号等
Cursor(光标),包含用于绘制光标的特殊的位图
库模块,主要是指在运行时可以被动态链接的二进制文件,即DLL。
默认的Window Procedure——DefWindowProc,是Windows系统提供的一个函数,用于处理某些通用的Win32-based应用程序的 Messages(例如最大化、最小话窗口,显示目录等)。如果DefWindowProc不能处理该Message,那么它就被忽略。
当一个应用程序被启动时,将会按顺序发生下列事件(上一节也提到过这个问题)
1、操作系统创建一个新进程和一个起始线程;
2、应用程序的代码被载入内存;
3、DLL也被载入内存(如果有的话);
4、从物理内存分配其他项目(例如数据,堆栈等)的空间,并被映射到虚拟地址空间中;
5、应用程序开始执行。
在Windows-Based应用程序中,Windows是应用程序和用户之间传递信息的主要方法。Windows-Based的应用程序为了接收从系统队列传来的Message,是通过以下方法实现的:
1、当Windows-Based的应用程序启动后,操作系统和这个应用程序就通过进入点(WinMain函数)联系起来。
2、应用程序创建一个或多个Windows,每个Window都包含有一个Window Procedure函数,用来决定Window显示什么以及Window如何响应用户的输入。
3、有专门的代码将Message队列中的Message循环检索出来,并传递给相应的Window Procedure,而不是直接传给Window。这样就可以使应用程序在Message被送到Window之前预先处理它。
到了下一节,我们将会用一个简单的源程序说明以上元素和步骤。
以下是本节新出现的专业名词
用户界面资源 = User Interface Resource
库模块 = Library Module
进入点 = Entry Point
客户窗口 = Client Area(例如Notepad中用来写字的空白部分)
菜单 = Menu
对话框 = Dialog box
模板 = Template
只读 = Read-Only
控件 = Control
快捷键表 = Accelerator
位图 = Bitmap
图标 = Icon
字符串表 = String Table
工具栏 = Toolbar
版本 = Version
光标 = Cursor
动态链接 = Dynamic Linking
源程序示例
本节列出了一个简单的源程序,来说明上两节的内容。请大家结合上两节的内容来看看下面的源程序,不需要完全看懂,只用理解大概的框架和流程就行了,注意黑体字部分。源程序如下:
// 摘自http://msdn.microsoft.com/library/partbook/win98dh/thewinmainprocedure.htm
// 包含头文件windows.h
#include <windows.h>
// 预先声明Message Handler,可以叫做任何名字,这里是MyWindowProcedure
LRESULT CALLBACK MyWindowProcedure(HWND,UINT,WPARAM,LPARAM);
// 以下是所有Windows程序都需要的WinMain函数
// WinMain主要用来实现三个功能:
// 1. 注册Window Class;
// 2. 在内存中创建Window并初始化Window的属性;
// 3. 创建一个Message Loop来检查Message Queue中有没有该Window的Message。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpszCmdLine,int nCmdShow)
{
static char szAppName[] = “WinHello”; // 定义一个字符串
HWND hwnd; // 定义一个Window Handle变量
MSG msg; // 定义一个Message结构的变量,用来储存Message的信息
WNDCLASS wc; // 定义一个Window Class数据结构,用来储存Window Class的属性
//下面这段代码用来定义Window的属性,例如Message Handler的地址、窗口背景、光标和图标等
wc.style=CS_HREDRAW|CS_VREDRAW; // 设置style: 当窗口改变大小时就重新绘制窗口
wc.lpfnWndProc=(WNDPROC)MyWindowProcedure; // 设定Window Procedure
wc.cbClsExtra=0; // 用来储存Class Structure后的额外的数据,这里不需要
wc.cbWndExtra=0; // 用来储存Window Instance后的额外的数据,这里不需要
wc.hInstance=hInstance; // Window Procedure所在的Instance
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); // class的图标
wc.hCursor=LoadCursor(NULL,IDC_ARROW); // class的光标
wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); // 背景刷
wc.lpszMenuName=NULL; // 菜单资源的名字,这里没有
wc.lpszClassName=szAppName; // 应用程序的名字
// 注册Window,通过调用API函数RegisterClass来实现
// 注册Window Class的一个目的就是将Window和Window Procedure关联起来
RegisterClass(&wc);
// 注册Window Class后,WinMain就调用CreateWindow函数来创建应用程序的Window
hwnd=CreateWindow(
szAppName, // 已注册的Class名字
“Hello, World – Windows_98 Style”, // Window名字
WS_OVERLAPPEDWINDOW, // Window风格
CW_USEDEFAULT, // Window起点的X坐标
CW_USEDEFAULT, // Window起点的Y坐标
CW_USEDEFAULT, // Window的宽度
CW_USEDEFAULT, // Window的高度
HWND_DESKTOP, // 父窗口的handle
NULL, // 菜单的handle
hInstance, // 应用程序instance的handle
NULL // window-creation数据的指针
);
// 以下两条语句用来显示Window
ShowWindow(hwnd,nCmdShow);
UpdateWindow(hwnd);
// 用while循环语句来检索并发送Messages
// 从Message Queue中检索Message,并将它放到变量msg中。
// 当收到”WM_QUIT”这个Message时,GetMessage函数就返回0,循环结束。而且WinMain函数也结束,程序终止。
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg); // 将Virtual-Key Messages转化为Character Messages
DispatchMessage(&msg); // 将Message发送到Window Procedure
}
return msg.wParam;
}
// MyWindowProcedure函数处理WM_PAINT和WM_DESTROY这两个Message,然后必须调用DefWindowProc去处理其他Message
LRESULT CALLBACK MyWindowProcedure(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
PAINTSTRUCT ps; // 定义一个PAINTSTRUCT结构的变量,用来储存绘制Window的Client Area的信息
HDC hdc; // 定义一个HDC变量
LPCTSTR text=”Welcome!”; // 定义一个LPCTSTR类型的字符串指针
// 用switch语句来处理WM_PAINT和WM_DESTROY这两个Message
switch(message)
{
case WM_PAINT:
// 下面5条语句是用来在屏幕上输出文字的,我们在后面的章节会详细讨论这个问题的,这里就不多说了
hdc=BeginPaint(hwnd,&ps);
RECT rect;
GetClientRect(hwnd,&rect);
TextOut(hdc,(rect.right-rect.left)/2,(rect.bottom-rect.top)/2,text,strlen(text));
EndPaint(hwnd,&ps);
return 0;
// 处理退出消息
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
// 调用默认的Window Procedure,使所有Message都可以被处理
return DefWindowProc(hwnd,message,wParam,lParam);
}
运行上面程序的步骤:
1、选菜单 File–>New…–>Projects–>Win32 Application
2、在Project Name中输入vchack_01_002_003(其它名字也行)
3、其他地方就保留默认值就行了,然后按”OK”
4、选中”An empty project”,然后按”Finish”
5、再按次”OK”
6、按Toolbar上的按钮”New Text File”新建一个空白文件
7、将以上源程序复制到那个空白文件中,然后按Toolbar上的按钮”Save”来储存文件,文件名为vchack_01_002_003.cpp
8、按左下角的”FileView”,然后按”vchack_01_002_003 files”旁边的”+”号展开这个目录
9、在”Source Files”上按鼠标右键,选”Add Files to Folder…”
10、选中vchack_01_002_003.cpp这个文件,然后按”OK”
11、选”Build”菜单中的”Build vchack_01_002_003.exe”
12、选”Build”菜单中的”Execute vchack_01_002_003.exe”来运行这个程序
以下是本节新出现的专业名词
类 = Class
窗口类 = Window Class
数据结构 = Data Structure
消息处理器 = Message Handler
实例 = Instance
句柄 = Handle
工程 = Project
MFC简介
微软基础类库(Microsof Foundation Class Library)和Visual C++提供了一个创建各种各样应用程序的环境,并简化了其中部分工作。MFC Library是Class的集合,大约有250个Class,在很大程度上扩展了C++语言;MFC Library也是一个应用程序框架(Application Framework),它定义了应用程序的结构(当然你也可以用源程序一行一行地写出自己的应用程序结构,不过这样比较麻烦),并可以处理应用程序的一些常规任务。
如果你想用MFC进行程序开发,首先你必须熟悉MFC所包含的Class以及各个Class之间的关系。MFC Class是有层次的(MFC的层次图请看http://msdn.microsoft.com/library/devprods/vs6/visualc/vcmfc/_mfc_hierarchy_chart.htm,请大家务必要看,最好把它保存下来,以便日后查找),有些Class可以直接使用,而有些Class是作为其他Class的基类(Bass Class)一般不直接使用。为了学习的方便,一般将MFC Class划分为以下几个种类:
CObject-Derived Classes
Application Architecture Classes
User-Interface Classes
General-Purpose Classes
ActiveX Classes
Database Classes
Internet Classes
Global Afx Functions
以上划分的种类之间决不是相互独立的,大多数的MFC Classes是直接或间接从CObject Class派生的,CObject Class是MFC Library中最基本的Class。
下一节我将会分别对以上几个种类的Classes做一个简单的介绍,然后我还会分别用1至2章来详细介绍上面的几种Classes。这一节的内容比较少,请大家仔细看看MFC的层次图。MFC的命名规则是:Class名以C开头,其他地方顾名思义。
以下是本节新出现的专业名词
微软基础类库 = Microsof Foundation Class Library
微软基础类 = Microsof Foundation Class (即MFC)
应用程序框架 = Application Framework
基类 = Bass Class
MFC的层次、分类和作用
心情随笔:本节有很多专业名词,其实这些单词从字面上并不难理解,例如”Document”,中文是” 文本”的意思,但假如把”Document Class”直接翻译成”文本类”的话,可能会把很多人搞混淆了,我觉得”文本类”比”Document Class”更难理解,正因为如此,所以我决定不把那些容易搞混淆的专业名词直译成中文了。我非常反感市面上的一些电脑书完全直译国外作品,可能是由于翻 译者的电脑水平不行,把一些专业名词凭空想象,例如有的把”Serialization”翻译成”序列化”,有的又翻译成”串行化”,完全脱离了原意。
本节将简要介绍MFC所包含的主要几种Class,大家最好要记住MFC的分类和各个Class的作用(特别是CObject派生的(CObject-Derived) Classes,应用程序结构(Application Architecture) Classes,用户界面(User-Interface) Classes这三种,一定要记住),这是后面章节的基础。大家现在无需知道各个Class的使用方法,因为我会在后面详细说明的。注:以下内容有部分摘自MSDN,其实我也记不住那么多Class,一般是有需要才去查帮助文件的。
一、CObject派生的(CObject-Derived) Classes
CObject是MFC大多数Class的基类,它主要提供了一些基本功能,主要包括:
Serialization,指把对象(Object)从存储媒体(例如磁盘上的文件)中读出或写入的过程;
Run-time Class信息,指从CObject派生的对象包含有在运行时可以访问的信息;
诊断输出,指CObject提供了一些输出函数,这些函数可以输出程序执行过程中的一些信息,可以帮助你调试程序。
从CObject派生的类为MFC应用程序提供了基本的结构和功能,重要的有以下几种:
类别 | 基类 | 描述 |
Command Targets | CCmdTarget | 用于处理用户请求 |
Applications | CWinApp | 代表应用程序的核心 |
Documents | CDocument | 包含应用程序的数据集 |
Windows | CWnd | 主要用于图形用户界面(GUI)的对象,可以处理常见的Windows Messages |
Frames | CFrameWnd | 用于应用程序的主要Window框架 |
Views | CView | 用于显示数据并于Document对象交互 |
此外,CObject-Derived Class还包括用于菜单、文件服务、图形等方面的Class。
MFC也包含了一些不是从CObect派生的类,这些类相对来说可以节省开销,主要分为以下几种:
1、用于常规编程的实用类,例如:CString, CTime, CTimeSpan, CRec, CPoint, CSize;
2、MFC结构的支持类,例如CArchive, CDumpContext, CRuntimeClass, CFileStatue, CMemoryState
3、用户定义指针的集合类,例如CTypedPointerArray
二、应用程序结构(Application Architecture) Classes
应用程序结构Class代表应用程序的基本结构元素,主要包括CWinApp, CDocument, CCmdTarget和CWinThread。当应用程序开始运行时,这些Class是最先被初始化的,它们都有很重要的作用。
1、CWinApp, 代表应用程序自己,所有的MFC应用程序都从CWinApp派生一个Class。根据应用程序框架(Framework)的种类,应用程序的对象(Object)要完成以下工作:
(1) 初始化(Initialize)应用程序
(2) 建立Document Template结构
(3) 循环检索Message Queue中的Message并派送这些Message到相应的地方
(4) 当应用程序退出时要进行”清理”工作
2、CDocument, 它是使用Document/View结构的应用程序中的Document的基类。这里的Document代表程序中的数据,是一个抽象概念,我们在开发程序时必须考虑数据如何储存到Document中。
3、CCmdTarget,它是MFC的Message映射的基础Class,从CCmdTarget派生的类可以成为Command Messages的目标。Command Messages是指由用户选择菜单或按钮等行为产生的Messages。
4、CWinThread,它的成员函数可以使MFC应用程序创建和管理线程。
三、用户界面(User-Interface) Classes
用户界面Classes主要包含Windows-based应用程序的一些可视性元素,例如:窗口、菜单、对话框、控件(Control)等,它还封装(Encapsulate)了Windows Device Context对象和Graphics Device Interface(GDI)对象。
用户界面Class包括CWnd, CView, CGdiObject和Menu这几个主要Class:
CWnd,它是所有MFC Windows的基类,它定义了Window的基本功能和Window对大部分Message的默认响应。CWnd可以直接用来派生其他Class,但通常情况下,Class是从CWnd派生的Class派生的,从CWnd派生的Class主要有:
CFrameWnd,主要用于单文档界面(Single Document Interface, 例如写字板之类的程序,一次只能打开一个Window);
CControlBar,是工具栏,状态栏等控件的基类;
CDialog,提供对话框的功能;
CButton, CListBox, CScrollBar等,主要用于按钮,列表框,滚屏栏等控件。
CView,是Document/View(一种应用程序的结构,下节再讲)应用程序的视图的基类;
CGidObject,它包含一些用于显示输出的对象(例如Pen, Brush, Font等),使MFC应用程序可以创建和使用这些对象。GDI最大的好处就是提供了设备无关性(Device-Independent),使到开发人员无需考虑不同设备的问题。
CMenu,主要用于提供菜单界面,通过CMenu,应用程序可以在运行时动态改变菜单的内容。
四、常规用途(General-Purpose) Classes
General-Purpose Classes包括各种各样的数据类型,常用的有:
CFile,用于文件的输入/输出
CString,用于管理字符串变量
CException,用于处理Exception
CByteArray, CIntArray, CStringArray, CStringList, CObList, 用于数据结构,例如数组和列表
CPoint, CSize, CRect, CTime, CTimeSpan,杂项
五、ActiveX Classes
ActiveX Classes可以简化ActiveX的编程和ActiveX API的访问,ActiveX的主要作用和功能是:
创建ActiveX控件和ActiveX控件容器
通过自动化(Automation),是一个程序控制另一个程序
创建包含有多种数据类型(例如文字、图片、声音等)的文档,既复合文档
创建可以嵌入复合文档的OLE Object
使用拖放(Drag-and-Drop)方式可以在两个应用程序之间复制数据
ActiveX Class的分类如下:
ActiveX Control Classes
包括COleControlModule, COleControl, CConnectionPoint, CPictureHolder, CFontHolder, COlePropertyPage, CPropExchange, CMonikerFile, CASyncMonikerFile, CDataPathProperty, CCachedDataPathProperty, COleCmdUI, COleSafeArray
Active Document Classes
包括CDocObjectServer, CDocObjectServerItem
ActiveX-related Classes
包括COleObjectFactory, COleMessageFilter, COleStreamFile, CRectTracker
Automation Classes
包括COleDispatchDriver, COleDispatchException
Container Classes
包括COleDocument, COleLinkingDoc, CDocitem, COleClientItem
OLE Server Classes
包括COleServerDoc
OLE Drag-and-Drop And Data Transfer Classes
包括COleDropSource, COleDataSource, COleDropTarget, COleDataObject
OLE Common Dialog Classes
包括COleDialog, COleInsertDialog, COlePasteSpecialDialog, COleLinksDialog, COleChangeIconDialog, COleConvertDialog, COlePropertiesDialog, COleUpdateDialog, COleChangeSourceDialog, COleBusyDialog
创建ActiveX比较难,我会在”提高篇”中详细讨论的。
六、数据库(Database) Classes
数据库编程是非常枯燥的,但我们不得不承认数据库非常有用,连接数据库然后访问数据是常用的数据库编程方法。MFC提供了一些类,这些类可以通过开放式数据库连结(Open Database Connectivity, 即ODBC)和数据访问对象(Data Access Object, 即DAO)来操作数据库。
Database Classes主要包括CDatabase, CDaoDatabase, CRecordset, CDaoRecordset。
CDatabase或CDaoDatabase的Object代表一个和数据源(Data Source)的连接,通过这个Object就可以操作数据源了。这里的数据源是指数据库中的数据的实例(Instance)。
CRecordset或DaoRecordset的Object代表从数据源中选中的数据的集合,叫做Recordset。CRecordset和DaoRecordset的Object有两种形式:
Dynasets, 动态的,假如数据库被更新,Recordset也同步被更新;
Snapshot,静态的,它只反映了在Recordset被调用时的状态,不会随着数据库的更新而更新。
CDaoRecordset还可以直接代表数据库的表(Table)。
七、Internet Classes
Internet Classes不但可以用于Internet,还可以用于Intranet(企业内部网)。MFC包括WinInet APIs(提供客户端的Class)和Internet Server API(即ISAPI,提供服务器端的Class)。
客户端的Class主要有以下几个:
CInternetSession, 创建并初始化一个或多个同步的Internet Session(会话),它有3个主要函数GetHttpConnection, GetFtpConnection和GetGopherConnection(这3个函数的作用大家可以顾名思义)。
CHttpConnection, 管理应用程序对HTTP服务器的连接。
CFtpConnection, 管理应用程序的FTP连接,它包含了一些用于搜索远程目录和文件的函数。
CGopherConnection,管理应用程序的Gopher连接,它也包含了一些用于搜索不同类型文件的函数。
CFileFind,它是CFtpFileFind和CGopherFileFind的基类,提供了搜索和定位的功能,并可返回文件的信息,它们都还支持通配符查询。
服务器端的Class主要有以下几个:
CHttpServer, 可用于创建和管理一个服务器扩展(Server Extension)DLL,也叫做Internet服务器应用程序(Internet Server Application,即ISA)。ISA一般用来扩展一个Internet服务器的能力。
CHttpServerContext, 被CHttpServer用来封装单个客户端请求的实例(Instance)。
CHttpFilter, 这个Class可以用来创建一个具有过滤客户数据功能的DLL。
CHttpFilterContext,被CHttpFilter用来封装单个客户通知(Notification)的实例(Instance)。
CHtmlStream, 封装HTML数据缓冲区(Buffer),该Buffer是被CHttpServer用来应答客户的。
八、全局Afx函数(Global Afx Functions)
Global Afx Functions不属于任何Class,它们以Afx开头,可以在应用程序的绝大多数地方被直接调用(这点和Class的成员函数有很大不同)。常用的全局Afx函数有:
AfxAbort(), 无条件中断应用程序
AfxMessageBox(), 显示一个消息框
AfxGetApp(), 返回一个指向Project的CWinApp Object的指针
AfxGetAppName(), 返回应用程序的名字,类型为一个指向字符串的指针
AfxGetMainWnd(), 返回指向主框架窗口(Main Frame Window)的指针
AfxGetInstanceHandle(), 返回当前应用程序的实例(Instance)的句柄(Handle),即HINSTANCE
以下是本节新出现的专业名词
派生 = Derive
连续化 = Serialization
对象 = Object
集合类 = Collection Classes
框架 = Frame
框架 = Framework
重载 = Override
初始化 = Initialize
Document
Command Messages
封装 = Encapsulate
控件 = Control
设备环境 = Device Context
图形设备接口 = Graphics Device Interface (GDI)
单文档界面 = Single Document Interface
设备无关性 = Device-Independent
异常或例外 = Exception
ActiveX控件 = ActiveX Control
ActiveX控件容器 = ActiveX Control Container
自动化 = Automation
拖放 = Drag-and-Drop
数据源 = Data Source
实例 = Instance
企业内部网 = Intranet
客户端 = Clien-Side
服务器端 = Server-Side
会话 = Session
服务器扩展 = Server Extension
Internet服务器应用程序 = Internet Server Application,即ISA
通知 = Notification
缓冲区 = Buffer
主框架窗口 = Main Frame Window
实例 = Instance
句柄 = Handle
Document, View和Application Framework
在MFC中,Document, View和Application Framework是3个非常重要的概念。顾名思义,Application Framework就是应用程序框架,你可以用这个框架来建立自己的Windows程序,可以节省不少时间。你也可以不用框架而用手工一行一行的写出源代码,这样做的话工作量就太大了。如果你用Application Framework的话,框架就会自动产生一些源程序代码和标准的用户界面,你需要做的工作就是提供剩余的代码,完成特定的任务。
另外,用MFC编程还要掌握一种重要的结构,即Document/View结构。在这里,Document是指用户正在使用的数据,它是一个Data Object;View是指用户所见到的Document的视图,它是一个Window Object。例如在Excel中,同一数据可以制成不同的报表图(例如饼状图,条形图),而且当数据改变时,报表图也随之改变。使用Document/View结构可以利用Application Frame以及MFC的很多好处。而不使用Document/View结构,对于某些简单的程序可以提高性能,并可减少程序的大小。总的来说,Document/View结构就是通过CDocument和CView来为Document和View提供框架。
MFC中的应用程序主要可以分成两类,即SDI(单文档界面,例如记事本)和MDI(多文本界面,例如 Word)。SDI应用程序一次只能打开一个文档框架窗口,而MDI应用程序在一个主框架窗口中可以有多个子窗口,这些子窗口可以包含不同类型的文档。 MDI比较复杂,我会在《提高篇》中再详细讨论这个问题的,在《入门篇》中我们只讨论SDI。
在SDI应用程序中,主要有以下Object(结合上一节的内容有助于理解):
1、Document:从CDocument派生,代表应用程序的数据;
2、View:从CView派生,代表应用程序数据的”外貌”,用户通过View来察看和操作Document;
3、Frame Window:从CFrameWnd派生,提供了用来显示View的文档框架窗口(Document Frame Window)。在SDI中,Document Frame Window也就是应用程序的主框架窗口(Main Frame Window),View就是显示在Frame Window里面的;
4、Document Template:在SDI中是从CSingleDocTemplate派生的,CSingleDocTemplate又是从CDocTemplate派生的,主要用于创建和管理某种类型的Document,每个Document Template创建和管理一个Document;
5、Application:从CWinApp派生,控制上面的4种Object,并指定应用程序的行为,例如初始化等。Application Object也用来响应用户的行为(例如由用户产生的Command Message)。
在MFC应用程序中,并不是需要以上所有的Object,以上那些Object是可以按照不同的规律来组合使用的。例如在非Document/View结构的程序中,有以下两种情况:
1、一个CWinApp Object和一个对话框(要Modal的,即类似文件打开那种对话框),在这种应用程序中,对话框用来显示和储存数据;
2、一个CWinApp Object、一个Main Frame Window(CFrameWnd)和一个View,在这种应用程序中,View用来定位数据储存和显示的地方。
注意:在非Document/View结构的程序中,一般是以重载CWinApp::InitInstance函数开始的,重载CWinApp::InitInstance函数的目的就是创建对话框或窗口。
操作系统、应用程序和应用程序组件之间的通讯是通过不同种类的Message来实现的。例如,当创建一个应用 程序的实例时,操作系统会发送一系列的Message给应用程序,应用程序就会响应相应的Message来初始化自己。键盘和鼠标也会使操作系统产生 Message并把这些Message发送给相应的应用程序。用户界面组件(例如按钮)也会产生Message并将Message发送给他们的父窗口。最 重要的两种Message是Window Message和Command Message。MFC通过CWnd和CWnd的派生类(例如:CView, CFrameWnd等)来提供对Window Message的支持,通过从CCmdTarget派生的类来提供对Command Message的支持。
Application Framework会把Message和处理该Message的函数联系起来,这样MFC就可以把Message映射到处理该Message的函数。每个Windows Message都有一个预先定义的宏(Macro),包括一个隐含的ID和处理函数的名字;而每个Command Message的Macro包括一个指定的ID和处理函数的名字。请看下面的源程序:
BEGIN_MESSAGE_MAP(CMyView, CView) //这是一个Macro,标志Message映射的开始,注意参数为Message映射的Class(这里是CMyView)及其基类(这里是 CView)的名字。这样的话,假如在CMyView中找不到该Message的处理函数,Framework还会在CView中继续寻找该 Message的处理函数。
ON_WM_CREATE() //这是一个处理Window Message的宏,不需要Message的ID和它的处理函数的名字作为参数(因为这两个参数是隐含的),在这里,ON_WM_CREAT所处理的 Message是WM_CREATE,该Message的处理函数是OnCreate。大家分析一下宏(ON_WM_CREATE),Message (WM_CREATE)和Message的处理函数(OnCreate)这三者之间的命名规则。
ON_COMMAND(ID_APPLY_SEQUENCE, OnApplySequence) // 这是一个处理Command Message的宏,需要Message的ID(ID_APPLY_SEQUENCE)和处理该Message的函数的名字(OnApplySequence)这两个参数
END_MESSAGE_MAP() //这是结束Message映射的宏
你可以用Visual自带ClassWizard或者WizardBar这两个工具来添加Message映射,也可以用手工的方法来添加Message映射,我会在后面的章节中详细讨论Message的问题的。
以下是本节新出现的专业名词
应用程序框架 = Application Framework
单文档界面 = Single Documnet Interface, 即SDI
多文档界面 = Multiple Documnet Interface, 即MDI
文档框架窗口 = Document Frame Window
主框架窗口 = Main Frame Window
宏 = Macro
用AppWizard来创建第一个应用程序
几个小提示:
1、在使用Visual C++时,大家千万不要忽略了鼠标右键的功能,它会根据不同的情况来给出不同的快捷菜单,十分方便;
2、如果你在源程序发现了不明白的地方(例如函数,Macro,关键字等),你就把光标停留在他们上面,然后按”F1″键就可以直接跳到相关的帮助文件了;
3、你可以根据自己的需要来定制界面,Visual C++会”记住”你所做的改变的;
4、把鼠标停留在函数,Macro,关键字等地方或者双击它们,也会有些小作用,大家自己体会吧。
Visual C++提供了很多向导来帮助你完成各种各样的程序,以后在需要使用向导时,我会详细介绍向导的使用方法的。现在我就举个例子,来说明AppWizard的使用方法。
步骤如下:
1、运行Visual C++,选择”File”菜单中的”New”命令,会出现一个”New”对话框;
2、在”New”对话框中选中”Project”,然后选”MFC AppWizard (exe)”,在”Project Name”中输入”vchack_01_004_001″,在”Location”中可以改变Project的目录,其他地方保留默认值就可以了,然后按”OK”
3、在”MFC AppWizard - Step 1″对话框中,选择”Single documnet”(因为我们现在要创建的是SDI应用程序,如果要创建MDI应用程序那就要选”Multiple document”了;如果要创建对话框类型的应用程序那就要选”Dialog based”),其他地方保留默认值,然后按”Next”;
4、在”MFC AppWizard - Step 2 of 6″对话框中,直接按”Next”;
5、在”MFC AppWizard - Step 3 of 6″对话框中,去掉”ActiveX Controls”的选中符号,然后按”Next”;
6、在”MFC AppWizard - Step 4 of 6″对话框中,直接按”Next”;
7、在”MFC AppWizard - Step 5 of 6″对话框中,你会看到”How would you like to use the MFC library”,如果你选择”As a shared DLL”,这样产生的可执行文件较小,但在运行时需要DLL文件(Mfc42.dll和Msvcrt.ll),也就是说,这个程序在没有DLL文件 (Mfc42.dll和Msvcrt.ll)的电脑上是不能运行的。如果你选择”As a statically linked library”,产生的可执行文件较大但不需要DLL文件(Mfc42.dll和Msvcrt.ll)的支持。在这里我们选择”As a shared DLL”,然后按”Next”;
8、在”MFC AppWizard - Step 6 of 6″对话框中,按”Finish”;
9、在”New Project Infomation”对话框中,会显示你刚刚创建的Project的信息,按”OK”,AppWizard就会自动创建一些开始文件并返回到主界面。如果按”Cancel”,你就可以返回上面的步骤;
10、在主界面中的左边,你可以在”ClassView”,”ResourceView”,”FileView”中切换(我用的是Visual C++ 6.0),如果你用的是Visual C++ 5.0,你还会看到一个”InfoView”。展开这些窗口里面的”+”号,看看AppWizard为你创建了些什么东西;
11、在”Build” 菜单,选”Build vchack_01_004_001.exe”来创建一个可执行的exe文件;
12、在”Build” 菜单,选”Execute vchack_01_004_001.exe”来运行这个程序。
大功告成,AppWizard为你建立了以下文件:
*.dsw,是所有Project的总和,DSW是Developer Studio Workspace的简写;
*.dsp,是单个Project文件,DSP是Developer Studio Project的简写;
*.cpp,源程序;
*.h,头文件;
*.rc,资源文件;
另外,还有一个Debug目录或Release目录,包含可执行文件和其他一些编译文件。
如果你想关掉这个Poject的话,就选”File”菜单中的”Close Workspace”。选”Open Workspace”可以打开一个Project。
以下是本节新出现的专业名词
向导 = Wizard
应用程序框架 = Application Framework
单文档界面 = Single Documnet Interface, 即SDI
多文档界面 = Multiple Documnet Interface, 即MDI
文档框架窗口 = Document Frame Window
主框架窗口 = Main Frame Window
宏 = Macro
调试应用程序(上)
当你创建应用程序时,你可以选择创建Debug版或Release版的应用程序,它们之间的区别是它们使用了 不同的DLL文件,Debug版包含了一些调试信息,一般是没有经过优化的,而且有证书限制,不能用于发行;而Release版是经过优化的,用于正式发 行时使用,一般不用于调试。创建一个Debug版的Project的步骤是:
1、然后选择”Project”菜单中的”Settings”;
2、在左上角的”Settings For”中选择”Win32 Debug”,然后按”OK”;
3、在”Build”菜单选择”Set Active Configuration”;
4、编译你的程序。
在编写程序时,大家一般都会碰到两种错误,一种是语法或拼写错误,VC的Compiler会查出这类错误。还有一种错误就是逻辑错误,Compiler不能检查出这种错误,不过Visual C++提供了十分强大的调试功能,来帮助你检查出此类错误。有关Debug的命令可以在以下菜单中找到:
1、”Build”菜单中的”Start Debug”,用于开始调试;
2、当”Debug” 正在进行时,”Build”菜单会变为”Debug”菜单,用于控制程序的执行;
3、”View”菜单中的”Debug Windows”,用于显示几种不同的Debug窗口;
4、”Edit”菜单中的”Breakpoints”,用于控制断点。
另外,在Debug过程中,还会出现一个浮动的”Debug”工具栏,可以方便你的调试工作。如果”Debug”工具栏没有自动出现的话,你也可以手工调出它,方法是:在工具栏的空白处按鼠标右键,然后在弹出菜单中选择”Debug”。
在Debug过程中,你选择一些特殊的窗口用于显示各种不同的调试信息,它们分别是:
1、Output,用于显示关于Build的过程的信息;
2、Watch,用于显示变量或表达式的名字和值;
3、Variaables,用于显示变量的信息;
4、Registers,用于显示CPU寄存器的内容;
5、Memory,用于显示当前内存的内容;
6、Call Stack,用于显示所有未返回的函数的堆栈;
7、Disassembly,用于显示原程序的汇编语言代码。
以上窗口的都可以通过浮动的”Debug”工具栏上的按钮调出。你也可以通过”Tools”菜单上的”Option”,然后选”Debug”来修改以上窗口的显示选项。
在Debug过程中,你还可以使用一些对话框,他们的名字和作用如下:
1、Breakpoints,位于”Edit”菜单下,用于显示和控制断点;
2、Exceptions,位于”Debug”菜单下,用于显示系统和用户定义的Exceptions,并可以指定调试器如何处理这些Exception;
3、QuickWatch,位于”Debug”菜单下,用于显示或修改变量和表达式;
4、Threads,位于”Debug”菜单下,用于显示和控制应用程序的可以Debug的Thread。
从上面的介绍可以看出Visual C++的Debug功能的强大,足以令Unix或Linux程序员羡慕不已。下面就来简单介绍一下各种Debug工具的使用方法:
一、设置断点(是指程序暂停执行的位置)
1、在源程序中设置断点
把光标移到你想使程序中断的地方,然后按工具栏上的”Build MiniBar”上的”Insert/Remove Breakpoint”按钮来设置断点,设置断点后在那行源程序的左边会出现一个红色的点。如果一句源程序超过了一行,那你就必须在这句源程序的最后一行设置断点。
2、在函数的开头设置断点
在工具栏上的”Standard”上的”Find”对话框中输入函数的名字,找到那个函数,然后设置断点。
3、在函数的Reture位置处设置断点
在”View”菜单中,选”Debug Windows”->”Call Stack”,然后将光标移到你想中断的函数,再设置断点。
4、在Label处设置断点
和”在函数的开头设置断点”的方法类似,只不过输入的是Label的名字。
5、激活和取消一个断点
在断点处按鼠标右键,然后选”Enable/Disable Breakpoint”,如果你取消一个断点,那个被取消的断点的标记会变为空心的。
6、察看断点
选”Edit” 菜单下的”Breakpoint”,就会显示所有断点的列表。如果你选中一个断点,然后按”Edit Code”就会跳到断点所在的源程序的位置。如果你清除某个断点前面的复选框(用鼠标或者选中该断点然后按按空格键),那个断点就会被取消。如果复选框的标记变为*号,那就说明当前的平台不支持断点。
注意:取消(Disable)和删除(Remove)断点的含义不同。
二、控制程序的执行
1、当应用程序的执行暂停在断点处时,可以用”Debug”菜单中的”Step Into”命令来执行下一条语句,执行完下一条语句后,程序又会被暂停执行。如果下一条语句是一个函数,那么就会执行该函数内的第一条语句。
2、还可以在源程序或Debug窗口中使用”Step Over”, “Run To Cursor”,”Step Into Specific Function”来控制程序的执行。如果是使用”Step Into Specific Function”的话,程序会在选定的函数的开始处暂停。
三、查看变量
1、在Debug窗口中,把鼠标停留在变量名上面,就会弹出一个窗口,显示变量的值。
2、当程序暂停在一个断点时,在Debug窗口中,用鼠标右键单击变量,然后选”QuickWatch”->”Recalculate”即可。
3、在Debug窗口中,选择”View”->”Debug Windows”->”Watch”,然后在”Watch”的”Name”处输入变量的名字(也可以直接把编量名拖到”Name”中)。如果变量是一个数组或对象,那么它前面就会有”+”或”-”,你可以展开”+”来查看变量。
4、在Debug窗口中,选择”View”->”Debug Windows”->”Variables”,然后再选”Variables”窗口中的”Auto”或”Locals”或”This”来查看相应的变量。
5、在”Watch”或”Variables”窗口中选中某个变量,然后选择”View”菜单下的”Properties”,可以查看变量的其它信息。
四、改变变量的值
1、在Debug窗口中,选”Debug”->”QuickWatch”,在”Expression”中输入变量名,然后按”Recalculate”,再使用”Tab”键把光标移到”Value”处,输入新的值,然后按回车。
2、在”Watch”或”Variables”窗口中的”Value”处,也可输入变量的新的值。
五、查看Call Stack
1、在Debug窗口中,选择”View”->”Debug Windows”->”Call Stack”,就会按照调用顺序显示所有的函数调用,当前的函数调用会显示在最上方。双击函数名可以直接跳到该函数的代码处。选中某个函数,然后选”Run to Cursor”命令,可以使程序执行到该函数的末尾;选”Insert/Remove Breakpoint”命令,可以在函数的末尾处设置断点。
2、选”Tools”->”Options”->”Debug”,然后选”Parameter Value”或”Parameter Types”可以改变”Call Stack”的显示方式。
注意:在”Variables”窗口顶部的”Context”中的下拉菜单中也包含”Call Stack”函数,你可以使用该下拉菜单在个函数之间跳转,但不能反向跟踪Windows Messages。
六、执行到指定的地方
1、通过设置断点的方法。
2、把光标移到源程序中想暂停的地方,然后选”Build”->”Start Debug”->”Run to Cursor”。
3、在”Disassembly”或”Call Stack”窗口中也可以使用方法2中的步骤。
4、在”Standard”工具栏的”Find”中输入函数名,然后选”Build”->”Start Debug”->”Run to Cursor”。
5、在源程序窗口,把光标移到你下一步想运行的语句处,然后单击鼠标右键,再选择”Set Next Statement”。
注意,可以用”Run to Cursor”命令跳到前面的代码处,然后用不同的变量值来测试程序。
七、使用Browse Windows
VC的Browse Windows是用来显示符号(Symbol,例如Class, Function, Data和Macro)的信息,也叫做Browse Infomation。如果你在Build一个Project时打开了”Browse Info”选项,Compiler就会为Project中的每个程序文件的信息都创建一个相关的信息文件(.sbr),然后BSCMAKE工具(BSCMake.exe)把这些sbr文件编译成一个单独的信息文件(.bsc)。
当在Browse Windows中查看Browse Infomation时,Browse Windows会根据不同的信息而显示不同的窗口,你可以在Browse Windows中检查:
1、源程序中所有Symbol的信息;
2、Symbol在源程序中的定义行;
3、Symbol在源程序中的参考(Reference)行;
4、基类和派生类之间的关系;
5、调用函数和被调用函数之间的关系。
当你打开一个Project Workspace时,Project的Browse文件也会被自动打开。当然,你也可以通过设置来不自动打开Browse文件,以加快速度。设置方法如下:
1、激活或取消Compile时.sbr文件的创建
选”Project”->”Setting”->”C/C++”,然后选中或取消”Generate browse info”选项。如果你取消”Generate browse info”选项的话,就不会产生.sbr文件,也不会更新.bsc文件。
2、激活或取消Compile时.bsc文件的更新
选”Project”->”Setting”->”Browse Info”,然后选中或取消”Build browse info file”选项。为了加速编译,一般可以打开.sbr文件的创建而关掉.bsc文件的更新,到有需要时才打开bsc文件的更新。
注意,当创建了Browse文件后,你就可以使用”Browse”工具栏了。
3、打开或关闭Browse Infomation文件
选”Tools”->”Source Browser”或”Close Source Browser File”。
注意,如果你在使用Brwose Infomation文件,那么.bsc文件就会处于打开状态中,而且.bsc文件不会自动关闭。当.bsc文件处于打开状态时,它是不能被更新的。
八、使用Just-In-Time Debugging
如果使用了”Just-In-Time Debugging”,那么你就可以不必在VC的窗口中来调试应用程序了。当你在VC以外的环境中运行应用程序时,如果应用程序出错了,”Just-In-Time Debugging”会自动调用VC的Debugger。使用方法是:选”Tools”->”Options”->”Debug”,选中”Just-In-Time Debugging”选项,然后按”OK”,然后重新Build这个程序。
注意:在NT环境下,必须有Administrator权限才能设置”Just-In-Time Debugging”选项。
以下是本节新出现的专业名词
Breakpoint = 断点
Register = 寄存器
Exception = 异常
Thread = 线程
Symbol = 符号
调试应用程序(下)
Visual C++和MFC还提供了一些比较高级的Debug技术:
一、使用MFC函数和Macro
Visual C++ 5.0以后的版本引入了对C运行库(C Run-Time Library)的Debug支持,这个新的Debug版本还提供了一些诊断服务,简化了Debug过程。下面将介绍一些具有诊断目的的Debug例程(Routine)和Macro。
1、C Run-Time Library的Debug支持
Visual C++对C Run-Time Library也提供了Debug支持,使你在Debug应用程序时可以直接进入Run-Time函数。C Run-Time Library也提供了一些工具来跟踪堆(Heap)的分配,定位内存的溢出(Memory Leak)以及发现其他有关内存的问题。有很多Heap检测技术已经从MFC Library转移到了C Run-Time Library的Debug版本中,如果你要使用Heap检测技术,你必须把MFC应用程序的Debug Build和Run-Time Library的Debug版本链接起来。
C Run-Time Library包括以下Debug报告函数:
(1) _CtrDbgReport和_CrelsValidPointer,用于验证和报告;
(2) _ASSERT和_RPTn,用于Debug Heap;
(3) Debug版本的malloc, free, calloc, realloc, new, delete,作用请参见C语言中的相关函数;
(4) _CrtCheckMemory和_CrtDumpMemoryLeaks等,用于监视Heap;
(5) _CrtSetDumpClient和_CrtSetAllocHook等,可以使你加入你自己的钩子函数(Hook Function)。
为了使用这些Routine,你必须使用_DEBUG标志,即使用”Win32 Debug”来编译你的程序(参见上一节开头部分)。在正式发行版中,这些Routine是不起作用的。C Run-Time函数在Windows 9x和NT中都可以使用。
2、Run-Time Debugging Routines
只有在运行Debug版的应用程序时,Run-Time Debugging Routines才被激活。而在Release版的应用程序中,Assertion是不起作用的,完全不会影响程序的执行速度。
(1) ASSERT Routine
ASSERT Routine主要用于确保一个假设的正确性,如果Assertion是错误的或者是失败的,Macro就会显示这个Assertion的消息框,包括源 文件的名称和在源文件中的位置等消息,还会给用户一个选择:中断或Debug这个程序。这个Macro通常用来验证函数的参数和返回值。例如:
CWnd* pWnd=GetParent();
ASSERT (pWnd != NULL);
注意,MFC Library的Debug版经常会使用到Assertion,细节请看MSDN中的”Foundation Classes Common Asserts, Causes, and Solutions”。
(2) VERIFY Routine
VERIFY Routine会计算在Debug和Release模式下的条件,只有在Debug模式中,它才会显示和中断。在Debug模式中,VERIFY非常类似 ASSERT。而在Release模式中,它所包含的表达式只会被执行,不会被验证。VERIFY一般用于检查返回类型为指针的函数。
(3) CObject::AssertValid函数
这个函数用来确定相关的对象在内部是否是有效的,所有的MFC Library Class都会通过Override这个函数,来提供对内部一致性检验的支持。当你创建一个可重用的Class时,你也应该Override这个函数。
(4) ASSERT_VALID Macro
MFC使用ASSERT_VALID Macro来强行调用一个Object的AsserValid函数。只要一个函数的参数是一个有效的CObject或CObject指针,这个函数就会使用ASSERT_VALID Macro来验证这个Object。ASSERT_VALID Macro和ASSERT Macro一样,都是只有在Debug模式下才有效。程序示例如下:
CShapsDoc *pDoc=GetDocument();
ASSERT_VALID(pDoc);
3、Debugger-Enhancing Routines
这些函数将会把信息直接显示在Output窗口中。
(1) TRACE Macro
TRACE Macro和C语言中的printf类似,用于把格式化了的字符串输出到Debug流(Stream)中。例如:
TRACE (”The number is %d”, m_Number);
(2) CObject::Dump函数
这个Dump函数会导致相关Object的内部状态被显示在Ouput窗口中。Dump函数是不会自动打印换行符的。
在Debug模式下,MFC Framework会在应用程序结束时自动调用没有被正确结束的CObject对象的Dump函数。因此,当你创建自己的Class时,你也应该 Override基类的Dump函数,为派生类提供诊断服务。被Override了的Dump函数通常在打印数据成员之前会调用基类的Dump函数。如果 你的Class使用了IMPLEMENT_DYNAMIC或IMPLEMENT_SERIAL Macro, 那么CObject::Dump就会打印Class的名称。
当你调用一个Object的Dump时,你必须提供一个类型为CDumpContext的参数,通常是全局对象afxDump。
二、使用Tracer
MFC提供了一个小工具Tracer.exe来帮助调试Windows-Based的程序,Tracer可以在Output或Console窗口中显示MFC Library的内部操作信息,以及应用程序的Warning和Error消息,你可以按照需要来查看它们。Tracer可以经常对所出现的问题发出警告,并可以提供错误的详细解释。
你可以通过运行Tracer.exe来设置Tracer,设置的结果保存在操作系统目录(例如c: winnt)下的Afx.ini文件中。全局整型变量afxTraceFlags用来设置Trace过程中各种报告的开或关,它的每一位(Bit)就代表 某种报告的开或关,你可以参看头文件”Afxwin.h”来知道afxTraceFlags各个位(Bit)的含义。
选择”Tools”菜单下的”MFC Tracer”就会出现”MFC Trace Options”的对话框,对话框中的第一项”Enable tracing”就是用来打开或关闭Trace的,其他七项是用来选择用来Trace的信息的类型的。
注意,Tracer也要在Debug模式中才能使用,不能在Release模式中使用。
当afxTraceEnabled的值为true时,Tracer信息和afxDump信息就会在 Output窗口中显示;当afxTraceEnabled的值为false时,Tracer信息和afxDump信息就不会被显示。而且Trace的信 息只有在Debug过程中才会被显示出来。
三、使用Spy++
Spy++(Spyxx.exe)是一个Win32-based工具,作用是用图形来显示系统的Process, Thread, Windows和Message。你可以用Refresh和Find工具来辅助你的”Spy”工作。
PView process Viewer(PView.exe)和Spy++类似,使你可以检查和修改某些Process和Thread的特性。
以下是本节新出现的专业名词
C运行库 = C Run-Time Library
例程 = Routine
堆 = Heap
内存溢出 = Memory Leak
钩子函数 = Hook Function
流 = Stream
创建MFC应用程序所需要的Class
MFC应用程序无需固定的结构,但有些Class一定要和其他Class一起使用。在编程时,可以根据 需要把所有的Class按照不同的方法联合起来。例如有些程序是Document/View结构的,有些是非Document/View结构的,还有些是 Dialog-based结构的。需要注意的一点是:所有的MFC应用程序都用到了Application Class–CWinApp和Frame Window Class–CFrameWnd。应用程序Objects是从CWinApp派生的,而应用程序Window Objects是从CFrameWnd基类派生的。
一、Application Class
Application Class, CWinApp代表应用程序本身,它是基本的Application Class,它封装了Windows-based应用程序的初始化、运行、Message映射和终止。Application Class还会创建至少一个Document Template Object。
MFC应用程序必须有且仅有一个从CWinApp派生的Class的Object,这个Object在 Windows被创建之前就会被创建,也就是说这个Object会和其他C++全局Object同时创建。当Windows调用WinMain(在MFC 应用程序中,你不必亲自调用WinMain,因为当应用程序启动时会由框架提供WinMain)时,这个Object已经可用了,而且这个Object必 须是全局的。
当用AppWizard创建Document/View应用程序时,AppWizard会声明一个从 CWinApp派生的Application Class,AppWizard所产生的.cpp文件中还包括:Message映射,空的构造函数(Constructor),一个应用程序Object (即一个变量),InitInstance函数。AppWizard提供的源代码和Message映射可以满足一些基本的任务,但在通常情况下,你还是需 要手工修改那些源程序的,特别是要修改InitInstance函数。
在CWinApp中,有以下几个关键的可Override的成员函数:
InitInstance,作用是创建Document Template,即按顺序创建Documents, Views和Frame Windows。InitInstace是唯一的一个你必须Override的成员函数;
Run,初始化后,WinMain就会调用Run这个成员函数去处理Message循环。Document/View应用程序会花掉大部分时间在Run这个函数上;
ExitInstance,每当一个应用程序的Copy终止时,就会调用这个函数,即发生在应用程序退出时;
OnIdle,当没有Windows Message处理时,就会由Framework调用这个函数。通常Override这个函数去执行后台任务。
当你从CWinApp派生一个Application Class时,你必须Override成员函数InitInstance去创建应用程序的Main Window Object。Windows允许同时运行同一个应用程序的多个”Copy”,该应用程序的每个Instance(包括第一个的)都会被初始化,而初始化时都会用到被你Override了的InitInstance函数所提供的信息。
通常情况下,每个Windows-based应用程序都有一个Main Window。因此,在初始化完成后,Framework就会检查是否存在一个指向有效Main Window(CWinApp:m_pMainWnd)的指针,如果不存在的话,应用程序就会终止。
当你用AppWizard创建应用程序时,AppWizard会Override缺省的InitInstance来创建Main Window Object,还会使CWinApp的数据成员m_pMainWnd指向那个Window。
二、Frame Window Class
Frame Window Class, CFrameWnd在屏幕上定义了应用程序的物理工作空间,并充当了View的容器(Container),在Single Document Interface(SDI)应用程序中,只有一个Frame Window充当应用程序的顶级窗口和Document的View的框架。
CFrameWnd代表了主窗口(Primary Window)的边框,还会自动来设定View Window的位置和大小,以及决定应用程序的外观(例如Maximize、Minimize、Save、Close等按钮,标题栏,标题栏的图标,主菜 单,滚动栏,状态栏,工具栏等)。
CFrameWnd这个Class提供了SDI应用程序窗口的一些功能性,并提过了一些成员函数来管理这些Window。通过派生类CMDIChildWnd,Frame Window就可以处理Multiple Document Interface(MDI) Windows了。
在CFrameWnd这个Class中有两个关键成员函数:
GetActiveView,返回当前的CView的指针,如果没有当前的View,就返回NULL;
GetActiveDocument, 返回当前的CDocument的指针,如果没有当前的Document,就返回NULL。
由于CFrameWnd派生的Class是间接从CCmdTarget派生的,所以CFrameWnd派生的Class也可以接收和处理Command Messages。
以下是本节新出现的专业名词
构造函数 = Constructor
容器 = Container
主窗口 = Primary Window
非Document/View结构的应用程序的创建
在Document/View结构未被开发出来之前,MFC应用程序就有两个重要的组成部分:一个是代表应用程序本身的Application Object,另一个是代表应用程序的窗口的Window Object。Application Object的任务就是创建Window,然后就由Window来处理Message。在这些早期的版本中,MFC只是仅仅封装了Windows API,而把Object-oriented Interface转嫁到标准Windows Object(例如菜单和对话框等)上。
虽然大部分MFC应用程序都是用Document/View结构,但Document/View结构并 非必需的,Document/View应用程序虽然功能强大,但它们包含了一套开始文件,从而就增加了文件的大小和复杂性(初学者可能根本看不懂 AppWizard等工具自动生成的代码)。因此在某些情况下(例如一个简单的基于对话框应用程序),就可以不使用Document/View。为了更好 的学习Document/View和MFC,我们应先从非Document/View的应用程序开始。下面我们就用手工建立一个非常简单的非 Document/View应用程序(其实创建非Document/View应用程序的最简单的方法就是利用AppWizard创建一个基于对话框的应用 程序,不过代码比较复杂,不利于初学者),希望大家能够完全理解以下代码。
1、选”File”->”New”;
2、选”Win32 Application”,然后在”Project name”中输入vchack_01_005_002,然后选”OK”;
3、选”An empty project”,然后按”Finish”,最后再按一次”OK”来创建一个新的Project;
4、选”FileView”,然后在”Header Files”上按鼠标右键,选”Add Files to Folder”;
5、输入文件名”vchack_01_005_002.h”,然后”OK”。系统会提示你创建一个新文件,选”Yes”就行了;
6、打开vchack_01_005_002.h,输入以下代码:(绿色部分为注释)
//以下程序摘自微软文档
#include <afxwin.h> //包含头文件afxwin.h,该头文件中定义了所有的MFC,是所有Windows程序都必须包含的
// 下面将分别从CWinApp和CFrameWnd中继承两个类,这是必须的,但代码可以有不同的写法
class CMyApp : public CWinApp //定义一个从CWinApp继承的类CMyApp
{
// InitInstance是程序的进入点,必须重载它
// WinMain会调用Application Object的成员函数InitInstance来初始化应用程序
// 所以Application Object一定要在WinMain被调用之前就已经存在
// 大概的流程是: Framework调用WinMain,然后WinMain调用Application Object的InitInstance
public:
virtual BOOL InitInstance ();
};
class CMainFrame : public CFrameWnd //定义一个从CFrameWnd继承的类CMainFrame,其实直接使用CFrameWnd也可以
{
};
7、在”Source Files”上按鼠标右键,选”Add Files to Folder”;
8、输入文件名”vchack_01_005_002.cpp”,然后”OK”。系统会提示你创建一个新文件,选”Yes”就行了;
9、打开vchack_01_005_002.cpp,输入以下代码:
#include “vchack_01_005_002.h” //包含上面创建的头文件vchack_01_005_002.h
// 在Framework应用程序中,不用写WinMain这个函数
// WinMain函数是由Class Library提供,并会在应用程序启动时被调用
// 但是,在Framework应用程序中一定要有且仅有一个从CWinApp派生的Object
// 而且在Framework调用WinMain函数之前这个Application Object就一定要存在
// 所以要在程序的开头部分声明这个全局变量
// 关于WinMain的详细信息请参考MSDN的”CWinApp: The Application Class”
CMyApp myApp;
// 以下是InitInstance的主要内容
// 本例中InitInstance将实例化CMainFrame的Object来创建一个Window
BOOL CMyApp::InitInstance()
{
// m_pMainWnd是从CWinApp(实际上是从CThreadWnd)继承来的一个数据成员
// m_pMainWnd是一个指向Application Object所使用的Window Object的指针
m_pMainWnd = new CMainFrame; //动态创建一个CMainFrame Object,并把它的地址赋给m_pMainWnd
((CMainFrame*)m_pMainWnd)->Create(NULL,”The Non-Document/View MFC Application”); //使用CFrameWnd::Create来创建一个Window
// m_nCmdShow也是CWinApp的一个数据成员,用来规定Window如何被显示
m_pMainWnd->ShowWindow (m_nCmdShow); //利用函数ShowWindow来显示Window
// 如果InitInstance函数的返回值为0,那么WinMain将会终止,应用程序也会停止执行
// 如果InitInstance函数的返回一个非0值,那么WinMain将会通过调用成员函数Run来运行应用程序的Message循环
// 如果收到Message队列中的”WM_QUIT”这条Message,Message循环就会终止
// 终止时,WinMain会调用Application Object的成员函数ExitInstance
return TRUE;
}
10、选”Project”->”Setting”;
11、选”General”标签,然后在”Microsoft Foundation Classes”下拉菜单中选”Use MFC in a Static Library”或”Use MFC in a Shared DLL”,然后按”OK”;
12、选”Build”->”Execute vchack_01_005_002.exe”来编译并执行该Project。
以下是本节新出现的专业名词
全局 = Global
Document/View的基本原理
在知道了Application Class和Frame Window Class在MFC应用程序中的作用后,下面我们就要学习主要用在Document/View应用程序的其他三个Class:Document Class, View Class和Document Template Class。
Document/View应用程序的结构是由5个Class或Object组成的,如果要开发MFC应用程序,就一定要了解它们的作用及其相互之间的关系。总的来说,Application Object会把Message发送到Frame Window和View,而View和Document之间的信息是双向流动的。
应用程序的数据是储存在Document Object中的,并且在View中显示出来。View Object是Frame Window的子窗口,而且子窗口的大小是由Frame Window决定的,View Object的作用是充当父窗口的客户区域(Client Area)。Frame Window Object就是应用程序的顶级窗口,通常会包含有可调整大小的边框、标题栏、系统菜单、最大化和最小化和关闭按钮。
一、Document Class
在Document Class应用程序中,数据是储存在一个CDocument派生类的Document Object中。CDocument Class载入、储存并管理程序的数据,他还提供了访问和操作数据的的函数。在Document/View结构中,Document和View的关系是非常密切的,每个Document Object都会维持一个所有与之相关的View的列表清单, 而每个View Object就会维持一个指向相关Document的指针。
从CDocument派生的Class继承(Inherit)了以下几个重要的成员函数:
GetFirstViewPosition, 返回一个类型为POSITION的值,可以把值可以传递给GetNextView,从而列举出所有的Document的View;
GetNextView, 返回一个类型为CView的指针,该指针指向与该Document相关的View的列表清单中的下一个View;
GetPathName, 获得Document的文件名和路径,如果Document没有被命名,就返回一个NULL字符串;
GetTitle, 获得Document的标题,如果Document没有被命名,就返回一个NULL字符串;
IsModified, 如果Document包含有未保存的数据就返回一个非0值,反之就返回0;
SetModifiedFlag, 设置或清除Document已被修改这个标记,该标记指明了Document是否包含有未保存的数据;
UpdateAllViews, 通过调用与该Document相关的所有View的OnUpdate函数来更新所有的View。
CDocument中包含有几个重要的可Override的函数,你可以Override它们来定制一个Document的行为,这些函数分别是:
OnNewDocument, 当一个新Document被创建时,Framework就会调用这个函数。Override它的目的是,在新Document被创建之前初始化Document Object;
OnOpenDocument, 当一个Document被从磁盘中载入时,Framework就会调用这个函数。Override它的目的是,在新Document被载入之前初始化未被Serialize的Document Object的数据成员;
DeleteContents, 由Framework调用这个函数来删除Document的内容,Override它的目的是,在Document被关闭之前释放分配给该Document的内存和其他资源;
Serialize, 由Framework调用这个函数来使Document储存到文件中或从文件中读出。Override它的目的是,提供特定的代码来保存或载入Document。
二、View Class
View Object,在物理上代表一个应用程序的Client Area;在逻辑上代表包含在Document Class中的信息的视见区(Viewport),它允许用户通过键盘或鼠标来输入。一个Document Object可以有多个与之关联的View,但一个View通常只属于一个Document。
CView Class提供了基本的Framework来提供向View Window和Printer(打印设备,微软喜欢把Printer翻译成打印设备而不是打印机)的输出以及与相关的Document进行通讯。CView定义了View的基本属性,从CView派生的View Class还会增加其他功能,而从CView直接派生的Class还可以通过不同的方法来显示信息,但它们必须提供它们自己的关于Paint的代码。
MFC提供了很多View Class,这些View Class可以以不同的方法来显示信息,而不需要你来写那些底层的Paint代码,你所需要做的在大多数情况下只是决定View Class如何Paint就行了。例如,在”资源管理器中”,左边是用CTreeView生成的目录树,右边是用CListView生成的文件列表,这些扩展了的View Class提供了各种各样的扩展功能,使你只用少量代码,就开发出功能强大的程序。以下是几个从CView派生的Class:
CCtrlView, 它是CEditView、CRichEditView、CListView和CTreeView的基类,可以被用来派生其他View;
CEditView,提供了剪切、复制、粘贴等Windows编辑控件的功能,还提供了打印、查找、查找并替换等功能;
CRichEditView,提供了Windows的Rich Edit控件的功能;
CListView, 提供了Windows的List View控件的功能;
CTreeView, 提供了Windows的Tree View控件的功能;
CScrollView, 它是CFormView和CRecordView的基类,并给View增加了卷屏的功能;
CFormView, 通过从Dialog Template创建的控件来实现卷屏View;
CRecordView, 提供数据库的View。
CView中包含有几个重要的可Override的函数,你可以Override它们来定制一个View的行为,这些函数分别是:
GetDocument, 返回相关的Document Object的指针;
OnDraw, 支持在屏幕上的绘画和打印、打印预览;
OnInitialUpdate, 当一个View首次和一个Document相连接时就会调用这个函数,Override它可以初始化一个刚被载入或创建的Document的View;
OnUpdate, 当Document的数据发生变化并且View需要被更新时就会调用这个函数,Override它可以实现”按需更新”,即只重新绘制发生了变化的View的部分,而不用重新绘制整个View。
三、Document Template Class
Document Template的基类是CDocTemplate,它的作用是将Frame, View, Document和应用程序的资源绑定到一起。至少有一个Document Class的Instance是由Application Class创建和维护的,而所有的Document Object的存在是由Document Template Object管理的,Document Template Object维持着一个所有Document Object的列表,Template还把不同的资源和那些Object联系起来。在大多数情况下,你不需要修改这个Class的行为。
Framework使用了两个Document Template Class:一个是用于SDI应用程序的CSingleDocTemplate,另一个是用于MDI应用程序的CMultiDocTemplate。
CSingleDocTemplate Object提供了一个Single Document Interface(SDI),只能创建和拥有一个Document。SDI应用程序通过Main Frame Window来显示它的Document,每次只能打开一个Document。CSingleDocTemplate的构造函数(Constructor)有4个参数,分别是:
资源的ID,用来确定与Document Template相关联的各种资源;
CDocument派生类的Run-time Class;
View的Frame的Run-time Class;
Document的View的Run-time Class;
CMultiDocTemplate Object可以创建、拥有和管理多个同类型的Document,它提供了一个Multiple Document Interface(MDI)。MDI应用程序把Main Frame Window做为Workspace,在这个Workspace中,用户可以打开多个Document Frame Windows。
如果一个应用程序同时包含Multiple View Classes和一个Single Document Class,那么这个应用程序就必须要有多个Document Templates,每个Document Template都对应一个View Class。如果一个应用程序有Multiple Document Classes和一个Single View,那么每对Multiple Document Classes和Single View都需要一个Document Template。
以下是本节新出现的专业名词
客户区域 = Client Area
继承 = Inherit
视见区 = Viewport
打印设备 = Printer
Document/View应用程序的实例分析
在了解了Document/View的基本原理后,我们将通过一个实例的源代码来加深大家对Document/View(对话框不属于Document/View结构,所以我们现在暂时不讨论)的理解,并向大家展示在MFC应用程序的背后到底发生了什么。
按照下面的步骤来创建一个简单的MFC应用程序(其实是一个简单的文本编辑器):
1、运行Visual C++,选择”File”菜单中的”New”命令,会出现一个”New”对话框;
2、在”New”对话框中选中”Project”,然后选”MFC AppWizard (exe)”,在”Project Name”中输入”vchack_01_005_004″,在”Location”中可以改变Project的目录,其他地方保留默认值就可以了,然后按”OK”
3、在”MFC AppWizard - Step 1″对话框中,选择”Single documnet”,其他地方保留默认值,然后按”Next”;
4、在”MFC AppWizard - Step 2 of 6″对话框中,直接按”Next”;
5、在”MFC AppWizard - Step 3 of 6″对话框中,去掉”ActiveX Controls”的选中符号,然后按”Next”;
6、在”MFC AppWizard - Step 4 of 6″对话框中,直接按”Next”;
7、在”MFC AppWizard - Step 5 of 6″对话框中,直接按”Next”;
8、在”MFC AppWizard - Step 6 of 6″对话框中,按”Finish”;
9、在”New Project Infomation”对话框中,会显示你刚刚创建的Project的信息,按”OK”;
10、在”Build” 菜单,选”Build vchack_01_005_004.exe”来创建一个可执行的exe文件;
11、在”Build” 菜单,选”Execute vchack_01_005_004.exe”来运行这个程序;
12、运行这个程序后,选择”文件”菜单下的”打开”命令来打开一个文件,你会发现现在是打不开文件的。
在Visual C++主界面中的左边,选中”ClassView”,然后按”vchack_01_005_004 classes”左边的”+”号,你会发现AppWizard为你创建了5个Class和1个全局变量(Globals,即theApp)。5个Class分别是:
Application Class–CVchack_01_005_004App, 是从CWinApp派生的;
Frame Window Class–CMainFrame, 是从CFrameWnd派生的;
Document Class–CVchack_01_005_004Doc, 是从CDocument派生的;
View Class–CVchack_01_005_004App, 是从CView派生的;
Dialog Class–CAboutDialog, 是从CDialog派生的(这一节暂时不讨论)。
下面我们就结合源代码来详细分析以上4个Class的作用:
一、CWinApp的派生类
CWinApp的派生类CVchack_01_005_004App是一个Application Class,它的成员函数InitInstance的功能是:在注册表中储存应用程序的设定,创建一个Document Template,注册Document Template,初始化Command Line。
1、设定注册表
InitInstance通过执行下列语句来将应用程序的设定储存到注册表中,也会把最近使用的(Most Recently Used, 即MRU)文件清单储存到注册表中,通常会以公司的名字作为注册表的关键字。
SetRegistryKey(_T(”Local AppWizard-Generated Applications”));
2、载入应用程序的Profile
InitInstance通过执行下列语句来载入MRU文件和最新的状态:
LoadStdProfileSettings();
3、创建Document Template
InitInstance通过执行下列语句来从CSingleDocTemplate类创建一个Document Template:
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CVchack_01_005_004Doc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CVchack_01_005_004View));
CSingleDocTemplate类定义了一个Document Template来实现SDI,SDI应用程序通过Main Frame Window来显示Document,每次只能打开一个Document。Document Template定义了三个主要Document/View Classes之间的关系:
(1) Document Class,代表应用程序的Document;
(2) View Class,显示Document Class的数据;
(3) Frame Window Class,包含Document的Views。
Document Template还指定了各种资源的ID(例如目录、图标、快捷键和字符串)。
以下语句的作用是将Document Template添加到由应用程序维护的可用Document Template列表中:
AddDocTemplate(pDocTemplate);
4、初始化Command Line
以下语句的作用是根据输入的命令行来初始化一个CCommandLineInfo Object:
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (!ProcessShellCommand(cmdInfo))
return FALSE;
ProcessShellCommand的作用是处理一个命令行参数,如果处理成功的话就返回一个非0值,否则就返回FALSE。
二、CFrameWnd的派生类
CFrameWnd的派生类CMainFrame是应用程序的Main Frame Window Class,Frame Window Class定义了Primary Window的边框并自动决定View Window的位置和大小,他还管理着应用程序的外观,例如目录,滚动栏,工具栏等。
1、动态创建Objects
在CMainFrame这个类的声明中,包含一个Macro–DECLARE_DYNCREATE,这个 Macro的作用是使CObject的派生类的Objects可以在运行时动态创建。DECLARE_DYNCREATE以动态创建的Class的名字作 为它的参数,见下面的代码:
class CMainFrame : public CFrameWnd
{
protected:
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)
…
};
如果在Class的声明处有DECLARE_DYNCREATE这个Macro,那么在Class的实现处 (即在MainFrm.cpp文件中)一定要包含有Macro–IMPLEMENT_DYNCREATE,这个Macro有两个参数,一个是动态创建的 Class的名字,另一个是动态创建的Class的基类的名字,见下面的代码:
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
然后你就可以用Macro–RUNTIME_CLASS来动态创建一个Object了(见CVchack_01_005_004App的InitInstance()函数),这个Macro以Class的名字作为参数,见下面的代码:
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CVchack_01_005_004Doc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CVchack_01_005_004View));
2、创建Window
在Header文件CMainFrame.h中,有两个成员函数PreCreateWindow和OnCreate,还有两个成员变量m_wndStatusBar和m_wndToolBar。
在Window被创建前,Framework会调用成员函数PreCreateWindow。如果你Override它,就可以改变Window的风格了,在此例中没有做任何改变:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
return TRUE;
}
如果你想调用基类的PreCreateWindow的话,把以上语句该为下面这样就可以了:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
return CFrameWnd::PreCreateWindow(cs);
}
3、创建并载入工具栏
OnCreate成员函数可以通过成员变量m_wndToolBar来创建并载入工具栏:
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
OnCreate还可以创建和设置状态栏:
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
4、Dock工具栏
Dock工具栏要用到以下三个成员函数(第一行的作用是允许一个Control Bar可以被Dock,第二行的作用是在一个Frame Window中启动一个可Dock的Control Bar,第三行的作用是在Frame Window中Dock一个Control Bar):
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
三、CDocument的派生类
CDocument的派生类CVchack_01_005_004Doc是应用程序的Document Class。我们需要对它作如下修改:
1、定义一个类型为CStringList的变量m_LineList来储存文件的内容,步骤如下:
(1) 用鼠标右键单击ClassView中的”CVchack_01_005_004Doc”,然后选”Add Member Variable…”;
(2) 在”Variable Type”中输入”CStringList”,在”Variable Name”中输入”m_LineList”,然后选”Protected”,按”OK”。
2、增加一个成员函数来访问m_LineList的内容
(1) 用鼠标右键单击ClassView中的”CVchack_01_005_004Doc”,然后选”Add Member Function…”;
(2) 在”Function Type”中输入”CStringList*”,在”Function Declaration”中输入”GetLineList”,然后选”Public”,按”OK”;
(3) 为函数”GetLineList”添加语句”return &m_LineList;”,完整的函数定义为:
CStringList* CVchack_01_005_004Doc::GetLineList()
{
return &m_LineList;
}
3、增加一个成员函数来删除m_LineList的所有内容
(1) 用鼠标右键单击ClassView中的”CVchack_01_005_004Doc”,然后选”Add Member Function…”;
(2) 在”Function Type”中输入”void”,在”Function Declaration”中输入”DeleteContents”,然后选”Protected”和”Virtual”,按”OK”;
(3) 为函数”GetLineList”添加语句”m_LineList.RemoveAll();”,完整的函数定义为:
void CVchack_01_005_004Doc::DeleteContents()
{
m_LineList.RemoveAll();
}
4、增加一个虚拟函数来打开文件
(1) 用鼠标右键单击ClassView中的”CVchack_01_005_004Doc”,然后选”Add Virtual Function…”;
(2) 在”New Virtual Functions”中选”OnOpenDocument”,然后按”Add Handler”,再按”Edit Existing”;
(3) 把函数的定义改为如下代码:
BOOL CVchack_01_005_004Doc::OnOpenDocument(LPCTSTR lpszPathName)
{
DeleteContents(); // 删除m_LineList中的所有内容
CStdioFile file(lpszPathName, CFile::modeRead | CFile::typeText); // 打开一个文件
CString strLine;
// 用while语句来读入文件的全部内容
while (file.ReadString(strLine) != NULL)
{
m_LineList.AddTail(strLine); // 将strLine的内容添加到m_LineList
}
return TRUE;
}
四、CView的派生类
CView的派生类CVchack_01_005_004View是应用程序的View Class,CVchack_01_005_004View包含两个成员函数GetDocument和OnDraw。
GetDocument的作用是获取相关Document的指针,代码如下:
inline CReaderDoc* CReaderView::GetDocument()
{ return (CReaderDoc*)m_pDocument; }
OnDraw的作用是在屏幕上一行一行的显示文件的内容,我们需要对它的定义作如下修改:
void CVchack_01_005_004View::OnDraw(CDC* pDC)
{
CStringList *pLineList = GetDocument()->GetLineList(); //GetLineList的作用是取得CStringList Object的指针
CString strLine;
POSITION pos;
int nXPos=10; int nYPos=10; int nYDelta = 0;
TEXTMETRIC tm;
pDC->GetTextMetrics(&tm);
nYDelta = tm.tmHeight;
// 用for循环来显示全部内容
for(pos = pLineList->GetHeadPosition(); pos != NULL; )
{
strLine = pLineList->GetAt(pos);
pDC->TabbedTextOut(nXPos,nYPos,strLine,0,NULL,0); //向屏幕输出
pLineList->GetNext(pos);
nYPos +=nYDelta;
}
}
以下是本节新出现的专业名词
最近使用的 = Most Recently Used, 即MRU