MFC應用程序框架入門(轉)

1 MFC概述

顧名思意,MFC應用程序框架是以MFC作爲框架基礎的,以此程序框架模式搭建起來的應用程序在程序結構組織上是完全不同於以前的Win32 SDK編程方式的。自20世紀90年代初問世以來,MFC一直試圖把Windows API函數封裝到類庫中個各個邏輯類中。MFC的這種封裝並非簡單地對API函數進行分組與打包,而是更多地通過類來試圖實現全部的系統策略。隨着越來越多系統功能的加入,MFC的規模也在不斷拓展,目前已包括有200多個類,涵蓋了通用Windows 類、文檔/視框架、OLE、數據庫、Internet以及分佈式功能等多方面的基本內容。這樣一個堅實的程序開發基礎無疑從很大程度上方便了程序設計人員對Windows 程序的開發。

MFC提供了相當多不同功能的類以適合儘可能廣泛的需求。這裏絕大多數的MFC類都是直接或間接從CObject類派生出來的,CObject類爲其派生類提供了三個重要的特性支持:持久性(Serialization)支持、運行時(Run-time)類信息支持和診斷(Diagnostic)調試支持等。其中持久性是以流的方式將某個類對象中的持久性數據輸出或輸入到外部存儲介質如磁盤文件等的過程;運行時類信息(Run-time Class Information,RTCI)則可以重新獲取一個對象的類名及其他一些有關對象在運行時的信息。RTCI也是C++中除運行時類型信息(Run-time Type Information,RTTI)機制外的另一個重要工具;診斷和調試支持作爲CObject類的一個組成部分,可以在實現CObject派生類時執行有效性檢查並可向調試窗口輸出狀態信息。

並非MFC提供的所有函數都是類成員函數,MFC也提供了一系列以Afx爲前綴的全局函數。類成員函數只能在其所屬類對象所在的上下文中使用,但是這些AFX函數卻可以在任何時候的任何地方直接使用。下表列出的是幾個比較重要AFX函數:
函數名 函數說明
AfxAbout 無條件終止一個應用程序;通常在發生無法回覆的錯誤時使用
AfxBeginThread 創建一個新的線程並開始執行
AfxEndThread 終止當前正在執行的線程
AfxMessageBox 顯示一個Windows 消息窗口
AfxGetApp 返回一個指向應用程序對象的指針
AfxGetAppName 返回應用程序名
AfxGetMainWnd 返回一個指向應用程序主窗口的指針
AfxGetInstanceHandle 返回一個標識當前應用程序實例的句柄
AfxRegisterWndClass 爲一個MFC應用程序註冊一個用戶自定義的窗口類

2 MFC對API函數的封裝

如果讀者曾經有過SDK的開發經歷,一定會對其煩瑣的編程方式和大量的Win32 API函數調用深有感觸。所有不同功能的API函數均是以全局函數的形式放在一起的,由於API函數數目比較龐大,因此無論是學習還是使用都是有一定難度的。相比而言,建立在API函數基礎之上的MFC類庫則通過把相關API函數的分類封裝而可以大大簡化編程的難度,用MFC類編寫的Windows 應用程序完成相同的任務只需要進行少量的工作。

衆多的API函數根據功能的不同而被MFC封裝到200多個類中,這些類基本涵蓋了進行Windows 編程大部分可能用到的功能。由於封裝後的MFC類太多,這裏不能一一介紹,下面就以其中比較重要的CObject類和CWnd類爲例對API函數的封裝情況做一簡要介紹。

CObject類是MFC中最主要也是最基本的類之一,該類不支持多重繼承,派生的類只能有一個CObject基類。CObject類是位於類層次結構最頂層的,絕大多數MFC類都是從CObject類派生出來的。CObject類包含了所有MFC類必須具備的幾個基本功能:持久性支持、運行時類信息支持和診斷調試支持。其中持久性支持功能由成員函數IsSerializable()和Serialize()提供。前者用於檢測對象是否支持序列化。如果一個類能夠被序列化,就必須在聲明時包含DECLARE_SERIAL宏、在實現時包含IMPLEMENT_SERIAL宏。Serialize()函數則可以將對象寫入檔案文件(Archive)或從檔案文件讀出對象。成員函數GetRuntimeClass()可以獲取到一個指向CruntimeClass類對象的指針,通過該指針可以得到對象的運行時類信息。CObject類在診斷調試支持方面提供了成員函數AssertValid()和Dump(),前者可對對象內存狀態的有效性進行檢查,後者負責將對象的內容轉儲到一個CdumpContext對象中,並可以提供診斷服務及一些有用的調試信息。

