MFC框架程序剖析笔记(下篇)
Posted 独饮月色的猫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MFC框架程序剖析笔记(下篇)相关的知识,希望对你有一定的参考价值。
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程序的时候试试。。例程在这篇笔记的隔壁。。实验结果就写那了~
AfxEndDeferRegisterClass ( WINCORE.CPP 中),完成注册窗口的操作。它对于窗口设置了大量默认的样式,这里只需要判断是样式中的哪一种,选择即可,然后调用 AfxRegisterClass ( WINCORE.CPP 中) 来完成注册。
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创建窗口时,其参数就会发生相应的改变,创建出符合我们需要的窗口。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
8.消息循环的实现(Run方法)
位于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));
Run函数主要结构是一个for循环,该循环在接受到一个WM_QUIT消息时退出。
此循环中用到了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来处理,而是采用了一种消息映射的机制来处理各种消息。以后学到了再补充。
以上是关于MFC框架程序剖析笔记(下篇)的主要内容,如果未能解决你的问题,请参考以下文章