MFC應用程序框架(轉)


對於程序員來說,如果要提高編程效率,一個好用的,功能強大的並且可以得心應手使用的編程工具往往會給我們程序員帶來莫大的方便。其實對於現在的編程工具來說,使用哪一種工具都不是問題的關鍵,重要的是你能夠使用到什么程度,畢竟現在的工具都是非常的強大,對於一般的編程任務來說還沒有不能夠勝任的工具,否則的話恐怕他就不可能在這個世界上存在哪怕是只有一個月的生命。
 但是根據個人所好以及周圍的人的影響,我們都會去使用某一種或者幾種工具。比較Visual Basic 、C++ Builder和Delphi等編程工具,用VC++編寫Windows應用程序可以說是最富於挑戰性和艱巨性。在本文中我無意去比較各種工具的好壞,僅就我自己學習Visual C++的MFC的體會和心得拿出來與大家一起分享和交流,也希望可以結識更多的志同道合的朋友。

  就我個人的偏見,學習VC++就應該要學習他的類庫MFC(Microsoft Foundation Classes)。也許有的人一聽說MFC就有點望而生畏,這是可以理解的,畢竟Microsoft雖然給了我們一個強大而且非常復雜的類庫,但是沒有給我們帶來學習他的好的方便之處。回想自己學習MFC時的無助和迷茫,以及所走過的彎路,現在想起來還心有余悸,雖然我現在也還是處於非常初級的初級入門階段,但是還是很樂意把自己的心得和體會拿出來一起與大家分享。也希望得到大家的指點。

  一、SDK應用程序結構

  我學習MFC之路可是從windows編程開始的(可能這一開始就是彎路了)。首先也請大家跟着我一起看一個SDK應用程序結構的Windows應用程序。當然也是經典的“Hello world!”了,編寫的過程就不必羅嗦了,下面給出他的主要源代碼(我使用向導生成的,但是為了閱讀的方便經過了一點點的編輯):

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
 // TODO: Place code here.
 MSG msg;
 ………………………………
 MyRegisterClass(hInstance);
 if (!InitInstance (hInstance, nCmdShow)) 
 {
  return FALSE;
 }

 // Main message loop:
 while (GetMessage(&msg, NULL, 0, 0)) 
 {
  if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 
  {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
 }
 return msg.wParam;
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
 ……………………
 return TRUE;
}

//窗口函數WndProc(),回調函數

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 int wmId, wmEvent;
 ……………………
 switch (message) 
 {
  case WM_COMMAND:
   ………………
   break;
  case WM_PAINT:
   hdc = BeginPaint(hWnd, &ps);
   // TODO: Add any drawing code here...
   RECT rt;
   GetClientRect(hWnd, &rt);
   DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
   EndPaint(hWnd, &ps);
   break;
  case WM_DESTROY:
   ……
  default:
   return DefWindowProc(hWnd, message, wParam, lParam);
 }
 return 0;
}


  SDK之"Hello World"源程序

  首先讓我們看一下這個程序的結構,WinMain()是函數的入口點,該函數的主要任務是完成一些初始化的工作和維護了一個消息循環。他們的工作流程如下:入口(WinMain())---à MyRegisterClass()---->InitInstance ()-àwhile消息循環。函數由入口開始執行,之后調用 MyRegisterClass()注冊窗口類,之后InitInstance ()生成並顯示窗口,這樣之后,就完成了一個窗口的初始化工作了(當然,在 MyRegisterClass(),InitInstance ()中都需要調用相應的API函數來具體的實現,不過我這里重點分析的是他的結構,所以不考慮他的具體實現細節),然后就是維護消息循環,至此,這個程序的基本結構就差不多建立了。以后程序的運作就靠個消息循環來推動了。

  現在,再讓我們看看那個消息循環的結構,在例子程序中,我們是要程序在窗口中輸出一句"Hello World"。在主程序中我們似乎已經把應用程序的框架全部分析的滴水不漏了,但是沒有看到要求程序輸出"Hello World" 呀?這就是Windows消息的作用了,我們當然還記得剛剛我們說過主程序還維持了一個消息循環,不錯,就是在這個循環里面大有文章。Window應用程序的特點就是消息驅動,當系統或者用戶要求應用程序完成某一個任務的時候,所依靠的就是消息,系統會把用戶的要求或者系統的要求放到一個消息結構中,然后發送給應用程序,再去處理。我們現在來看看應用程序是怎么來完成我們的任務的。在應用程序初始化完成之后,調用了一個顯示窗口的API函數,所以系統知道了程序要顯示窗口了,此時(注意,這里就是產生消息的時機),此時就會在消息隊列中產生一個WM_PAINT消息,這樣,應用程序的消息循環就可以捕捉到這個消息並且將它發送給窗口函數(注意,這個函數是由系統調用的),然后窗口函數就處理這個消息,我們就是在他處理這個消息的時候讓他完成我們的任務的。從這個過程中我們可以看到,如果我們要與程序交互的話,需要做得僅僅就是選擇適當的時機讓系統產生消息了。現在,我們終於可以完全明白SDK的程序的運作過程了。

  一個SDK的應用程序的框架就這樣被建立了,下面我們再來看看如何建立一個MFC的應用程序的框架,以及這兩者之間的對應關系。

 