在MFC中,CWnd類提供了所有窗口類的基本功能,是一個非常重要的類,大約三分之一的MFC類都是以此爲基類。該類主要對創建、操縱窗口類的API函數進行了封裝,而且通過消息映射機制隱藏了SDK編程中使用相當不便的窗口處理函數,是消息的分發處理更加方便。

CWnd類最重要的一個封裝是對API函數CreateWindow()的封裝,該函數被封裝爲CWnd類成員函數Create()。從VC提供的MFC源文件WinCore.cpp中可以清楚看出CWnd類對CreateWindow()函數的封裝過程,下面給出相關部分的實現清單:

BOOL CWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)

{

// can't use for desktop or pop-up windows (use CreateEx instead)

ASSERT(pParentWnd != NULL);

ASSERT((dwStyle & WS_POPUP) == 0);

return CreateEx(0, lpszClassName, lpszWindowName, dwStyle | WS_CHILD, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd->GetSafeHwnd(), (HMENU)nID, (LPVOID)pContext);

}



可以看出,主要工作是在CreateEx()成員函數中完成的,而該函數又是對API函數CreateWindowEx()的封裝。封裝後的代碼在調用CreateWindowEx()前構造並填充了一個非常類似於WNDCLASS結構的CREATESTRUCT結構,並調用了PreCreateWindow()。

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)

{

// allow modification of several common create parameters

CREATESTRUCT cs;

cs.dwExStyle = dwExStyle;

cs.lpszClass = lpszClassName;

cs.lpszName = lpszWindowName;

cs.style = dwStyle;

cs.x = x;

cs.y = y;

cs.cx = nWidth;

cs.cy = nHeight;

cs.hwndParent = hWndParent;

cs.hMenu = nIDorHMenu;

cs.hInstance = AfxGetInstanceHandle();

cs.lpCreateParams = lpParam;

if (!PreCreateWindow(cs))

{

PostNcDestroy();

return FALSE;

}

AfxHookWindowCreate(this);

HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

#ifdef _DEBUG

if (hWnd == NULL)

{

TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8Xn", GetLastError());

}

#endif

if (!AfxUnhookWindowCreate())

PostNcDestroy(); // cleanup if CreateWindowEx fails too soon

if (hWnd == NULL)

return FALSE;

ASSERT(hWnd == m_hWnd); // should have been set in send msg hook

return TRUE;

}



看上去經過封裝的窗口創建函數要比原API函數複雜許多,但這並不說明MFC的封裝將導致編程的效率低下,恰恰相反,由於CWnd在絕大多數場合中是以基類的形式出現的,因此可在派生類中添加代碼完成對CWnd::Create()的調用而比較方便的實現對派生類窗口的創建。

3 MFC應用程序框架

MFC應用程序框架可以看作是MFC基本類庫的一個超集(Superset),類庫是衆多可在任何程序中使用的類的集合,而應用程序框架則定義了程序自身的結構。下面給出一個使用了MFC應用程序框架的簡單例子,通過這段例程可以比較清楚地瞭解MFC應用程序框架的一般結構。

 

// Sample01.h文件

// 應用程序類

class CSample01App : public CWinApp

{

public:

virtual BOOL InitInstance();

};

// 框架窗口類

class CSample01Frame : public CFrameWnd

{

public:

CSample01Frame();

protected:

afx_msg void OnPaint();

DECLARE_MESSAGE_MAP()

};

// Sample01.cpp文件

#include <afxwin.h>

#include "Sample01.h"

// 應用程序對象

CSample01App theApp;

// 初始化應用程序實例

BOOL CSample01App::InitInstance()

{

m_pMainWnd = new CSample01Frame();

m_pMainWnd->ShowWindow(m_nCmdShow);

m_pMainWnd->UpdateWindow();

return TRUE;

}

// 消息映射

BEGIN_MESSAGE_MAP(CSample01Frame, CFrameWnd)

ON_WM_PAINT()

