深入淺出MFC筆記2-MFC程式如何包裝Win32程式
從上一節的Win32程式與MFC程式的對比中可以看到,MFC程式中可執行程式碼只有MyApp::InitInstance()和MyFrame::MyFrame(),可以想到MFC程式一定會呼叫此兩個函式,如果你手中有一個好的整合開發環境(如果沒有,那你最好去安裝一個^_^),可以在這兩個函式中設定斷點,然後執行程式,程式首先會在InitInstance函式中中斷,此時檢視程式呼叫堆疊,可以看到如下圖所示的函式呼叫順序:
嗯!我們看到堆疊中有Win32程式入口函式WinMain,說明MFC已經為我們定義了WinMain函式,MFC程式也是從WinMain函式開始執行。
然後進入函式WinMain發現其只是呼叫了AfxWinMain函式,其程式碼如下:
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, __in LPTSTR lpCmdLine, int nCmdShow) { ASSERT(hPrevInstance == NULL); int nReturnCode = -1; 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) { TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n"); pThread->m_pMainWnd->DestroyWindow(); } nReturnCode = pThread->ExitInstance(); goto InitFailure; } nReturnCode = pThread->Run(); InitFailure: #ifdef _DEBUG // Check for missing AfxLockTempMap calls if (AfxGetModuleThreadState()->m_nTempMapLock != 0) { TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n", AfxGetModuleThreadState()->m_nTempMapLock); } AfxLockTempMaps(); AfxUnlockTempMaps(-1); #endif AfxWinTerm(); return nReturnCode; }
主要的函式呼叫過程如下:
AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
pApp->InitApplication();
pThread->InitInstance();
pThread->Run();
AfxWinTerm();
這裡先不對每個呼叫進行說明,只用記住這裡有這些過程呼叫。
這部分裡面就有pThread->InitInstance()呼叫,可以知道pThread一定是指向MyApp物件的(CWinThread是CWinApp的基類,CWinApp是MyApp的基類)。
這裡順便說明下MFC的類層次結構:
CObject
|-CCmdTarget
|-CWinThread
| |-CWinApp
|
|-CWnd
| |-CFrameWnd
| |-Cview
| |-CDialog
|
|-CDocument
|
|-CDocTemplate
|-CSingleDocTemplate
|-CMultiDocTemplate
上面只是列出了和MFC底層機制相關的部分類,更加詳細的內容可以參看MFC幫助文件。在InitInstance中我們建立了一個MyFrame物件賦值給m_pMainWindow指標,之後顯示更新視窗,我們有理由猜測註冊視窗類別和建立Windows視窗物件的過程是在建立MyFrame物件時完成的,下面深入MyFrame的建立過程。
還是在整合環境中執行進入MyFrame的建立過程,最終可以看到如下圖所示的呼叫堆疊:
可以看到依次呼叫CFrameWnd::Create,CWnd::CreateEx,CFrameWnd::PreCreateWindow,AfxEndDefRegisterClass,_AfxRegisterWithIcon函式,_AfxRegisterWithIcon程式碼如下:
AFX_STATIC BOOL AFXAPI _AfxRegisterWithIcon(WNDCLASS* pWndCls,
LPCTSTR lpszClassName, UINT nIDIcon)
{
pWndCls->lpszClassName = lpszClassName;
HINSTANCE hInst = AfxFindResourceHandle(
ATL_MAKEINTRESOURCE(nIDIcon), ATL_RT_GROUP_ICON);
if ((pWndCls->hIcon = ::LoadIcon(hInst, ATL_MAKEINTRESOURCE(nIDIcon))) == NULL)
{
// use default icon
pWndCls->hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
}
return AfxRegisterClass(pWndCls);
}
上面的程式碼中就有AfxRegisterClass函式呼叫,此函式又呼叫了::AfxCtxRegisterClass,這個函式是用巨集定義的,呼叫了RegisterClass函式註冊視窗。
定義的巨集如下:AFX_ISOLATIONAWARE_STATICLINK_FUNC(ATOM ,RegisterClassW,(const WNDCLASSW*lpWndClass),(lpWndClass),0)
書中寫的是在AfxRegisterClass函式中直接呼叫了::RegisterClass,和現在分析的並不一樣,可能是版本更新後的調整。
現在註冊視窗類的程式碼已經找到,下面是建立Windows物件的過程。
通過上面的呼叫堆疊進入CWnd::CreateEx函式,在呼叫CFrameWnd::PreCreateWindow函式後有::AfxCtxCreateWindowEx呼叫,此函式和上面::AfxCtxRegisterClass一樣是通過巨集定義的,實際上就是呼叫了::CreateWindowEx,這樣建立視窗的程式碼也找到了,接著看下視窗函式的指定。
在註冊視窗前有呼叫AfxEndDeferRegisterClass,程式碼如下:
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
// mask off all classes that are already registered
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
fToRegister &= ~pModuleState->m_fRegisteredClasses;
if (fToRegister == 0)
return TRUE;
LONG fRegisteredClasses = 0;
// common initialization
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults
wndcls.lpfnWndProc = DefWindowProc;
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
...
}
嗯?上面用的視窗函式竟然是DefWindowProc!這其中的玄機等學習MFC的訊息處理時再來細說。
現在我們再MFC程式中也找的WinMain入口函式,在視窗類中設定了視窗處理函式,註冊視窗類RegisterClass函式,建立視窗CreateWindowEx函式,顯示視窗函式ShowWindow以及更新視窗函式UpdateWindow,在第一節的問題中只剩下訊息迴圈的啟動了。
我們回到AfxWinMain函式中,在pThread->InitInstance後可以看到呼叫了pThread->Run函式,深入此函式,看見呼叫堆疊如下:
棧頂函式AfxInternalPumpMessage程式碼如下:
BOOL AFXAPI AfxInternalPumpMessage()
{
_AFX_THREAD_STATE *pState = AfxGetThreadState();
if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
{
// Note: prevents calling message loop things in 'ExitInstance'
// will never be decremented
return FALSE;
}
// process this message
if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
{
::TranslateMessage(&(pState->m_msgCur));
::DispatchMessage(&(pState->m_msgCur));
}
return TRUE;
}
可以看到上面的程式碼中就有Win32程式進行訊息提取的呼叫過程,迴圈在CWinThread::Run中進行。
對我們MFC的程式執行過程作一個總結:
從WinMain開始,呼叫AfxWinMain,呼叫過載的MyApp::InitInstance,建立MyFrame物件,呼叫CFrameWnd::Create,呼叫CWnd::CreateEx,呼叫CFrameWnd::PreCreateWindow,呼叫::RegisterClass註冊視窗,呼叫::CreateWindowEx建立視窗,呼叫CWinApp::Run,呼叫CWinThread::Run(迴圈),呼叫::GetMessage獲取訊息進行處理。以上除了全域性函式外,其他函式都可以在MyFrame和MyApp進行重寫從而修改程式的執行。
至此,我們已經在MFC的程式碼中找到了Win32程式的各個必要的組成部分,但是對訊息處理部分還不甚明瞭,接著來詳細分析下MFC的訊息處理過程。