二、 MFC應用程序結構

  在前面我分析了一個經典的SDK應用程序的結構,現在可是要進入主題“MFC應用程序結構”了。MFC應用程序有好多種,為了能夠更清楚地與前面的文章形成對比,我們在這里看一個SDI的應用程序,當然例子還是經典的“Hello World”了。在使用向導生成應用程序后,會發現有好幾個文件,首先我們不管有哪些文件,按照程序執行得主線抽取主要的源程序分析一下再說(因為MFC生成的應用程序不是很方便閱讀,所以在這里我將他們重新編輯了)。

CHelloWorldApp theApp;

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPTSTR lpCmdLine, int nCmdShow)
{
 CWinThread* pThread = AfxGetThread();
 CWinApp* pApp = AfxGetApp();

 // AFX internal initialization

 if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
  goto InitFailure;

 // App global initializations (rare)
 if (pApp != NULL && !pApp->InitApplication())
  goto InitFailure;
  // Perform specific initializations
 if (!pThread->InitInstance())
 {
  if (pThread->m_pMainWnd != NULL)
  {
   TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
   pThread->m_pMainWnd->DestroyWindow();
  }
  nReturnCode = pThread->ExitInstance();
  goto InitFailure;
 }
 nReturnCode = pThread->Run();

 InitFailure:
 ……………………
 AfxWinTerm();
 return nReturnCode;
}

BOOL CWinApp::InitApplication()
{
 if (CDocManager::pStaticDocManager != NULL)
 {
  if (m_pDocManager == NULL)
   m_pDocManager = CDocManager::pStaticDocManager;
   CDocManager::pStaticDocManager = NULL;
 }

 if (m_pDocManager != NULL)
  m_pDocManager->AddDocTemplate(NULL);
 else
  CDocManager::bStaticInit = FALSE;
  return TRUE;
}

BOOL CHelloWorldApp::InitInstance()
{
 AfxEnableControlContainer();
 ………………………………
 // Change the registry key under which our settings are stored.
 // TODO: You should modify this string to be something appropriate
 // such as the name of your company or organization.

 SetRegistryKey(_T("Local AppWizard-Generated Applications"));
 LoadStdProfileSettings(); // Load standard INI file options (including MRU)
 // Register the application's document templates. Document templates
 // serve as the connection between documents, frame windows and views.

 CSingleDocTemplate* pDocTemplate;
 pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CHelloWorldDoc),
  RUNTIME_CLASS(CMainFrame), // main SDI frame window
  RUNTIME_CLASS(CHelloWorldView));

 AddDocTemplate(pDocTemplate);

 // Parse command line for standard shell commands, DDE, file open
 CCommandLineInfo cmdInfo;
 ParseCommandLine(cmdInfo);

 // Dispatch commands specified on the command line
 if (!ProcessShellCommand(cmdInfo))
  return FALSE;
  // The one and only window has been initialized, so show and update it.
 m_pMainWnd->ShowWindow(SW_SHOW);
 m_pMainWnd->UpdateWindow();

 return TRUE;
}

BOOL CWinApp::InitInstance()
{
 return TRUE;
}


  MFC應用程序之“Hello World”

  咋一眼看上去,好像這個程序無從下手分析,甚至連程序的入口點都找不到。其實,上面的程序還是經過整理后才有如此模樣。好了,一樣的來看看這個程序是怎么運行的吧(要注意的事上面的程序來自於不同的文件,這里排版在一起只是為了更清楚地表示程序的結構,至於MFC的文件組織我會在下面一個話題中具體的分析,這里可以暫時不考慮)。

  首先,在程序的開始處,首先定義了一個全局變量theApp,我們現在只需要知道他代表了整個程序的存在,然后程序開始介入入口點。有沒有搞錯,入口點在哪里?不及,其實int AFXAPI AfxWinMain()就是這個程序的入口點,奇怪吧!不過沒有關系,就好像我們第一次看到C語言中的main()函數一樣,只要了解就可以了。在AfxWinMain()中分別調用了一些類的成員函數,仿照前面的分析方法,也可以畫出一個程序執行路徑圖。入口點----〉AfxGetThread()------〉AfxGetApp()-------àAfxWinInit()-------àpApp->InitApplication()-----àpThread->InitInstance()------àpThread->Run()。可以看到,程序一樣有一個執行的線索可循,但是,相對於SDK來說,如今已經面目全非了,過去的那種清晰的程序結構在這些程序中也有嗎?答案是肯定的,只不過他們的具體實現在MFC中都進行了包裝而已,那么,還是來看看這個應用程序是如何啟動並且運行的吧。

  程序由AfxWinMain()開始運行后,首先調用了AfxGetApp()來獲取應用程序的對象指針pApp,然后通過這個指針調用有關的成員函數來完成初始化和啟動工作,最后就調用了Run()函數,在這里,Run()函數就是代表了SDK中的消息循環。

  事情的發展在預料中進行着,但是似乎還遺漏了一點什么似的?不錯,在上面我們的確是還有一樣工作沒有完成,這就是我們需要的”Hello World”好像還沒有輸出來!這不是我的疏忽,而是故意的安排,因為MFC中采用了一種全新(當然是相對於SDK來說的了)的消息處理機制,至少在表面上來說是這樣的。然而,在這里我不打算一下子就把問題解決掉,畢竟這有點復雜,等我們明白了MFC的文件之間的關系后我會在回答這個問題。

  我沒有想到這篇文章會這么長,剛剛開始的時候我以為我可以一下子就把這個問題說清楚地,但是事實上我寫作的思路也像我分析程序時一樣,竟然是一個漫長的過程!所以我也有必要提醒一下自己以及讀這篇文章的朋友,應該休息一下了。我現在都開始慶幸我把這個問題分成好幾個小問題來解決了,我會在接下來的話題中繼續討論的事MFC程序所生成的文件以及它們之間的調用關系。

 

