上回书说到,WinMain和窗口处理函数分别被相应的类操作和宏操作所取代。这次我们就来看一下,取代之后的MFC程序是如何运行的吧。
先把类继承图贴出来,随时可以回顾一下。
主cpp文件中的第一个操作语句:
// The one and only CMyWinApp object CMyWinApp theApp;
1.生成一个对象自然是要调用构造函数的,但是不仅要调用自身的构造函数,还得调用父类的构造函数。虽说一个自动生成的类,其构造函数是空的,但是父类的构造函数还是可以完成一些初始化操作的。
打开MFC框架中的APPCORE.CPP文件,可以看到CWinApp的构造函数定义。其关部分操作如下:
CWinApp::CWinApp(LPCTSTR lpszAppName) { if (lpszAppName != NULL) m_pszAppName = _tcsdup(lpszAppName); else m_pszAppName = NULL; // initialize CWinThread state 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_pszExeName = NULL; m_lpCmdLine = NULL; m_pCmdInfo = NULL; …… }
由上可见,CWinApp中的成员变量因为theApp全局对象的出现而获得配置与初值。如果程序中没有theApp存在,编译链接还是可以顺利通过,但执行时会出现系统错误消息。
2.theApp配置完成后,接下来就该WinMain出场了。但是我们并未编写相应的代码,而是由MFC准备好并由链接器直接加到应用程序代码中的,其代码可在APPMODUL.CPP中查看:
extern "C" int WINAPI //_t是为了支持Unicode准备的一个宏 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // call shared/exported WinMain return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); }
3.由上可见,函数体中调用了AfxWinMain函数(在WINMAIN.CPP中),该函数的主要部分如下:
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { 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())//执行MFC内部管理 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: AfxWinTerm(); return nReturnCode; }
4.在上述函数体中又调用了AfxWinInit,该函数定义于APPINIT.CPP中,主要部分如下:
BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // 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; pApp->m_hPrevInstance = hPrevInstance; pApp->m_lpCmdLine = lpCmdLine; pApp->m_nCmdShow = nCmdShow; pApp->SetCurrentHandles(); } // initialize thread specific data (for main thread) if (!afxContextIsDLL) AfxInitThread();//调用==== > return TRUE; }
5.最后又调用了AfxInitThread函数。该函数定义于THRDCORE.CPP中,内容如下:
void AFXAPI AfxInitThread() { if (!afxContextIsDLL) { // set message filter proc _AFX_THREAD_STATE* pThreadState = AfxGetThreadState(); ASSERT(pThreadState->m_hHookOldMsgFilter == NULL); pThreadState->m_hHookOldMsgFilter = ::SetWindowsHookEx(WH_MSGFILTER, _AfxMsgFilterHook, NULL, ::GetCurrentThreadId()); #ifndef _AFX_NO_CTL3D_SUPPORT // intialize CTL3D for this thread _AFX_CTL3D_STATE* pCtl3dState = _afxCtl3dState; if (pCtl3dState->m_pfnAutoSubclass != NULL) (*pCtl3dState->m_pfnAutoSubclass)(AfxGetInstanceHandle()); // allocate thread local _AFX_CTL3D_THREAD just for automatic termination _AFX_CTL3D_THREAD* pTemp = _afxCtl3dThread; pTemp; // avoid unused warning #endif } }
函数中主要初始化了线程的相关状态,顺便还设置了个“钩子”。钩子函数的声明如下:
HHOOK WINAPI SetWindowsHookEx( __in int idHook, //钩子的类型,即它处理的消息类型 __in HOOKPROC lpfn, //回调函数地址 __in HINSTANCE hMod, //应用程序实例的句柄。如果dwThreadId 标识当前进程创建的一个线程,而且子程代码位于当前进程,hMod必须为NULL。 __in DWORD dwThreadId); //与安装的钩子子程相关联的线程ID
函数体中设置的WH_MSGFILTER Hook使我们能够监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。
6.好了,到此为止AfxWinInit函数的调用就彻底结束了,接下来调用的是InitApplication函数。
pApp->InitApplication();
其中InitApplication是虚函数,而本类中并未改写,所以它将调用父类的对应InitApplication函数,即相当于:CWinApp:: InitApplication();
该函数定义于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; return TRUE; }
其中的操作都是MFC内部的相关管理操作。
7.下一步操作是:pThread->InitInstance();
AfxGetThread()返回的是当前界面线程对象的指针,AfxGetApp()返回的是应用程序对象的指针,如果该应用程序(或进程)只有一个界面线程在运行,那么这两者返回的都是一个全局的应用程序对象指针,这个全局的应用程序对象就是MFC应用框架所默认的theApp对象。
当我们在调用AfxGetThread时,自然还只有一个界面线程在运行。也就是说,上述的语句调用也相当于:pApp->InitInstance();好了,这个时候终于第一次可以实实在在的在我们自己的项目中看到要分析的代码了。在继承CWndApp的那个类的cpp文件中会自动生成InitInstance函数的部分代码,主要内容如下:
BOOL CMyWinApp::InitInstance() { CMyFrameWnd* pMyFrameWnd = new CMyFrameWnd;//注册相关的窗口类 pMyFrameWnd ->ShowWindow(m_nCmdShow); pMyFrameWnd ->UpdateWindow(); return TRUE; }
8.很明显,接下来就该调用CMyFrameWnd的默认构造函数了。在本类中,自动生成的代码什么都没做,但是其父类CFrameWnd的相关操作也会被调用。在CFrameWnd构造函数中的关键步骤就是调用了CFrameWnd::Create函数。该函数的主要操作如下:
BOOL CFrameWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, LPCTSTR lpszMenuName, DWORD dwExStyle, CCreateContext* pContext) /* 第一个参数:指定WNDCLASS窗口类,放置NULL是要以MFC內建的窗口类产生一个标准的外框窗口。 第二个参数:指定窗口标题 第三给参数:指定窗口风格,默认是WS_OVERLAPPEDWINDOW 第四个参数:指定窗口的位置与大小 第五个参数:指定父窗口。对于一个top-level窗口而言,此值应为NULL,表示没有(其实父窗口是desktop)。 第六个参数:指定菜单 第七个参数:指定窗口的扩充风格 第八个参数:指向一个CCreateContext结构的指针,Framework利用它,在具备Document/View结构的程序中初始化外框窗口。 */ { HMENU hMenu = NULL; if (lpszMenuName != NULL) { //搜索包含该菜单资源的实例(当前进程或者按进行装入的DLL) HINSTANCE hInst = AfxFindResourceHandl(lpszMenuName, RT_MENU); //装入菜单资源 if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL) { TRACE0("Warning: failed to load menu for CFrameWnd.\n"); PostNcDestroy(); //perhaps delete to C++ object return FALSE; } } m_strTitle = lpszWindowName; //存储窗口标题,以备后用(如刷新显示) // 调用CWnd::CreateEx() if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right – rect.left, rect,bottom – rect.top, pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext)) { if (hMenu != NULL) DestroyMenu(hMenu); //如果创建失败,释放菜单资源 return FALSE; } return TRUE; }
9.在上函数中,最重要的操作就是又调用了CreateEx函数。但是从CFrameWnd开始就没有重新定义该函数,所以调用的还是CWnd中定义的CreateEx函数。该函数定义于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); …… }
10.在以上函数中又先后调用了PreCreateWindow和::CreateWindowEx两个函数。其中PreCreateWindow的主要操作定义于WINFRM.CPP,主要内容如下:
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs) { if (cs.lpszClass == NULL) { VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)); cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background } …… }
11.其中又调用了AfxDeferRegisterClass函数(其实这是一个宏)。它定义于AFXIMPL.H中,如下:
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
12.其中又有AfxEndDeferRegisterClass函数的调用,该函数定义于WINCORE.CPP中,主要操作如下:
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; INITCOMMONCONTROLSEX init; init.dwSize = sizeof(init); // work to register classes as specified by fToRegister, populate fRegisteredClasses as we go 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; } …… }
以上函数在创建窗口之前,根据不同功能的窗口会注册一些窗口类。其中主要用到了两个函数_AfxRegisterWithIcon和AfxRegisterClass。
13.在函数_AfxRegisterWithIcon内部其实还是调用了AfxRegisterClass函数。该函数的定义如下:
AFX_STATIC BOOL AFXAPI _AfxRegisterWithIcon(WNDCLASS* pWndCls, LPCTSTR lpszClassName, UINT nIDIcon) { pWndCls->lpszClassName = lpszClassName; HINSTANCE hInst = AfxFindResourceHandle( MAKEINTRESOURCE(nIDIcon), RT_GROUP_ICON); if ((pWndCls->hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDIcon))) == NULL) { // use default icon指定icon pWndCls->hIcon = ::LoadIcon(NULL, IDI_APPLICATION); } return AfxRegisterClass(pWndCls); }
14.AfxRegisterClass中对本文有意义的代码如下:
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass) { WNDCLASS wndcls; if (GetClassInfo(lpWndClass->hInstance, lpWndClass-> lpszClassName, &wndcls)) { // class already registered return TRUE; } if (!::RegisterClass(lpWndClass)) { TRACE1("Can't register window class named %s\n", lpWndClass->lpszClassName); return FALSE; } …… return TRUE; }
函数中通过调用RegisterClass注册窗口类,以便在后面供CreateWindowEx函数使用,来创建相应的窗口对象。至此Create函数执行完了,CMyFrameWnd对象也构造完成。
15.后面的执行语句是:
pMyFrameWnd ->ShowWindow(m_nCmdShow); pMyFrameWnd ->UpdateWindow();
调用ShowWindow函数来显示窗口,并调用UpdateWindow函数给程序送去了WM_PAINT消息,驱动窗口的绘制。到此InitInstance函数终于执行完毕,剩下最后一步操作:pThread->Run();
16.和上面的pThread->InitInstance();调用一样,这里调用的也是pApp-> Run();由于本类中没有重写Run虚函数,所以还是调用父类CWinApp中的Run函数。该函数定义于APPCORE.CPP中,内容如下:
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(); }
17.正常情况下,上面的函数会调用CWinThread::Run函数。该函数定义于THRDCORE.CPP,主要内容如下:
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 }
终于在其中看见了我们朝思暮想的PeekMessage的熟悉身影。小乔出现了,那么大乔呢?别着急,看看PumpMessage就行了,人家只是有点害羞而已。
18.PumpMessage的定义在同一个文件中,主要内容如下:
BOOL CWinThread::PumpMessage() { ASSERT_VALID(this); if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) { return FALSE; } …… if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)) { ::TranslateMessage(&m_msgCur); ::DispatchMessage(&m_msgCur); } return TRUE; }
是的除了GetMessage,里面还有TranslateMessage和DispatchMessage姐妹花,该出现的都出现了。现在消息循环也已经开启了,程序成功跑起来了,收工。
不得不说,写到这里感觉身体被掏空了,我要去喝杯水,思考一下人生了。