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()返回的是应用程序对象的指针,如果该应用程序(或进程)只有一个界面线程在运行,那么这两者返回的都是一个全局的应用程序对象指针。

知道pThreadpApp所指向的实际上都是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中调了GetMessageTranslateMessageDispatchMessage这个三个函数。

 

 

 

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函数不仅允许了窗口xy坐标以及长宽作参数,还允许包含这些参数的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;      
 

转到CFrameWndPreCreateWindow函数(查看声明,这个函数的确是虚函数):

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框架程序剖析笔记(下篇)的主要内容,如果未能解决你的问题,请参考以下文章

二. MFC框架程序分析--Windows编程课程学习笔记

MFC框架程序剖析

JAVA 笔记 ThreadPoolExecutor 源码剖析

MFC程序执行过程剖析(转)

HIVE3 深度剖析 (下篇)

MFC实现原理剖析