三、MFC程序結構剖析

  在前面我分別給出了SDK和MFC應用程序的框架,並且稍微理了一下它們之間的對應關系。但是對於MFC程序來說,要想真正搞懂它的框架的話,還是不夠的。現在我要做的事就是繼續去分析一下上面的MFC程序,期望能夠如我的標題所言,明明白白的看透MFC的應用程序框架。

  首先就是看一下應用向導生成的MFC應用程序都有哪些主要的文件,包含哪些類以及相關類的對應功能和他們之間的關系(再次說明,因為我在這里關心的是程序的框架結構,所以對於一些與話題關系不很大的東西我將會略去不提)。

  在上面的SDI應用程序中,向導為我們生成了4個主要的類,這些類都是MFC類的派生類,分別包含在對應的頭文件以及實現文件中。

  1、 框架窗口類及其相關的文件

  框架窗口類對應應用程序的主窗口,明白這一點后就可以建立一個對這個類的感性認識了,他的定義是在頭文件MainFrm.h中,而實現則是在MainFrm.cpp文件中。所有與框架窗口向光的功能都是在這里定義和實現的。

  2、 文檔類及其相關的文件

  文檔類在應用程序中沒有直觀的對應關系,但是,我們應該知道的一點就是MFC的框架的一個特點就是文檔/視圖結構。這里可以抽象一點地來理解,比如說我們在Word中打開了一個文件,其實,這個文件就是文檔,而我們看到的只不過是這個文件一個視圖,所以說文檔提供了應用程序顯示的支持,但是我們真正看到的應用程序顯示的則是一個視圖,文檔類的定義是在“Hello WorldDoc.h”中,而他的實現則是在“Hello WorldDoc.cpp”文件中。

  3、 視圖類及其相關的文件

  視圖類是用來顯示文檔對象的內容的,我們在Word程序中所看到的界面就是一個視圖,我們要修改,畫圖首先操作的對象就是視圖,所以,視圖類就如他的名稱所指出的那樣,提供了從用戶角度看到的東西。視圖類的定義在“Hello WorldView.h”中,實現是在文件“Hello WorldView.h”中。

  4、 應用程序類及其相關的文件

  MFC應用程序的初始化,啟動運行和結束都是由應用程序對象完成的。他對應的文件是”Hello World.cpp”和”Hello World.h”。

  在大致的了解了應用程序對象的作用之后,現在我們來看看它們之間的關系(如下圖所示)。


  從上面的圖中可以大致的看出MFC應用程序對象之間的關系了。應用程序一開始(此處的內容可以參考上一篇)生成應用程序對象,然后,在InitInstance中創建中將會創建文檔模板對象(通過CdocManager管理),這樣一來,應用程序就可以通過建立的模板對象來管理文檔、視圖、和框架窗口。

  至此,我的這篇文章終於可以暫時告一段落了,在文中我主要分析的是應用程序的框架,所以忽略了很多的實現細節。其實,學習MFC是一個非常漫長的路,處在不同的階段都會產生不同的看法和認識,相信隨着學習的深入,每一個人都會有自己的理解。但是在這里我給出的自己的經驗就是:在學習MFC的時候,一定要在宏觀上對他的應用程序框架有個感性的認識!因為這樣之后,我們就會在學習的時候有個明顯的方向,知道自己學習的是什么東西。我在一開始的時候,就沒有認識到這一點,所以往往在學習過程中,往往會不知道下一步應該做什么,也不明白自己看的東西是做什么用的。希望通過這篇文章可以與大家一起學習!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM