簡述MFC程式生與死 (轉)

worldblog發表於2007-12-09
簡述MFC程式生與死 (轉)[@more@]

  經常從網上好,但開啟VC看了不到幾行,滑鼠就移到最到最右上角,對準那個“X”,咔嚓...(心還暗念:嚓死你!),二話不說在“程式”選單裡尋找qq的存在,開始進入休閒時光!!這可是我經常做的事情,唉!苦於基礎不紮實,經常被美好的程式碼踢出門外。但幸好我還對她有一斯感覺,近來有幸買到侯先生的《深入淺出MFC》,看到第六章:MFC程式的生死因果,覺得是學MFC,喔不,應該是看MFC程式程式碼的好起點,該章對MFC程式(沒有支援Document/View)的生死因果做也詳細的講解,為了加深記憶,我總結了以下流程,供大家參考。


MFC程式的啟動與死亡順序:


1、建立Application theApp

  程式一開始生產一個(且只有一個)Application object物件theApp,也即一個CWinApp物件,這個全域性物件一產生,便其構造,因為並沒有定義CMyWinApp建構函式,所以即執行CWinApp類的建構函式。該函式定義於APPCORE.CPP第75行,你可以自己搜出來啃一啃,因此,CWinApp之中的成員變數將因為theApp這個全域性物件的誕生而獲得與初值。

2、WinMain登場

  用SDK序時,程式的入口點是WinMain函式,而在MFC程式裡我們並沒有看到WinMain函式,哦!~ 原來她是被隱藏在MFC程式碼裡面了。當theApp配置完成後,WinMain登場,慢!細看程式,並沒連到WinMain函式的程式碼啊!這個我也不知道,MFC早已準備好並由連結器直接加到應用程式程式碼中了,原來她在APPMODUL.CPP裡面,好,我們就認為當theApp配置完成後,程式就轉到APPMODUL.CPP來了。那執行什麼呢?看看下面從APPMODUL.CPP摘出來的程式碼:

  extern "C" int WIN

  _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
  {
  // call shared/exported WinMain
  return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
  }

  _tWinMain函式的“_t”是為了支援Unicode而準備的一個宏。

  _tWinMain函式返回值是AfxWinMain函式的返回值,AfxWinMain函式定義於WINMAIN.CPP第21行,稍加整理,去蕪存菁,就可以看到這個“程式進入點”主要做些什麼事:

  int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
  {
  int nReturnCode = -1;
  CWinApp* pApp = AfxGetApp();

  AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow);

  pApp->InitApplication();
  pApp->InitInstance()
  nReturnCode = pApp->Run();

  AfxWinTerm();
  return nReturnCode;
  }

  AfxGetApp()函式是取得CMyWinApp物件指標,故上面函式第6至8行相當於:

  CMyWinApp::InitApplication();
  CMyWinApp::InitInstance()
  CMyWinApp::Run();

  因而導致呼叫:
  CWinApp::InitApplication();  //因為 CMyWinApp 並沒有改寫 InitApplication
  CMyWinApp::InitInstance()  //因為 CMyWinApp 改寫了 InitInstance
  CWinApp::Run();  //因為 CMyWinApp 並沒有改寫 Run

  用過SDK寫程式的朋友,現在可能會發出會心的微笑。

3、AfxWinInit——AFX內部初始化操作

  AfxWinInit是繼CWinApp建構函式之後的第一個操作,主要做的是AFX內部初始化操作,該函式定義於APPINIT.CPP第24行,這裡就不掏出來了,你自己搜出來啃吧!

4、執行CWinApp::InitApplication

  AfxWinInit之後的操作是pApp->InitApplication,我們已知道pApp指向CMyWinApp物件,當呼叫:

  pApp->InitApplication();

  相當於呼叫:

  CMyWinApp::InitApplication();

  但是你要知道,CMyWinApp繼承自CWinApp,而InitApplication又是CWinApp的一個虛擬函式,我們並沒有改寫它(大部分情況下不需改寫它),所以上述操作相當於呼叫:

  CWinApp::InitApplication();

  此函式定義於APPCORE.CPP第125行,你自己搜出來看吧!我就不搬出來了,裡面的操作都是MFC為了內部管理而做的(其實我也看不懂,知道有這回事就好了)。

5、執行CWinApp::InitInstance

  繼InitApplication函式之後,AfxWinMain呼叫pApp->InitInstance。當程式呼叫:

  pApp->InitInstance();

  相當於呼叫:

  CMyWinApp::InitInstance();

  但是你要知道,CMyWinApp繼承自CWinApp,而InitInstance又是CWinApp的一個虛擬函式。由於我們改寫了它,所以上述操作就是呼叫我們自己(CMyWinApp)的這個InitInstance函式。