END_MESSAGE_MAP()

// 構造函數

CSample01Frame::CSample01Frame()

{

Create(NULL, "MFC應用程序框架程序");

}

// WM_PAINT消息響應函數

void CSample01Frame::OnPaint()

{

CPaintDC dc(this);

dc.TextOut(100, 100, "Hello World!");

}



仍象編寫Sample00程序一樣建立一個Win32應用程序工程Sample01(配套程序見"光盤配套程序Sample01"),然後分別向工程添加頭文件Sample01.h和源文件Sample01.cpp,並將上述代碼寫入相應的文件。爲了能順利編譯,還需要修改一下編譯命令,通過"Alt+F7"快捷鍵呼出【Project Settings】對話框,在【Preprocessor definitions】欄的最後添加選項"_AFXDLL",前面用逗號分隔。接下來還需要在【Project Options】欄的最後添加命令行"/MD",用空格同其他命令行參數進行分隔。編譯運行,可以看出效果同SDK方式編寫的Sample00程序是一樣的,但在代碼實現上更加結構化,編寫過程也更加簡單。

接下來,對上述應用程序框架代碼進行分析。首先從MFC應用程序的核心--CWinApp類的派生類CSample01App談起。CWinApp類提供了可以獲取消息並將獲取到的消息分發到應用程序窗口的消息循環和一些關鍵的虛函數,通過對這些虛函數的重載可使開發人員對應用程序的一些固有行爲進行擴展。當把頭文件Afxwin.h包含進來後,就可以在程序中使用包括CWinApp在內的一些MFC類了。一個MFC應用程序有且只能有一個應用程序對象而且必須被以全局方式進行聲明,所以該對象自程序開始運行起就一直駐留在內存。

由於使用了MFC應用程序框架的程序在本質上仍是Windows 應用程序,因此必然需要在程序中存在作爲Windows 應用程序入口的WinMain()函數。在前面的示例代碼中之所以沒有看到WinMain()函數是由於該函數已經通過封裝的手段隱藏到應用程序框架中了。除WinMain()外,CWinApp類成員函數Run()也是隱含執行的,這個函數也是非常重要的,它負責把消息放進應用程序窗口的消息循環中,由WinMain()函數完成對Run()的調用 。

當WinMain()函數尋找到應用程序對象後將立即調用CWinApp類的虛函數InitInstance()。由於CWinApp基類是不知道究竟需要何種主框架窗口的,因此在使用時必須在CWinApp的派生類中對InitInstance()函數進行重載。InitInstance()函數是在應用程序已經開始運行但窗口尚未創建時被調用的,若非由InitInstance()創建了窗口,應用程序是無法擁有窗口的,這也就意味着缺少了InitInstance()函數的應用程序將無法接收、處理消息,對Windows程序而言這也就失去了存在的意義。由此可見,從CWinApp類中進行派生,並且對InitInstance()函數進行重載是編寫MFC應用程序框架程序的必要條件。

除應用程序類外,從CFrameWnd派生的CSample01Frame類還對應用程序的主框架窗口做了描述。在構造函數中調用基類的CFrameWnd成員函數Create(),由Windows 負責創建出實際的窗口結構,並由應用程序框架將其鏈接到C++對象。

本示例程序的大部分功能實際是在MFC的CWinApp和CFrameWnd等基類中完成的,在編程時,只需在派生類中編寫少量功能代碼,C++允許以這樣的方式借用基類中的大量代碼而無須複製代碼。應用程序框架負責提供程序的結構框架,開發人員在此基礎上爲其添加相應的實現代碼,從而可以非常方便地完成一個完整的應用程序。應用程序框架不僅定義了應用程序的結構安排,實際上還包含了更多的C++基類。

4 小結

SDK的API編程方法、MFC的編程方法以及本系列講座後面將要介紹的ATL編程方法是VC++程序設計中比較常用的幾種編程方法,其中MFC以其強大的功能和靈活的編程方式而成爲大多數程序開發人員最經常使用的一種編程方式。本文從基礎問題入手對MFC及其框架程序做了較爲詳細的論述,使讀者能夠對MFC編程有一個基本的認識。

轉自(http://blog.chinaunix.net/uid-9563036-id-352088.html

發佈了70 篇原創文章 · 獲贊 6 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章