///////////////////////////////////////////////////////////////////////////////////
/********* 文章系列:MFC技術內幕系列***********/
/************MFC技術內幕系列之(一)***********/
/****文章題目:MFC應用程序“生死因果”內幕*****/
/* Copyright(c)2002 bigwhite */
/* All rights Reserved */
/***********關鍵字:MFC,生死因果**************/
/* 時間:2002.7.23 */
/* 註釋:本文所涉及的程序源代碼均在Microsoft */
/ Visual Studio.Net Enterprise Architect Edition /
/* 開發工具包提供的源代碼中 */
////////////////////////////////////////////////////////////////////////////////////////////////
引言:
侯捷老師在他那本著名的"深入淺出MFC"(第二版)的第六章中對比着傳統的Win32API編程,詳細講解了MFC應用程序“生死因果”,而且侯捷老師 還在"深入淺出MFC"(第二版)一書的“無責任書評”中稱應用程序和MFC Framework的因果關係,是學習MFC程序設計的關鍵,並把它作爲學習MFC程序設計的"第一個臺階".
作爲已是“過來人”的我非常贊同侯捷老師的觀點,特寫下此篇文章以供大家參考,本文章特別對MFC程序設計的初學者大有裨益。
正文:
初學MFC程序設計的人(甚至包括已經很精通Win32API編程的大蝦們)都會感到很疑惑,對MFC應用程序的運行流程不能馬上領悟,多數人都會提出類 似"WinMain函數跑到哪裏去了?","窗口函數(WinProc),消息循環好像一下子都消失了?"等問題。下面就讓我們看一個MFC SDI應用程序的運行流程並挖掘一下MFC庫的源代碼,來盡力爭取弄清MFC應用程序“生死因果”的內幕。
////////////////////////////////////////////
/* 1. Windows 幫忙 */
/* 程序誕生! */
//////////////////////////////////////////
Windows 操作系統爲應用程序創建進程核心對象,併爲該應用程序分配4GB的進程地址空間,系統加載器
將應用程序可執行文件映像以及一些必要的代碼(包括數據和一些應用程序使用的dlls)加載到應用程序的進程地址空間中。
/////////////////////////////////////////////
/* 2.啓動函數是什麼? */
/////////////////////////////////////////////
Windows 操作系統在初始化該應用程序進程的同時,將自動爲該應用程序創建一個主線程,該主線程與
C/C++運行時庫的啓動函數一道開始運行。很多初學者並不知道C/C++運行時庫的啓動函數是何方神聖,這裏我
簡單介紹一下:當你的應用程序編譯後開始鏈接時,系統的鏈接器會根據你的應用程序的設置爲你的應用程序
選擇一個C/C++運行時庫的啓動函數(註釋:這些函數聲明在../Visual Studio.NET/vc7/crt/src/crt0.c中)
一般的ANSI版本的GUI的應用程序的C/C++運行時庫的啓動函數爲:
int WinMainCRTStartup(void);
其它版本的C/C++運行時庫的啓動函數如下:
ANSI版本的CUI的應用程序: int mainCRTStartup(void);
Unicode版本的CUI的應用程序: int wmainCRTStartup(void);
Unicode版本的GUI的應用程序: int wWinMainCRTStartup(void);
C/C++運行時庫的啓動函數的主要功能爲初始化C/C++運行時庫和爲所有全局和靜態的C++類對象調用構造函數。
/////////////////////////////////////////////////
/* 3.侯捷老師所說的"引爆器" */
/////////////////////////////////////////////////
前面所說的C/C++運行時庫的啓動函數的主要功能之一是爲所有全局和靜態的C++類對象調用構造函數。侯捷老師所說的"引爆器"--- CMyWinApp theApp這個Application Object就是由啓動函數調用其構造函數構造出來的。CWinApp的構造函數到底作了什麼?看看源代碼吧,源代碼最能說明問題了。
註釋:CWinApp的構造函數定義在../Visual Studio.NET/vc7/atlmfc/src/mfc/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;
ASSERT(AfxGetThread() == this);
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
// initialize CWinApp state
ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
// in non-running state until WinMain
m_hInstance = NULL;
m_hLangResourceDLL = NULL;
m_pszHelpFilePath = NULL;
m_pszProfileName = NULL;
m_pszRegistryKey = NULL;
m_pszExeName = NULL;
m_pRecentFileList = NULL;
m_pDocManager = NULL;
m_atomApp = m_atomSystemTopic = NULL;
m_lpCmdLine = NULL;
m_pCmdInfo = NULL;
// initialize wait cursor state
...//
// initialize current printer state
...//
// initialize DAO state
m_lpfnDaoTerm = NULL; // will be set if AfxDaoInit called
// other initialization
...//
}
從源代碼中可以看出CWinApp的構造函數主要收集了一些關於應用程序主線程的信息及初始化一些相關應用程序的信息。值得注意的是CWinApp類的一 些主要的數據成員如:m_hInstance,m_lpCmdLine,m_pCmdInfo及m_atomApp等都初始化爲NULL,這些成員在後面 將被重新賦值。
//////////////////////////////////////////////
/* 4. WinMain函數登場了 */
//////////////////////////////////////////////
C/C++運行時庫的啓動函數int WinMainCRTStartup(void);所調用的WinMain函數---同時也是主線程的入口函數爲:
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine
,int nCmdShow);
註釋1:該函數定義在../Visual Studio.NET/vc7/atlmfc/src/mfc/appmodul.cpp中
註釋2:_t 是爲了照顧Unicode版本而定義的宏。
講到這個時候你也許會稍稍展開你那緊皺的眉頭,不過也許你還會問:"MFC中的WinMain函數到底作了什麼?" 其實很簡單,看看源代碼就知道了。
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
這一下清楚了,MFC中的WinMain函數其實什麼也沒做,只是調用了一個函數AfxWinMain。
/////////////////////////////////////////////////
/* 5.MFC程序的入口點函數 */
//////////////////////////////////////////////////
MFC作了一個"乾坤大挪移",將WinMain函數的全部責任轉移交給了MFC程序的入口點函數---AfxWinMain。
註釋:該函數定義在../Visual Studio.NET/vc7/atlmfc/src/mfc/winmain.cpp中。
// Standard WinMain implementation
// Can be replaced as long as 'AfxWinInit' is called first
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
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;
}
從上面源代碼可以看出AfxWinMain函數主要由四大模塊組成,他們分別是AfxWinInit,InitApplication,
InitInstance,Run。下面將分別介紹這四大模塊的功能。
///////////////////////////////////////////////
/* 5.1 AFX的內部初始化 */
///////////////////////////////////////////////
AfxWinInit函數是既CWinApp類構造函數後的又一個重量級的函數。不妨看一下它的源代碼:
註釋:該函數定義在../Visual Studio.NET/vc7/atlmfc/src/mfc/appinit.cpp中。
BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
// handle critical errors and avoid Windows message boxes
SetErrorMode(SetErrorMode(0) |
SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
// set resource handles
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
pModuleState->m_hCurrentInstanceHandle = hInstance;
pModuleState->m_hCurrentResourceHandle = hInstance;
// fill in the initial state for the application
CWinApp* pApp = AfxGetApp();
if (pApp != NULL)
{
// Windows specific initialization (not done if no CWinApp)
pApp->m_hInstance = hInstance;
hPrevInstance; // Obsolete.
pApp->m_lpCmdLine = lpCmdLine;
pApp->m_nCmdShow = nCmdShow;
pApp->SetCurrentHandles();
}
// initialize thread specific data (for main thread)
if (!afxContextIsDLL)
AfxInitThread();
// Initialize CWnd::m_pfnNotifyWinEvent
HMODULE hModule = ::GetModuleHandle(_T("user32.dll"));
if (hModule != NULL)
{
CWnd::m_pfnNotifyWinEvent = (CWnd::PFNNOTIFYWINEVENT)::GetProcAddress(hModule, "NotifyWinEvent");
}
return TRUE;
}
還記得我在第三個標題---侯捷老師所說的"引爆器"處 的話麼,"CWinApp類的一些主要的數據成員在後面將被重新賦值。",AfxWinInit函數就是這些數據成員被賦值的地方,它重新初始化這些在整 個程中都扮演重要角色的成員,並且調用AfxInitThread()爲主線程作了一些初始化工作,這些都爲以後MFC框架的正常運作鋪平了道路。
/////////////////////////////////////////////////
/* 5.2 應用程序的全局初始化 */
/////////////////////////////////////////////////
InitApplication函數(virtual)爲程序進行全局初始化:
註釋1:該函數定義在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp中。
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;
LoadSysPolicies();
return TRUE;
}
由於初次調用時 CDocManager::pStaticDocManager==0x00000000;m_pDocManager==0x00000000;所以 InitApplication函數只是調用了CWinApp::LoadSysPolicies();而後者將加載一些註冊表的信息用來初始化一些程序 定義的結構併爲程序註冊一些基本信息。(由於該函數可能尚未文檔化,所以關於LoadSysPolicies函數的說明只是看了源代碼後的推測,下面列出 了它的部分源代碼僅供參考)
註釋2:該函數定義在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp中。
BOOL CWinApp::LoadSysPolicies()
{
HKEY hkPolicy = NULL;
DWORD dwValue = 0;
DWORD dwDataLen = sizeof(dwValue);
DWORD dwType = 0;
// clear current policy settings.
m_dwPolicies = _AFX_SYSPOLICY_NOTINITIALIZED;
static _AfxSysPolicyData rgExplorerData[] =
{
{_T("NoRun"), _AFX_SYSPOLICY_NORUN},
{_T("NoDrives"), _AFX_SYSPOLICY_NODRIVES},
{_T("RestrictRun"), _AFX_SYSPOLICY_RESTRICTRUN},
{_T("NoNetConnectDisconnect"), _AFX_SYSPOLICY_NONETCONNECTDISCONNECTD},
{_T("NoRecentDocsHistory"), _AFX_SYSPOLICY_NORECENTDOCHISTORY},
{_T("NoClose"), _AFX_SYSPOLICY_NOCLOSE},
{NULL, NULL}
};
...//
static _AfxSysPolicyData rgComDlgData[] =
{
{_T("NoPlacesBar"), _AFX_SYSPOLICY_NOPLACESBAR},
{_T("NoBackButton"), _AFX_SYSPOLICY_NOBACKBUTTON},
{_T("NoFileMru"), _AFX_SYSPOLICY_NOFILEMRU},
{NULL, NULL}
};
static _AfxSysPolicies rgPolicies[] =
{
{_T("Software//Microsoft//Windows//CurrentVersion//Policies//Explorer"),
rgExplorerData},
{_T("Software//Microsoft//Windows//CurrentVersion//Policies//Network"),
rgNetworkData},
{_T("Software//Microsoft//Windows//CurrentVersion//Policies//Comdlg32"),
rgComDlgData},
{NULL, NULL}
};
_AfxSysPolicies *pPolicies = rgPolicies;
_AfxSysPolicyData *pData = NULL;
...//
}
註釋3:在MFC文檔中有這麼一句話"The CWinApp::InitApplication member function is obsolete in MFC.",所以你大多情況下不用在意這個virtual函數。
/////////////////////////////////////////////////
/* 5.3 應用程序的標準實例化 */
//////////////////////////////////////////////////
CWinApp::InitInstance()是一個虛函數,大多數應用程序都要override這個函數。讓我們看看應用程序嚮導MFC AppWizard(.exe)爲SDI 程序作出的override後的代碼吧!
BOOL CMyWinApp::InitInstance()
{
// 如果一個運行在 Windows XP 上的應用程序清單指定要
// 使用 ComCtl32.dll 版本 6 或更高版本來啓用可視化方式,
//則需要 InitCommonControls()。否則,將無法創建窗口。
InitCommonControls();
CWinApp::InitInstance();//顯式調用基類的InitInstance()
// 初始化 OLE 庫
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
// 標準初始化
SetRegistryKey(_T("應用程序嚮導生成的本地應用程序"));
LoadStdProfileSettings(4); // 加載標準 INI 文件選項(包括 MRU)
// 註冊應用程序的文檔模板。
// 文檔模板將用作文檔、框架窗口和視圖之間的連接
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口
RUNTIME_CLASS(CTestView));
pDocTemplate->SetContainerInfo(IDR_CNTR_INPLACE);
AddDocTemplate(pDocTemplate);
// 分析標準外殼命令、DDE、打開文件操作的命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 唯一的一個窗口已初始化,因此顯示它並對其進行更新
m_pMainWnd->ShowWindow(SW_HIDE);
m_pMainWnd->UpdateWindow();
return TRUE;
}
CMyWinApp::InitInstance()先顯式調用了基類的InitInstance();
我們先看看這個基類的函數的定義吧!
註釋1:該函數定義在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp中。
BOOL CWinApp::InitInstance()
{
InitLibId();
m_hLangResourceDLL = LoadAppLangResourceDLL();
if(m_hLangResourceDLL != NULL)
{
AfxSetResourceHandle(m_hLangResourceDLL);
_AtlBaseModule.SetResourceInstance(m_hLangResourceDLL);
}
return TRUE;
}
註釋2:vc.net中的CWinApp::InitInstance()已與vc6.0中的CWinApp::InitInstance()有所區別。
基類的InitInstance()
先 調用InitLibId()函數用於Initializes the data member containing the GUID of the current module;不過該函數現在爲空,估計以後微軟會填充該函數。
之後調用LoadAppLangResourceDLL()函數加載應用程序所需資源;在vc6.0中的CWinApp::InitInstance()函數只有一條語句:即return TRUE;
CMyWinApp::InitInstance()在其基類的幫助後,開始執行它自己的一系列代碼來完成諸如"初始化 OLE 庫","設置註冊表主鍵以使程序能保存信息到註冊表中","分析標準外殼命令","生成程序主框架,文檔和視圖結構","顯示程序主窗口"等工作。
註釋3:有關應用程序是如何在CMyWinApp::InitInstance()完成上面一系列工作的,將在本系列文章之二 的“MFC文檔視圖結構內幕”一文中詳述。
註釋4:在MSDN中有關InitInstance的敘述如下:"Windows allows several copies of the same program to run at the same time."
////////////////////////////////////////////
/* 5.4 "消息泵"啓動了 */
////////////////////////////////////////////
衆所周知,Windows是一個以消息爲基礎,以事件驅動的操作系統,每一個Win32程序也都是如此。那麼
MFC應用程序是如何實現消息機制的呢?MFC應用程序框架將這種消息機制包裝到了一個"消息泵"中,而這個"消息泵"在CMyWinApp::InitInstance()中被啓動了。其源代碼如下:
註釋1:該函數定義在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp中。
// Main running routine until application exits
int CWinApp::Run()
{
if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
{
// Not launched /Embedding or /Automation, but has no main window!
TRACE(traceAppMsg, 0, "Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application./n");
AfxPostQuitMessage(0);
}
return CWinThread::Run();
}
由上面的源代碼看出:CWinApp::Run()調用了其基類的Run()函數,繼續看源代碼:
int CWinThread::Run()
{
ASSERT_VALID(this);
_AFX_THREAD_STATE* pState = AfxGetThreadState();
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
// acquire and dispatch messages until a WM_QUIT message is received.
for (;;)
{
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))
{
// call OnIdle while in bIdle state
if (!OnIdle(lIdleCount++))
bIdle = FALSE; // assume "no idle" state
}
// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after pumping "normal" message
//if (IsIdleMessage(&m_msgCur))
if (IsIdleMessage(&(pState->m_msgCur)))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));
}
}
//CWinThread implementation helpers
BOOL CWinThread::PumpMessage()
{
return AfxInternalPumpMessage();
}
BOOL AFXAPI AfxInternalPumpMessage()//部分源碼
{
_AFX_THREAD_STATE *pState = AfxGetThreadState();
if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
{
...//
return FALSE;
}