4.探索AfxWinMain
在WinMain函数的定义中可以发现仅有一行:
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
所以WinMain的具体实现是交给AfxWinMain完成的。
打开WinMain.cpp查看定义:
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow) { ASSERT(hPrevInstance == NULL); int nReturnCode = -1; CWinThread* pThread = AfxGetThread(); //CWinApp派生于CWinThread CWinApp* pApp = AfxGetApp(); //CTestApp派生于CWinApp // 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) { TRACE(traceAppMsg, 0,"Warning: Destroying non-NULL m_pMainWnd\n"); pThread->m_pMainWnd->DestroyWindow(); } nReturnCode =pThread->ExitInstance(); goto InitFailure; } nReturnCode = pThread->Run(); //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; }
关键行1:CWinThread* pThread =AfxGetThread();
我们转到AfxGetThread函数的定义,位于THREDCORE.CPP文件中。
CWinThread* AFXAPI AfxGetThread() { // check for current thread in modulethread state AFX_MODULE_THREAD_STATE* pState =AfxGetModuleThreadState(); CWinThread* pThread =pState->m_pCurrentWinThread; return pThread; }
-------------------------------------------------------------------------------------------------------------------------------------------------------------
发现与书中相比少了如下几行:
//if no CWinThreadfor the module, then use the global app. if(pThread == NULL) pThread = AfxGetApp();
根据MSDN2013对于AfxGetThread的批注:
If you are porting an MFC project calling AfxGetThread fromVisual C++ versions 4.2, 5.0, or 6.0,AfxGetThread calls AfxGetApp if no thread is found. In VisualC+ .NET and later,AfxGetThread returnsNULL if nothread was found. If you want the application thread, you must call AfxGetApp.
翻译:
如果你正在移植一个MFC项目,并且是从旧版本的VC++(如VC++6.0)调用AfxGetThread函数,那么AfxGetThread调用AfxGetApp如果没有找到线程。(如上面的代码:pThread=AfxGetApp(); )在之后的VC版本中,如果没有找到线程,那么这个函数返回值是NULL。如果你想要获得应用程序线程,必须调用AfxGetApp。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
关键行2:CWinAPP* pApp =AfxGetApp ();
查看AfxGetApp定义:位于AFXWIN1.INL
_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp() { return afxCurrentWinApp; }
查看afxCurrentWinApp的定义:位于AFXWIN.H
#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp
根据前面CWinApp构造函数的定义,m_pCurrentWinApp存放的就是theApp对象。
也就是说,对于Test程序,pApp指向的是theApp这个全局对象。
但是调用了AfxGetApp函数也没有明白pThread指针指向哪里,书中是在pThread为NULL时让pThread = AfxGetApp();,而现在没有这行赋值语句?
这里我的猜想是pThread会在调用其他函数的时候来得到应用程序线程(Test应用程序只有一个线程,多线程的问题以后再研究),因为pThread->InitInstance(),pThread->ExitInstance(),pThread->Run()这些函数调用表明pThread的确通过某种方法获取了theApp对象。
这里先简单地根据以下文字:
AfxGetThread()返回的是当前界面线程对象的指针。
AfxGetApp()返回的是应用程序对象的指针,如果该应用程序(或进程)只有一个界面线程在运行,那么这两者返回的都是一个全局的应用程序对象指针。
知道pThread和pApp所指向的实际上都是CTestApp类的对象:theApp全局对象,即可。
5.完成窗口相关初始化工作的InitInstance函数
在探索AfxWinMain函数的时候,我们很难不注意到pThread和pApp分别调用了InitApplication()、InitInstance()、Run()三个函数。
正是这三个函数完全了Win32程序所需的几个步骤:设计窗口类、注册窗口类、创建窗口、显示窗口、更新窗口、消息循环、以及窗口过程函数。
首先由pApp调用InitApplication函数,完成的是MFC内部管理方面的工作。
接着由pThread调用InitInstance函数,完成了窗口类注册、窗口产生、显示和更新等工作,在CWinApp类和CTestApp类中我们都能发现InitInstance函数,查看声明:
virtual BOOL InitInstance();
原来它是虚函数,根据类的多态性原理,theApp调用的是CTestApp类的InitInstance函数,查看定义:
BOOL CTestApp::InitInstance() { ......... // 若要创建主窗口,此代码将创建新的框架窗口 // 对象,然后将其设置为应用程序的主窗口对象 CMainFrame* pFrame = new CMainFrame; if (!pFrame) return FALSE; m_pMainWnd = pFrame; // 创建并加载框架及其资源 pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW |FWS_ADDTOTITLE, NULL, NULL); // 唯一的一个窗口已初始化,因此显示它并对其进行更新 pFrame->ShowWindow(SW_SHOW); pFrame->UpdateWindow(); return TRUE; }
显示、更新窗口容易找到,但是设计窗口类、注册窗口类和创建窗口呢?
这里不管是原书还是视频都没有讲清楚,调用过程,只说了结果。
给出我自己的猜想,注意到:
CMainFrame* pFrame = new CMainFrame;
定义一个CMainFrame类的对象,根据前面定义theApp全局对象时的套路, 这里肯定会调用基类,一层层往上,CFrameWnd,CWnd类的构造函数都会被调用!这里就很可能完成这些操作。
这里有时间我会跟踪来尝试证明,可以为我解惑的欢迎留言~
m_pMainWnd = pFrame;
而这一行呢?这是因为pFrame这个指针变量是定义在InitInstance这个函数中的,它的生命周期在函数执行完后就结束了,即析构,保存下来的方法自然是把这个窗口对象传给m_pMainWnd这个成员变量了(生命周期和对象一致,也就是程序结束时)~为什么要保存呢?不保存的话,你可以DIY一个MFC程序的时候试试。。例程在这篇笔记的隔壁。。实验结果就写那了~
CWnd类的CreateEx函数,实现创建窗口的过程。
Run函数,完成消息循环工作:先是一个for循环,再套了while循环,其中PumpMessage中调了GetMessage、TranslateMessage和DispatchMessage这个三个函数。
6.对注册窗口类的跟踪(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 NULLdefaults wndcls.lpfnWndProc = DefWindowProc; wndcls.hInstance =AfxGetInstanceHandle(); wndcls.hCursor = afxData.hcurArrow; ...... // work to register classes as specifiedby fToRegister, populate fRegisteredClasses as we go if (fToRegister & AFX_WND_REG) { // Child windows - no brush, noicon, 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 - useparent DC for speed wndcls.style |= CS_PARENTDC |CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; wndcls.lpszClassName =_afxWndOleControl; if(AfxRegisterClass(&wndcls)) fRegisteredClasses |=AFX_WNDOLECONTROL_REG; } ......//这里省略了15个左右的窗口类设计样式 if (fToRegister &AFX_WNDCOMMCTL_PAGER_REG) { init.dwICC =ICC_PAGESCROLLER_CLASS; fRegisteredClasses |=_AfxInitCommonControls(&init, AFX_WNDCOMMCTL_PAGER_REG); } ...... // save new state of registered controls pModuleState->m_fRegisteredClasses |=fRegisteredClasses; // special case for all common controlsregistered, 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 mamyclasses as requested return (fToRegister &fRegisteredClasses) == fToRegister; }
AfxRegisterClass:
BOOL AFXAPI AfxRegisterClass(WNDCLASS*lpWndClass) { WNDCLASS wndcls; if(GetClassInfo(lpWndClass->hInstance, lpWndClass->lpszClassName, &wndcls)) { // class already registered return TRUE; } if (!RegisterClass(lpWndClass)) //与Windows SDK编程的API一样,用到了RegisterClass { TRACE(traceAppMsg, 0, _T("Can'tregister window class named %s\n"), lpWndClass->lpszClassName); return FALSE; } BOOL bRet = TRUE; if (afxContextIsDLL) { AfxLockGlobals(CRIT_REGCLASSLIST); TRY { // class registeredsuccessfully, add to registered list AFX_MODULE_STATE*pModuleState = AfxGetModuleState(); pModuleState->m_strUnregisterList+=lpWndClass->lpszClassName; pModuleState->m_strUnregisterList+='\n'; } CATCH_ALL(e) { AfxUnlockGlobals(CRIT_REGCLASSLIST); THROW_LAST(); // Note: DELETE_EXCEPTIONnot required. } END_CATCH_ALL AfxUnlockGlobals(CRIT_REGCLASSLIST); } return bRet; }发现了RegisterClass这一Windows API函数~
书中还说到我们创建的MFC应用程序实际上有两个窗口,一个是CMainFrame类的对象所代表的应用程序框架窗口。
这里我们返回去看一下CTestApp类的InitInstance函数中的一段代码:
//若要创建主窗口,此代码将创建新的框架窗口 //对象,然后将其设置为应用程序的主窗口对象 CMainFrame* pFrame = new CMainFrame; if (!pFrame) return FALSE; m_pMainWnd = pFrame;
注释语句正好可以证实书上所说,在创建主窗口之前,会创建一个新的框架窗口对象,然后设置为主窗口对象。
具体为什么可以这样做?我觉得这和MFC中用对象来标识窗口,窗口类对象(对象的某个成员变量可以存放窗口句柄)和窗口的关系有关。
窗口类对象销毁时,窗口一定会同时销毁;而窗口被销毁时,窗口类对象却不一定析构(一般就是WinMain执行完毕)。
所以我觉得两个窗口的意思实际上是说建立了两个窗口类对象(一定会执行一系列构造函数创建窗口),m_pMainWnd主窗口对象之前一定存放了另一个窗口对象,现在为其重新赋pFrame存放的新框架窗口对象。
这里有时间我会跟踪来尝试证明,可以为我解惑的欢迎留言~
7.对创建窗口的跟踪(CreateEx函数实现)
打开AFXWin.h文件:
CreateEx的声明:
// advanced creation (allows access toextended styles) virtual BOOL CreateEx(DWORD dwExStyle,LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORDdwStyle, int x, int y, int nWidth, intnHeight, HWND hWndParent, HMENUnIDorHMenu, LPVOID lpParam = NULL); virtual BOOL CreateEx(DWORD dwExStyle,LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORDdwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, LPVOID lpParam = NULL);
定于位于WINCORE.CPP:
// CWnd creation BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORDdwStyle, const RECT& rect, CWnd*pParentWnd, UINT nID, LPVOID lpParam /* = NULL */) { return CreateEx(dwExStyle,lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right -rect.left, rect.bottom - rect.top, pParentWnd->GetSafeHwnd(),(HMENU)(UINT_PTR)nID, lpParam); } 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) { ASSERT(lpszClassName == NULL ||AfxIsValidString(lpszClassName) || AfxIsValidAtom(lpszClassName)); ENSURE_ARG(lpszWindowName == NULL ||AfxIsValidString(lpszWindowName)); // allow modification of several commoncreate parameters CREATESTRUCT cs; ...... cs.hInstan<span style="font-size:12px;">ce = AfxGetInstanceHandle(); cs.lpCreateParams = lpParam; if (!PreCreateWindow(cs)) { PostNcDestroy(); return FALSE; } AfxHookWindowCreate(this); HWND hWnd = CreateWindowEx(cs.dwExStyle,cs.lpszClass, cs.lpszName,</span> cs.style,cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu,cs.hInstance, cs.lpCreateParams); ...... }
两种重载形式使得CreatEx函数不仅允许了窗口x,y坐标以及长宽作参数,还允许包含这些参数的rect结构体作参数。
CWnd类的CreateEx函数虽是虚函数,但由于CFrameWnd类中没有重写这个函数,所以CFrameWnd类就继承了CWnd类的CreateEx函数。
所以在MFC底层代码中,被CFrameWnd类的Create函数所调用的CreateEx函数实际上是CWnd类定义的。
声明(AFXWIN.H):
virtual BOOL Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle = WS_OVERLAPPEDWINDOW, const RECT& rect = rectDefault, CWnd* pParentWnd = NULL, // != NULL for popups LPCTSTR lpszMenuName = NULL, DWORD dwExStyle = 0, CCreateContext* pContext = NULL);
定义(WINFRM.CPP):
BOOL CFrameWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, LPCTSTR lpszMenuName, DWORD dwExStyle, CCreateContext* pContext) { ...... if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext)) { TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.\n"); if (hMenu != NULL) DestroyMenu(hMenu); return FALSE; } ...... }
了解了Create函数的具体实现是转交给CreateEx函数来实现的以后,再转回来观察CreateEx函数定义:
发现其调用了PreCreateWindow函数,实现的是在窗口产生之前有机会来修改窗口属性:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: 在此处通过修改 // CREATESTRUCT cs 来修改窗口类或样式 cs.dwExStyle &= ~WS_EX_CLIENTEDGE; cs.lpszClass = AfxRegisterWndClass(0); return TRUE; }
转到CFrameWnd的PreCreateWindow函数(查看声明,这个函数的确是虚函数):
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs) { if (cs.lpszClass == NULL) { VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)); cs.lpszClass =_afxWndFrameOrView; // COLOR_WINDOWbackground } if (cs.style & FWS_ADDTOTITLE) cs.style |= FWS_PREFIXTITLE; cs.dwExStyle |= WS_EX_CLIENTEDGE; return TRUE; }
-------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATESTRUCT经过跟踪发现其包含的字段和CreateWindowEx函数的字段是一致的。
typedef struct tagCREATESTRUCTW { LPVOID lpCreateParams; HINSTANCE hInstance; HMENU hMenu; HWND hwndParent; int cy; int cx; int y; int x; LONG style; LPCWSTR lpszName; LPCWSTR lpszClass; DWORD dwExStyle; } CREATESTRUCTW,*LPCREATESTRUCTW;
#ifdef UNICODE typedef CREATESTRUCTW CREATESTRUCT; ......
将其引用作参数传给PreCreateWindow修改以后,那么在接下来调用CreateWindowEx创建窗口时,其参数就会发生相应的改变,创建出符合我们需要的窗口。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
位于THRDCORE.CPP文件中:
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)); } }
此循环中用到了PumpMessage函数(同样在THRDCORE.CPP中),定义如下:
BOOL CWinThread::PumpMessage() { return AfxInternalPumpMessage(); }AfxInternalPumpMessage:
BOOL AFXAPI AfxInternalPumpMessage() { _AFX_THREAD_STATE *pState = AfxGetThreadState(); if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL)) { #ifdef _DEBUG TRACE(traceAppMsg, 1, "CWinThread::PumpMessage - Received WM_QUIT.\n"); pState->m_nDisablePumpCount++; // application must die #endif // 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 SDK的GetMessage,TranslateMessage,DispatchMessage三个关于消息循环的函数~
至于窗口过程函数,其实在前面的DeferRegisterClass函数定义中有这样一行:
wndcls.lpfnWndProc = DefWindowProc;
这行代码的作用就是设置了窗口过程函数,不过这里指定的是默认的窗口过程。
但实际上,MFC程序并不会把所有消息都交给DefWindowProc来处理,而是采用了一种消息映射的机制来处理各种消息。以后学到了再补充。