6、CFrameWnd::Create產生主視窗(並先註冊視窗類)

  現在已經來到CWinApp::InitInstance了,該函式先new一個CMyFrameWnd物件,從而產生主視窗。在建立CMyFrameWnd對之前,要先執行建構函式CMyFrameWnd::CMyFrameWnd(),該函式用Create函式產生視窗:

  CMyFrameWnd::CMyFrameWnd()
  {
  Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL, "MainMenu");
  }

  其中Create是CFrameWnd的成員函式,它將產生一個視窗,用過SDK程式設計序的朋友都知道,要建立主視窗時要先註冊一個視窗類,規定視窗的屬性等,但,這裡使用哪一個視窗類呢?Create函式第一個引數(其它引數請參考MSDN或《深出淺出MFC》詳解)指定視窗類設為NULL又是什麼意思啊?意思是要以MFC內建的空中類產生一個標準的外框視窗,但,我們的程式一般都沒有註冊任何視窗類呀!噢,Create函式在產生視窗之前會引發視窗類的註冊操作。

  讓我們先挖出Create函式都做了些什麼操作,Create函式定義於WINFRM.CPP的第538行(在此我就不把程式碼Copy過來了,你自己開啟出來看吧),函式在562行呼叫CreateEx函式,由於CreateEx是CWnd的成員函式,而CFrameWnd是從CWnd繼而來,故將呼叫CWnd::CreateEx。此函式定義於WINCORE.CPP第665行,下面是部分程式碼:

  BOOL CWnd::CreateEx(D 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);
  ...
}

  用過SDK程式設計序的朋友,看到上面程式碼應該有一點感覺了吧,函式中呼叫的PreCreate是虛擬函式,在CWnd和CFrameWnd之中都有定義。由於this指標所指物件的緣故,這裡應該呼叫的是CFrameWnd::PreCreateWindow。該函式定義於WINFRM.CPP第521行,以下是部分程式碼:

  BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
  {
  if (cs.lpszClass == NULL)
  {
  VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
  cs.lpszClass = _afxWndFrameOrView;  // COLOR_WINDOW background
 }
  ...
  }

  其中AfxDeferRegisterClass是一個定義於AFXIMPL.H中的宏。該宏如下:

  #define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)

  注:這裡有宏和《深入淺出MFC》的不一樣,以上程式碼是從Visual C++ 6.0摘取。

  AfxEndDeferRegisterClass定義於WINCORE.CPP第3619行,該函式很複雜,主要是註冊五個視窗類(哇!終於看到視窗類了,怎麼用5個呢?我還不清楚),不同類的PreCreateWindow成員函式都是在視窗產生之前一刻被呼叫,準備用來註冊視窗類。如果我們指定的視窗類是NULL,那麼就使用預設類。從CWnd及其各個派生類的PreCreateWindow成員函式可以看出,整個針對不同功能的視窗使用了哪些視窗類。

7、視窗顯示與

  CMyFrameWnd::CMyFrameWnd結束後,視窗已經誕生出來;程式流程又回到CMyWinApp::InitInstance,於是呼叫ShowWindow函式令視窗顯示出來,並呼叫UpdateWindow函式令程式送出WM_PAINT訊息。在SDK程式中,訊息是透過視窗函式來處理,而現在視窗函式在哪裡、又如何送到視窗函式手中呢?那要從CWinApp::Run說起了。

8、執行CWinApp::Run——程式生命的活水源頭

  在執行完CMyWinApp::InitInstance函式後,程式的腳步到了AfxWinMain函式的pApp->Run了,現在我們已知道這將執行CWinApp::Run函式,該函式定義於APPCORE.CPP第391行,下面是程式程式碼:

  int CWinApp::Run()
  {
  if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
  {
  // Not launched /Embedding or /Automation, but has no main window!
  TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.n");
  AfxPostQuitMessage(0);
  }
  return CWinThread::Run();
  }

  函式呼叫CWinThread::Run函式,該函式定義於THRDCORE.CPP第456行,在這裡我就不Copy出來了。函式在第480行呼叫了PumpMessage函式,該函式定義於THRDCORE.CPP第810行,整理後的部分程式碼如下:

  BOOL CWinThread::PumpMessage()
  {
  if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
  {
  return FALSE;
 }

  // process this message
 if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
  {
  ::TranslateMessage(&m_msgCur);
  ::DispatchMessage(&m_msgCur);
  }
  return TRUE;
  }

  該函式主要操作是將訊息由::DispatchMessage送到視窗函式(CWnd::DefWindowProc)中,但程式一般沒有提供任何視窗函式,但在AfxEndDeferRegisterClass中,在註冊五種視窗類之前已經指定視窗函式為:

  wndcls.lpfnWndProc = DefWindowProc;

  雖然視窗函式被指定為DefWindowProc成員函式(CWnd::DefWindowProc),但事實上訊息並不是被唧往該處,而是一個名為AfxWndProc的全域性函式去。

9、把訊息與處理函式連線在一起——Message Map機制

  到此,主視窗已經產生,等待的就是各種訊息了,然後呼叫相應的處理函式,然而訊息和處理函式怎樣連線在一起呢?MFC採用了Message Map機制(訊息對映機制),提供給應用程式使用的“很方便的介面”的兩組宏,其原理我還不大清楚,在這裡也無法講解,主要用法是:先在類宣告中結合DECLARE_MESSAGE_MAP()給出處理函式,如:

  class CMyFrameWnd : public CFrameWnd
  {
  public:
  CMyFrameWnd();
  afx_msg void OnPaint();  // for WM_PAINT
  afx_msg void OnAbout();  // for WM_COMMAND (IDM_ABOUT)
  DECLARE_MESSAGE_MAP()
  }

  再在相應的.CPP的任何位置(當然不能在函式之內)使用BEBIN_MESSAGE_MAP()和END_MESSAGE_MAP()宏把相應的訊息加入去,如:

  BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
  ON_COMMAND(IDM_ABOUT, OnAbout)
  ON_WM_PAINT()
  END_MESSAGE_MAP()

  為什麼經過這樣的宏之後,訊息就會自動流往指定的函式去呢?謎底在於Message Map的結構設計,自己找《深入淺出MFC》第3章的Message Map模擬程式去啃一啃吧!


  好了,就寫到這了,如果你是剛接觸MFC,我想看了之後你可能也有點糊塗,SORRY啦!我是從來沒有寫過總結的,沒事!把侯先生的《深入淺出MFC》拿出來啃幾遍就不會了。

  2001-11-26 16:15  ">  阿青


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990484/,如需轉載,請註明出處,否則將追究法律責任。

相關文章