上一講雞啄米講的是VS2010應用程序工程中文件的組成結構,可能大家對工程的運行原理還是很模糊,理不出頭緒,畢竟跟C++編程入門系列中的例程差別太大。這一節雞啄米就為大家分析下MFC應用程序框架的運行流程。

       一.SDK應用程序與MFC應用程序運行過程的對比

       程序運行都要有入口函數,在之前的C++教程中都是main函數,而Windows應用程序的入口函數是WinMain函數,MFC程序也是從WinMain函數開始的。下面雞啄米就給出用Windows SDK寫的“HelloWorld”程序,與應用程序框架進行對比,這樣能更好的了解框架是怎樣運行的。Windows SDK開發程序就是不使用MFC類庫,直接用Windows API函數進行軟件開發。雞啄米不是要講解SDK開發,只是為了對比而簡單介紹,至于SDK開發可以在大家學完MFC以后選擇是否要研究,一般來說有簡單了解就可以了。

       SDK應用程序

       首先,給出Windows SDK應用程序“HelloWorld”的源碼:  

C++代碼
  1. #include <windows.h>    
  2.   
  3. LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);   
  4.      
  5. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)      
  6. {      
  7.   const static TCHAR appName[] = TEXT("Hello world");      
  8.   WNDCLASSEX myWin;      
  9.   myWin.cbSize = sizeof(myWin);      
  10.   myWin.style = CS_HREDRAW | CS_VREDRAW;      
  11.   myWin.lpfnWndProc = myWndProc;      
  12.   myWin.cbClsExtra = 0;      
  13.   myWin.cbWndExtra = 0;      
  14.   myWin.hInstance = hInstance;      
  15.   myWin.hIcon = 0;      
  16.   myWin.hIconSm  = 0;      
  17.   myWin.hCursor = 0;      
  18.   myWin.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);      
  19.   myWin.lpszMenuName = 0;      
  20.   myWin.lpszClassName = appName;      
  21.   //Register      
  22.   if (!RegisterClassEx(&myWin)) return 0;      
  23.   const HWND hWindow = CreateWindow(      
  24.     appName,      
  25.     appName,      
  26.     WS_OVERLAPPEDWINDOW,      
  27.     CW_USEDEFAULT,      
  28.     CW_USEDEFAULT,      
  29.     CW_USEDEFAULT,      
  30.     CW_USEDEFAULT,      
  31.     0,      
  32.     0,      
  33.     hInstance,      
  34.     0);      
  35.   ShowWindow(hWindow,iCmdShow);      
  36.   UpdateWindow(hWindow);      
  37.   {      
  38.     MSG msg;      
  39.     while(GetMessage(&msg,0,0,0))      
  40.     {      
  41.       TranslateMessage(&msg);      
  42.       DispatchMessage(&msg);      
  43.     }      
  44.     return (int)msg.wParam;      
  45.   }      
  46. }      
  47.      
  48. LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)      
  49. {      
  50.   if (msg==WM_PAINT)      
  51.   {      
  52.     PAINTSTRUCT ps;      
  53.     const HDC hDC = BeginPaint(hWindow,&ps);      
  54.     RECT rect;      
  55.     GetClientRect(hWindow,&rect);      
  56.     DrawText(hDC,TEXT("HELLO WORLD"),-1,&rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);      
  57.     EndPaint(hWindow,&ps);      
  58.     return 0;      
  59.   }      
  60.   else if (msg==WM_DESTROY)      
  61.   {      
  62.     PostQuitMessage(0);      
  63.     return 0;      
  64.   }      
  65.   return DefWindowProc(hWindow,msg,wParam,lParam);      
  66. }  

       上面的程序運行的流程是:進入WinMain函數->初始化WNDCLASSEX,調用RegisterClassEx函數注冊窗口類->調用ShowWindow和UpdateWindow函數顯示并更新窗口->進入消息循環。關于消息循環再簡單說下,Windows應用程序是消息驅動的,系統或用戶讓應用程序進行某項操作或完成某個任務時會發送消息,進入程序的消息隊列,然后消息循環會將消息隊列中的消息取出,交予相應的窗口過程處理,此程序的窗口過程函數就是myWndProc函數,窗口過程函數處理完消息就完成了某項操作或任務。本例是要顯示“HELLO WORLD”字符串,UpdateWindow函數會發送WM_PAINT消息,但是此消息不經過消息隊列而是直接送到窗口過程處理,在窗口過程函數中最終繪制了“HELLO WORLD”字符串。

VS2010/MFC編程入門之四(MFC應用程序框架分析)

       MFC應用程序

       下面是MFC應用程序的運行流程,通過MFC庫中代碼進行分析:

       首先在HelloWorld.cpp中定義全局對象theApp:CHelloWorldApp theApp;。調用CWinApp和CHelloWorldApp的構造函數后,進入WinMain函數(位于appmodul.cpp中)。

C++代碼
  1. extern "C" int WINAPI   
  2. _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,   
  3.     _In_ LPTSTR lpCmdLine, int nCmdShow)   
  4. #pragma warning(suppress: 4985)   
  5. {   
  6.     // call shared/exported WinMain   
  7.     return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);   
  8. }  

       在TCHAR.h中,有此定義:#define _tWinMain   WinMain,所以這里的_tWinMain就是WinMain函數。它調用了AfxWinMain函數(位于WinMain.cpp中)。

C++代碼
  1. int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow)   
  2. {    
  3.        .............略   
  4.        // App global initializations (rare)   
  5.        if (pApp != NULL && !pApp->InitApplication())   
  6.               goto InitFailure;   
  7.   
  8.        if (!pThread->InitInstance())   
  9.        {   
  10.               .........略   
  11.        }   
  12.   
  13.        // Run函數位于THRDCORE.cpp中,由此函數進入消息循環   
  14.        nReturnCode = pThread->Run();   
  15.   
  16.        ..............略   
  17.   
  18.        return nReturnCode;   
  19. }   

       上面InitInstance函數的代碼如下:

C++代碼
  1. BOOL CTestApp::InitInstance()        
  2. {       
  3.        .............略       
  4.        CSingleDocTemplate* pDocTemplate;       
  5.        pDocTemplate = new CSingleDocTemplate(       
  6.               IDR_MAINFRAME,       
  7.               RUNTIME_CLASS(CTestDoc),       
  8.               RUNTIME_CLASS(CMainFrame),      // main SDI frame window       
  9.               RUNTIME_CLASS(CTestView));     
  10.        if (!pDocTemplate)   
  11.              return FALSE;     
  12.        AddDocTemplate(pDocTemplate);       
  13.        // Parse command line for standard shell commands, DDE, file open       
  14.       
  15.        CCommandLineInfo cmdInfo;       
  16.        ParseCommandLine(cmdInfo);       
  17.       
  18.        //ProcessShellCommand位于AppUI2.cpp中,注冊并創建窗口       
  19.        if (!ProcessShellCommand(cmdInfo))       
  20.              return FALSE;       
  21.       
  22.        m_pMainWnd->ShowWindow(SW_SHOW);       
  23.        m_pMainWnd->UpdateWindow();       
  24.       
  25.        return TRUE;       
  26. }      

       InitInstance中的ProcessShellCommand函數又調用了CMainFrame的LoadFrame函數注冊并創建了窗口,執行完ProcessShellCommand函數以后,調用了m_pMainWnd的ShowWindow和UpdateWindow函數顯示并更新框架窗口。這些是不是與上面的SDK程序十分類似?

       接下來該是消息循環了,上面的AfxWinMain函數中調用了pThread的Run函數(位于THRDCORE.cpp中),在Run中包含了消息循環。Run函數的代碼如下:

C++代碼
  1. int CWinThread::Run()       
  2. {       
  3.         .............略       
  4.         // phase2: pump messages while available       
  5.         do      
  6.         {       
  7.               // pump message, but quit on WM_QUIT       
  8.               if (!PumpMessage())       
  9.                      return ExitInstance();       
  10.       
  11.               // reset "no idle" state after pumping "normal" message       
  12.               if (IsIdleMessage(&m_msgCur))       
  13.               {       
  14.                      bIdle = TRUE;       
  15.       
  16.                      lIdleCount = 0;       
  17.       
  18.               }       
  19.        } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));       
  20.        ..............略       
  21. }       
  22.         
  23. BOOL CWinThread::PumpMessage()       
  24. {     
  25.        return AfxInternalPumpMessage();    
  26. }    
  27.   
  28. BOOL AFXAPI AfxInternalPumpMessage()   
  29. {   
  30.        _AFX_THREAD_STATE *pState = AfxGetThreadState();   
  31.       
  32.        if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))          
  33.        {       
  34.              .............略       
  35.        }       
  36.        ...............略       
  37.        if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))   
  38.        {   
  39.              ::TranslateMessage(&(pState->m_msgCur));   
  40.              ::DispatchMessage(&(pState->m_msgCur));   
  41.        }     
  42.       
  43.        return TRUE;       
  44. }       

       我們看到PumpMessage中通過調用GetMessage、TranslateMessage、DispatchMessage等建立了消息循環并投遞消息。

       窗口過程函數AfxWinProc形式如下:

C++代碼
  1. LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT nMsg,WPARAM wParam, LPARAM lParam)   
  2. {   
  3.       ……   
  4.       CWnd*pWnd=CWnd::FromHandlePermanent(hWnd);  
  5.       ReturnAfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);   
  6. }  

       兩者運行過程對比

       到此,通過對比可以發現,MFC應用程序的運行流程與SDK程序是類似的,都是先進行一些初始化過程,再注冊并創建窗口,然后顯示、更新窗口,最后進入消息循環,消息都由窗口過程函數處理。現在大家是不是覺得有些頭緒了?在運行流程上有基本的掌握即可。

       二.MFC應用程序框架主要類之間的關系

       在第二講中,給大家演示了如何利用應用程序向導生成單文檔應用程序框架,可以看到程序的基本框架和必要的代碼都自動生成了,上一講又講解了文件組成結構,實際上在前面自動生成的框架中比較重要的類包括以下幾個:CHelloWorldApp、CMainFrame、CHelloWorldDoc和CHelloWorldView,至于其他的類比如CClassView、CFileView等都是在框架窗口(CMainFrame)上創建的面板等,不是必要的。

       雞啄米就四個主要類的關系簡單講下,CHelloWorldApp類處理消息,將收到的消息分發給相應的對象。CMainFrame是視圖CHelloWorldView的父窗口,視圖CHelloWorldView就顯示在CMainFrame的客戶區中。視圖類CHelloWorldView用來顯示文檔類CHelloWorldDoc中的數據,并根據對視圖類的操作修改文檔類的數據。一個視圖類只能跟一個文檔類相聯系,而一個文檔類可以跟多個視圖類相聯系。關于視圖類和文檔類的關系后面會詳細講解。

       本節VC++/MFC編程入門教程內容比較多,主要是讓大家對MFC應用程序的運行原理有大概的了解。對于以后的MFC開發有很多好處。如果有問題請在雞啄米博客留言交流。謝謝。

 

除非特別注明,雞啄米文章均為原創
轉載請標明本文地址:http://www.cpbsu.com/software/145.html
2012年3月16日
作者:雞啄米 分類:軟件開發 瀏覽: 評論:43