MFC程序的基本框架
3.1 MFC項目的類框架
建立Single document MFC應用程序,嚮導會爲我們自動生成代碼,其中有五個重要的類需要注意:
- CAboutDlg(派生自CDialog)
- CMainFrame(派生自CFrameWnd)
- CTestAPP(派生自CWinApp):應用程序類
- CTestDoc(派生自CDocument)
- CTestView(派生自CView)
這五個類中後三個名字中的Test是項目名稱,會隨着項目名字變化而變化。
這裏涉及到的基類的繼承關係可以從下圖看出:
最終繼承自窗口類(CWnd)的類都和窗口實現有關,CAboutDlg負責幫助對話框,CMainFrame負責主框架窗口,CTestView負責視窗口(在主框架窗口之上)。
3.2 應用程序實例
在一個MFC項目中僅有一個類派生自應用程序類(CWinApp),這個類的名字由C+項目名+App構成。
我們知道在Win32應用程序中通過一個實例句柄(hInstance)來唯一標識應用程序本身,MFC中則有所不同,它通過一個**應用程序類的全局對象(theApp)**來標識應用程序本身。
MFC程序運行的第一件事就是創建CTestApp類的實例(theApp):
//Test.CPP
//創建應用程序實例對象
CTestApp theApp;
//CTestApp構造函數
CTestApp::CTestApp()
{
}
創建theApp的同時會調用CTestApp的構造函數,根據C++繼承相關的概念,在調用CTestApp的構造函數之前還會先調用基類CWinApp的構造函數,基類構造函數部分代碼如下:
//APPCORE.CPP
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
if (lpszAppName != NULL)
m_pszAppName = _tcsdup(lpszAppName);
else
m_pszAppName = NULL;
// initialize CWinThread state
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;//this指向theApp對象
ASSERT(AfxGetThread() == this);
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
ASSERT(afxCurrentWinApp == NULL);
pModuleState->m_pCurrentWinApp = this;//this指向theApp對象
ASSERT(AfxGetApp() == this);
......
基類CWinApp構造函數具有一個lpszAppName參數,因此在派生類中應該顯式調用。然而由於該構造函數中聲明瞭默認參數,所以實際上並沒有顯示調用。應用程序類的構造函數完成了應用程序的各種初始化工作。
3.3 WinMain函數
當theApp全局對象成功創建以後就要進入WinMain的環節了,該部分代碼如下:
//APPMODUL.CPP
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)//入口函數
{
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
這部分代碼和Win32中的WinMain並沒有什麼太大的區別,_tWinMain前面第一篇文章已經介紹過是一個兼容UNICODE和ASCII兩種情況的宏。在WinMain函數中調用了AfxWinMain函數,MFC中以Afx開頭的函數是應用程序框架函數,他們都是全局函數,可以在任意一個類中調用,此處AfxWinMain函數負責創建,註冊窗口類,創建,顯示,更新窗口等等工作。
AfxWinMain中分別調用AfxGetThread函數和AfxGetApp函數取得了兩個指針pThread和pApp,然後又通過這兩個指向theApp的指針調用了三個成員函數完成了一個Windows應用程序的全部必要步驟,部分代碼如下:
//WINMAIN.CPP
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();//指向theApp的指針
CWinApp* pApp = AfxGetApp();//指向theApp的指針
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
if (pApp != NULL && !pApp->InitApplication())//重要函數1,完成MFC內部管理方面的工作
goto InitFailure;
if (!pThread->InitInstance())//重要函數2,該函數是一個虛函數,因此這裏調用的是子類的函數
{
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();//重要函數3,完成消息循環
通過pThread調用了虛函數InitInstance,因此實際調用的是子類版本,該函數中完成了窗口類的創建和註冊,窗口的創建,顯示,更新。跟蹤該函數的代碼,在其中會發現下面兩行:
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
很顯然,這兩行會進行窗口的顯示和更新,在本文的後面還會再次提到。
3.4 設計和註冊窗口類
註冊窗口類是在一個叫做CMainFrame::PreCreateWindow的函數中完成的,該函數在窗口產生之前被調用,其代碼大概如下:
//MAINFRM.CPP
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )//進一步調用了其基類的PreCreateWindow函數
return FALSE;
return TRUE;
}
然後進入被調用的函數CFrameWnd::PreCreateWindow,其代碼如下:
//MAINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));//這裏驗證了窗口類是否註冊,如果沒有則註冊
cs.lpszClass = _afxWndFrameOrView;
}
if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4)
cs.style |= FWS_PREFIXTITLE;
if (afxData.bWin4)
cs.dwExStyle |= WS_EX_CLIENTEDGE;
return TRUE;
}
上面這個函數調用AfxDeferRegisterClass來註冊窗口類,實際上它是一個到AfxEndDeferRegisterClass函數的宏,AfxEndDeferRegisterClass的實現如下:
//WINCORE.CPP
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
fToRegister &= ~pModuleState->m_fRegisteredClasses;
if (fToRegister == 0)
return TRUE;
LONG fRegisteredClasses = 0;
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS));
wndcls.lpfnWndProc = DefWindowProc;//給窗口類設置窗口過程函數
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
INITCOMMONCONTROLSEX init;
init.dwSize = sizeof(init);
if (fToRegister & AFX_WND_REG)
{
// Child windows - no brush, no icon, safest default class styles
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpszClassName = _afxWnd;
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WND_REG;
}
if (fToRegister & AFX_WNDOLECONTROL_REG)
{
// OLE Control windows - use parent DC for speed
wndcls.style |= CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpszClassName = _afxWndOleControl;
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WNDOLECONTROL_REG;
}
if (fToRegister & AFX_WNDCONTROLBAR_REG)
{
// Control bar windows
wndcls.style = 0; // control bars don't handle double click
wndcls.lpszClassName = _afxWndControlBar;
wndcls.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WNDCONTROLBAR_REG;
}
if (fToRegister & AFX_WNDMDIFRAME_REG)
{
// MDI Frame window (also used for splitter window)
wndcls.style = CS_DBLCLKS;
wndcls.hbrBackground = NULL;
if (_AfxRegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
}
if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
{
// SDI Frame or MDI Child windows or views - normal colors
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
}
if (fToRegister & AFX_WNDCOMMCTLS_REG)
{
// this flag is compatible with the old InitCommonControls() API
init.dwICC = ICC_WIN95_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WIN95CTLS_MASK);
fToRegister &= ~AFX_WIN95CTLS_MASK;
}
if (fToRegister & AFX_WNDCOMMCTL_UPDOWN_REG)
{
init.dwICC = ICC_UPDOWN_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_UPDOWN_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_TREEVIEW_REG)
{
init.dwICC = ICC_TREEVIEW_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TREEVIEW_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_TAB_REG)
{
init.dwICC = ICC_TAB_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TAB_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_PROGRESS_REG)
{
init.dwICC = ICC_PROGRESS_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_PROGRESS_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_LISTVIEW_REG)
{
init.dwICC = ICC_LISTVIEW_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_LISTVIEW_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_HOTKEY_REG)
{
init.dwICC = ICC_HOTKEY_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_HOTKEY_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_BAR_REG)
{
init.dwICC = ICC_BAR_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_BAR_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_ANIMATE_REG)
{
init.dwICC = ICC_ANIMATE_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_ANIMATE_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_INTERNET_REG)
{
init.dwICC = ICC_INTERNET_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_INTERNET_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_COOL_REG)
{
init.dwICC = ICC_COOL_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_COOL_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_USEREX_REG)
{
init.dwICC = ICC_USEREX_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_USEREX_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_DATE_REG)
{
init.dwICC = ICC_DATE_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_DATE_REG);
}
// save new state of registered controls
pModuleState->m_fRegisteredClasses |= fRegisteredClasses;
// special case for all common controls registered, turn on AFX_WNDCOMMCTLS_REG
if ((pModuleState->m_fRegisteredClasses & AFX_WIN95CTLS_MASK) == AFX_WIN95CTLS_MASK)
{
pModuleState->m_fRegisteredClasses |= AFX_WNDCOMMCTLS_REG;
fRegisteredClasses |= AFX_WNDCOMMCTLS_REG;
}
// must have registered at least as mamy classes as requested
return (fToRegister & fRegisteredClasses) == fToRegister;
}
首先,該函數接受了一個參數fToRegister,這個參數是由很多標誌位通過或運算組合起來的一個整數,他代表着想要註冊的全部類。代碼的第一部分將已經註冊的類的標誌位從fToRegister中刪除,然後通過大量的分支結構來判斷某個預定義類是否在帶註冊類列表中,如果在就調用函數AfxRegisterClass將其註冊,代碼看起來很長,但大部分都是同一功能的簡單重複。
AfxRegisterClass中包含下面的代碼:
//WINCORE.CPP
if (!::RegisterClass(lpWndClass))//註冊窗口類
{
TRACE1("Can't register window class named %s\n",
lpWndClass->lpszClassName);
return FALSE;
}
裏面的RegisterClass函數我們很熟悉,這個函數就是在Win32中我們用來註冊窗口類的函數。
3.5 創建窗口
註冊號窗口類之後就該創建窗口了,這個功能最終是由CWnd::CreateEx來完成的,而這個函數則由Cwnd的派生類CFrameWnd中的方法CFrameWnd::Create來調用
//WINCORE.CPP
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.8X\n",
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;
}
該函數中調用了Windows API函數**::CreateWindowEx**來創建窗口,實際上在調用這個函數之前,還有一次調用了PreCreateWindow函數,目的是爲了讓用戶可以在創建窗口前有機會修改窗口的外觀樣式。CreateEx函數在應用程序執行過程中會執行很多次,因爲一個單文檔MFC應用程序中包含很多個窗口,每個窗口的創建都是依靠這個函數完成的。
然後就是顯示和更新窗口,這部分內容在CTestApp::InitInstance中完成,上面已經介紹過。
3.6 消息循環和窗口過程
前面在看AfxWinMain函數實現的時候提到過裏面通過兩個指針調用了三個函數完成了應用程序的全部初始化創建過程,裏面說的第三個函數就是CWinThread::Run,他的定義如下:
//THRDCORE.CPP
int CWinThread::Run()
{
ASSERT_VALID(this);
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
for (;;)//進入死循環
{
while (bIdle &&
!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))//從消息隊列取消息
{
if (!OnIdle(lIdleCount++))
bIdle = FALSE;
}
//在接受到WM_QUIT時候退出
do
{
if (!PumpMessage())//PumpMessage中調用了很熟悉的GetMessage()
return ExitInstance();
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE);
}
上面這部分就是消息循環的代碼,他主體就是一個for循環,該循環內部調用PumpMessage,這個函數內部先通過GetMessage取消息,然後TranslateMessage和DispatchMessage來翻譯和派送消息,這個過程和Windows API的實現過程並沒有區別。
在前面AfxEndDeferRegisterClass函數設計窗口類時,我們看到了窗口類綁定到了默認窗口過程,所以這裏不再粘貼代碼,指得注意的是看起來MFC完全通過默認窗口過程響應消息,但實際上使用的是消息映射機制,這個後面再說。
3.7 在窗口中顯示一個按鈕
爲了讓窗口中顯示一個按鈕,就應該響應響應窗口的WM_CREATE事件,在MFC中響應該消息的方法就是編寫窗口類的onCreate()函數,以創建按鈕爲例,代碼如下:
private:
CButton btn;//給主框架窗口類添加一個成員變量用於保存按鈕
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
......//這裏略去很多自動生成的代碼
//下面是用戶自己編寫的代碼
btn.Create("按鈕", WS_CHILD|BS_DEFPUSHBUTTON, CRect(0,0,100,100),this,123);
btn.ShowWindow(SW_SHOWNORMAL);
return 0;
}
注意按鈕類的對象不能在消息響應函數中創建,這樣會導致函數結束之後按鈕對象析構。
注意下C++窗口類的對象和窗口本身並不是一個東西,他們之間的關係是窗口類對象中定義的窗口句柄變量保存了與這個C++窗口類相關的窗口的句柄。當窗口銷燬時,窗口類對象是否銷燬取決於該對象的生存期是否結束。窗口類對象銷燬時,其內部的局部變量也會被回收,所以窗口也會跟着銷燬(被析構函數析構)。
總體來說MFC是早已過時的技術,這裏只是因爲好奇隨便看看,應該不會一直更新。