我们提取CWinApp类原型的一部分:
class CWinApp : public CWinThread { DECLARE_DYNAMIC(CWinApp) public: // Constructor CWinApp(LPCTSTR lpszAppName = NULL);// default app name // Attributes // Startup args (do not change) HINSTANCE m_hInstance; HINSTANCE m_hPrevInstance; LPTSTR m_lpCmdLine; int m_nCmdShow; // Running args (can be changed in InitInstance) LPCTSTR m_pszAppName; // human readable name LPCTSTR m_pszExeName; // executable name (no spaces) LPCTSTR m_pszHelpFilePath; // default based on module path LPCTSTR m_pszProfileName; // default based on app name // Overridables virtual BOOL InitApplication(); virtual BOOL InitInstance(); virtual int ExitInstance(); // return app exit code virtual int Run(); virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing virtual LRESULT ProcessWndProcException(CException* e,const MSG* pMsg); public: virtual ~CWinApp(); protected: DECLARE_MESSAGE_MAP() }; |
SDK程序的WinMain 所完成的工作现在由CWinApp 的三个函数完成:
virtual BOOL InitApplication(); virtual BOOL InitInstance(); virtual int Run(); |
"CMFCThreadApp theApp;"语句定义的全局变量theApp是整个程式的application object,每一个MFC 应用程序都有一个。当我们执行MFCThread程序的时候,这个全局变量被构造。theApp 配置完成后,WinMain开始执行。但是程序中并没有WinMain的代码,它在哪里呢?原来MFC早已准备好并由Linker直接加到应用程序代码中的,其原型为(存在于VC++6.0安装目录下提供的APPMODUL.CPP文件中):
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // call shared/exported WinMain return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); } |
其中调用的AfxWinMain如下(存在于VC++6.0安装目录下提供的WINMAIN.CPP文件中):
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) { TRACE0("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) { TRACE1("Warning: Temp map lock count non-zero (%ld)./n", AfxGetModuleThreadState()->m_nTempMapLock); } AfxLockTempMaps(); AfxUnlockTempMaps(-1); #endif AfxWinTerm(); return nReturnCode; } |
我们提取主干,实际上,这个函数做的事情主要是:
CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp(); AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow) pApp->InitApplication() pThread->InitInstance() pThread->Run(); |
其中,InitApplication 是注册窗口类别的场所;InitInstance是产生窗口并显示窗口的场所;Run是提取并分派消息的场所。这样,MFC就同WIN32 SDK程序对应起来了。CWinThread::Run是程序生命的"活水源头"(侯捷:《深入浅出MFC》,函数存在于VC++ 6.0安装目录下提供的THRDCORE.CPP文件中):
// main running routine until thread exits int CWinThread::Run() { ASSERT_VALID(this); // 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(&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)) { bIdle = TRUE; lIdleCount = 0; } } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } ASSERT(FALSE); // not reachable } |
其中的PumpMessage函数又对应于:
/ // CWinThread implementation helpers BOOL CWinThread::PumpMessage() { ASSERT_VALID(this); 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; } |
因此,忽略IDLE状态,整个RUN的执行提取主干就是:
do { ::GetMessage(&msg,...); PreTranslateMessage{&msg); ::TranslateMessage(&msg); ::DispatchMessage(&msg); ... } while (::PeekMessage(...)); |
由此,我们建立了MFC消息获取和派生机制与WIN32 SDK程序之间的对应关系。下面继续分析MFC消息的"绕行"过程。
在MFC中,只要是CWnd 衍生类别,就可以拦下任何Windows消息。与窗口无关的MFC类别(例如CDocument 和CWinApp)如果也想处理消息,必须衍生自CCmdTarget,并且只可能收到WM_COMMAND消息。所有能进行MESSAGE_MAP的类都继承自CCmdTarget,如:
![]() ![]() ![]() |
MFC中MESSAGE_MAP的定义依赖于以下三个宏:
DECLARE_MESSAGE_MAP() BEGIN_MESSAGE_MAP( theClass, //Specifies the name of the class whose message map this is baseClass //Specifies the name of the base class of theClass ) END_MESSAGE_MAP() |
我们程序中涉及到的有:MFCThread.h、MainFrm.h、ChildView.h文件
DECLARE_MESSAGE_MAP() MFCThread.cpp文件 BEGIN_MESSAGE_MAP(CMFCThreadApp, CWinApp) //{{AFX_MSG_MAP(CMFCThreadApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP END_MESSAGE_MAP() MainFrm.cpp文件 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code ! ON_WM_SETFOCUS() //}}AFX_MSG_MAP END_MESSAGE_MAP() ChildView.cpp文件 BEGIN_MESSAGE_MAP(CChildView,CWnd ) //{{AFX_MSG_MAP(CChildView) ON_WM_PAINT() //}}AFX_MSG_MAP END_MESSAGE_MAP() |
由这些宏,MFC建立了一个消息映射表(消息流动网),按照消息流动网匹配对应的消息处理函数,完成整个消息的"绕行"。
看到这里相信你有这样的疑问:程序定义了CWinApp类的theApp全局变量,可是从来没有调用AfxBeginThread或theApp.CreateThread启动线程呀,theApp对应的线程是怎么启动的?
答:MFC在这里用了很高明的一招。实际上,程序开始运行,第一个线程是由操作系统(OS)启动的,在CWinApp的构造函数里,MFC将theApp"对应"向了这个线程,具体的实现是这样的:
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_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 m_nWaitCursorCount = 0; m_hcurWaitCursorRestore = NULL; // initialize current printer state m_hDevMode = NULL; m_hDevNames = NULL; m_nNumPreviewPages = 0; // not specified (defaults to 1) // initialize DAO state m_lpfnDaoTerm = NULL; // will be set if AfxDaoInit called // other initialization m_bHelpMode = FALSE; m_nSafetyPoolSize = 512; // default size } |
很显然,theApp成员变量都被赋予OS启动的这个当前线程相关的值,如代码:
m_hThread = ::GetCurrentThread();//theApp的线程句柄等于当前线程句柄 m_nThreadID = ::GetCurrentThreadId();//theApp的线程ID等于当前线程ID |
所以CWinApp类几乎只是为MFC程序的第一个线程量身定制的,它不需要也不能被AfxBeginThread或theApp.CreateThread"再次"启动。这就是CWinApp类和theApp全局变量的内涵!如果你要再增加一个UI线程,不要继承类CWinApp,而应继承类CWinThread。而参考第1节,由于我们一般以主线程(在MFC程序里实际上就是OS启动的第一个线程)处理所有窗口的消息,所以我们几乎没有再启动UI线程的需求!