TRACE 的信息是顯示在OutPut窗口中的

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章