MFC程序的剖析及生死因果揭秘

本文剖析MFC底层的程序脉络,主要是由MFC深入浅出的学习笔记而得。

SDK下Windows程序的编程:对于一般SDK下Windows程序的编程,其主要步骤如下:


各步骤说明:

1:WinMain函数:它是Windows程序的基础,也是入口点函数,当Windows系统启动一个程序的时候,它调用的就是该 程序的WinMain函数,这很类似于传统Dos下的main函数。

WinMain函数的原型如下:

int WINAPI WinMain(  HINSTANCE hInstance,//表示该程序当前运行的实例句柄      
                     HINSTANCE hPrevInstance, //表示当前运行的实例的前一个实例的句柄 
                     LPSTR lpCmdLine, //一个以空终止的字符串,指定传递给应用程序的命令行参数
                     int nCmdShow);   //指定程序的窗口应该如何显示
2:在调用一个API函数例如CreateWindow创建一个窗口前,有必要对该类型的窗口进行一个整体性的设计,这就是窗口类的作用,它由一个WNDCLASS结构体的形式定义了一个类型的窗口各个方面的类容,例如串口的样式、窗口过程函数、图标、光标、背景画刷、菜单、以及窗口类的名字等。

       2.1  创建窗口类完成后,还需要注册这个窗口类,调用RegistClass(const WNDCLASS *lpWndClass)函数即可。

       2.2  接下来就是创建一个窗口啦,调用函数CreateWindow即可,CreateWindow函数原型如下:

HWND CreateWindow( LPCTSTR lpClassName, //指定窗口类的名称
                   LPCTSTR lpWindowName,//指定窗口的名称
                   DWORD dwStyle,       //指定窗口的创建样式
<span style="font-family: Arial, Helvetica, sans-serif;">                                      int   x,  </span><span style="font-family: Arial, Helvetica, sans-serif;"> int y,                    / /指定窗口的左上角x、y坐标</span>
                   int nWidth,int nHeight,//指定窗口的宽度和高度
                   HWND hWndParent,       //指定被创建窗口的父窗口的句柄
                   HMENU hMenu,           //指定窗口菜单的句柄
                   HANDLE hInstance,      //指定窗口所属应用实例的句柄
                   PVOID lpParam );       //作为WM_CREATE消息的附加参数lParam传入的数据指针
      2.3  创建窗口完毕以后,需要将其显示出来,这个调用ShowWindow函数来进行。其函数原型如下:

BOOL ShowWindow( HWND hWnd,      //需要显示的窗口句柄,如果创建窗口成功,CreateWindow函数会返回该句柄
                 int nCmdShow ); //指定了窗口显示的状态。
     2.4  在调用ShowWindow函数之后,紧接着调用UpdateWindow函数来刷新窗口,他其实是通过发送一个WM_PAINT消息来刷新窗口

3:Windows程序是基于消息的事件驱动程序,所以接下来需要编写消息循环,以不断地从消息队列中取出消息进行响应。

       消息的结构体原型如下:

typedef struct tagMSG {
  HWND   hwnd;      //指消息所属的窗口句柄
  UINT   message;   //指定消息的标识符,消息是由数值来表示的,但是通常为了便于记忆,用宏定义成特殊标识
  WPARAM wParam;    //指定消息的附加信息
  LPARAM lParam;    <span style="font-family: Arial, Helvetica, sans-serif;">//指定消息的附加信息</span>
  DWORD  time;      //消息投递到消息队列的时间
  POINT  pt;        //鼠标的当前位置
} MSG, *PMSG; 
     消息循环的创建需要调用GetMessage函数,其函数原型如下:

BOOL GetMessage(
LPMSG lpMsg,          //指向一个消息结构体
HWND hWnd,            //指定接收属于哪一个窗口的消息,通常设置为NULL,用于接收属于调用线程的所有窗口的窗口消息
UINT wMsgFilterMin,   //指定要获取的消息的最小值
UINT wMsgFilterMax ); //指定要获取的消息的最大值
    通常编写的消息循环代码如下:
MSG  msg
while(GetMassage(&msg,NULL,0,0))
{
     TranslateMessage(&msg);//将键盘等消息的附加参数中的虚拟键消息转换为字符消息
     DispatchMessage(&msg); //将消息回传给操作系统,操作系统调用窗口过程函数对消息进行处理
}
   GetMessage函数只有在接收到WM_QUIT消息时,才返回0,此时While语句的调节判断为假,循环退出,程序才有可能结束运行。

4:完成了消息循环的编写,最后就是编写窗口过程函数,它是用来处理发送给窗口的消息,函数原型如下:

LRESULT CALLBACK WindowProc(
HWND hwnd,        //指定消息对应的窗口句柄
UINT uMsg,        //消息代码
WPARAM wParam,    //消息附加信息
LPARAM lParam );  //消息附加信息
在这里,LERSULT的实际类型是long,CALLBACK的实际类型是_stdcall,定义成CALLBACK宏是表示该函数是一回调函数,其中_stdcall表示的是函数的一种调用约定,它指定了参数入栈的顺序等,在Windows程序中,回调函数必须遵循_stdcall的调用约定。窗口过程函数内部使用switch/case语句来确定窗口过程接收的是什么信息,以及如何处理这个消息。

以上就是SDK下编写Windows程序的基本步骤,本章主要剖析MFC中Windows程序的脉络,看清楚MFC程序的来龙去脉。

Application Framework与MFC

它是一个完整的数据模型,具备标准应用软件所需的一起功能,例如文件存取,打印预览,数据交换以及这些功能的使用接口,更术语的说,它是一整组合作无间的对象构成的大模型,这样做的好处是程序员只要带个购物袋到“类超级市场”买菜,回家后就可以轻易拼凑出一个色香味俱全的大餐。这里的类超级市场,就是C++类库,以产品来说,MFC就是一个类库,是(微软基础类库)的简称,但是MFC不仅仅是类库,它称得上是APPlocation FrameWork,这是因为它比一般性的类库更完整,更富有结构性特点,如果把一般性类库比作超时里的更重菜品,那么MFC就类似以一个火锅拼盘,缺的就是一把火(application object),也称之为引爆器,在MFC中就是派生自CWinAPP的一个全局性对象,正是他引起了连锁反应(一连串的new),是每一个类有了真正的对象,把应用程序以及Application FrameWork整个带动起来。也就是说,静态情况下的MFC是一组类库,但在程序运行时就生出了一群有活力的对象组。

纵览MFC

MFC类主要可分为下列数大群组:



     2.1:General Pupose classes 

             这些类既适用于Windows,也适用于DOS.

  1. CObject:CObject类是MFC的万类之首,派生自CObject的类,得以继承数个面相对象的重要性质,包括RTTI(运行时类型识别)、Persistence(对象保存)、Dynamic Creation(动态创建)等。
  2. 数据处理类:数据处理类主要包括了数组(Array)、链表(List)、及映射(Map)类,             
            

          3.杂项类

            CRect:封装Windows的RECT结构,常被用作MFC类成员函数的参数

            CSize:封装Windows的SIZE结构

            CPoint:封装Windows的POINT结构

            CTime:表现绝对时间,提供许多成员函数

            CTimeSpan:以秒数表现时间,通常用于计时码表。

            CString:用来处理字符串

        4.异常处理类(exception handling classes)

   2.2:Windows API classes

           这事MFC声名最著的一群类,CWinThread,CWinApp,CWnd,CCmdTarget,GDI类、DC类、Menu类等等。

  2.3:Application framework classes

          这一部分最为人认知的便是Document/view,这也是MFC跻身application framework的关键,Document/view的观念是希望把数据的本体和数据的显示分开处理。由于文           件产生之际,必须动态创建Document/view/Frame三种对象,所以必须需要Document Template管理之。

          CDocTemplate、CSingleDocTemplate、CMultiDocTemplate:Document Template扮演粘胶的角色,把Document和View和其Frame(外框窗口)黏胶在一起。

          CDocument:当你为自己的程序由CDocument派生出一个之类后,应该在其中加入成员变量以容纳文件数据,并加上成员函数负责修改文件的内容及读写文件。读写文             件由虚函数Serialize负责。

          CView:此类负责将文件内容呈现到显示装置上:也许是屏幕,也许是打印机。文件内容呈现由虚函数OnDraw负责。

   2.4:视觉性UI属于此类,例如工具栏CToolBar、状态栏CStatusBar、对话框列CDialogBar。

MFC程序的生死因果

以传统的C/SDK撰写Windows程序,最大的好处是可以清楚地看见整个程序的来龙去脉和消息动向,然而这些重要的动作在MFC应用程序中却隐晦不明,因为它们被Application Framework包起来了,现在就来抽丝剥茧,从源头上一点点分析MFC的来龙去脉。

 我们知道,Windows程序的入口点函数是WinMain,在MFC应用程序中,却丝毫找不见它,事实上, 在程序进入点之前,还有一个(仅有一个)全局对象,也是所谓的application object,当操作系统将程序加载并激活时,这个全局对象获得配置,其构造函数会先执行,比WinMain更早。以下代码来自AFXWIN.H。

class CWinApp : public CWinThread
{
	DECLARE_DYNAMIC(CWinApp)
public:



// Attributes
	HINSTANCE m_hInstance;     //一下四个参数对应WinMain中的四个参数
        HINSTANCE m_hPreInstance;
	LPTSTR m_lpCmdLine;
	int m_nCmdShow;
        CWnd*  m_pMainWnd;        //用来记录主窗口的handle,但是自MFC4.x开始,该变量已被移往其CWinApp的父类CWinThread中。
public:
<pre name="code" class="cpp">        virtual BOOL InitInstance();<span style="font-family: Arial, Helvetica, sans-serif;">     </span>
        virtual int Run();
Virtual BOOL InitApplication();

 几乎可以说CWinAPP用来取代WinMain在SDK程序中的地位,并不是说MFC程序没有WinMain,而是说传统上SDK程序的WinMain所完成的工作现在由CWinAPP的三个函数来完成,WinMain只是扮演驾驭它们的角色: 

<pre name="code" class="cpp"> virtual BOOL InitInstance();<span style="font-family: Arial, Helvetica, sans-serif;">     </span>
 virtual int Run();
Virtual BOOL InitApplication();

 CFrameWnd主要是用来掌握一个窗口,几乎可以说它是用来SDK程序中的窗口函数的地位。 

class CMyFrame:public CFameWnd
{
public:
       CMyFrameWnd();
       afx_msg  void OnPaint();
       afx_msg  void  OnAbout();
       DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyFrameWnd,CFrameWnd)
    ON_WM_PAINT()
    ON_COMMAND(IDM_ABOUT,OnAbout)
END_MESSAGE_AMP()
MFC内部建立了一个所谓的Message Map机制(消息路由),会把消息自动送到“与消息对应的特定函数中去”,消息与处理函数之间的对应关系由程序员指定,DECLARE_MESSAGE_MAP另搭配其他宏,就可以很便利的将消息与其处理函数关联在一起:

引爆器-Applixation Object (Hello.cpp)

CMyWinApp theApp;   // application object(引爆器)

BOOL CMyWinApp::InitInstance()
{
   m_pMainWnd = new CMyFrameWnd();
   m_pMainWnd->ShowWindow(m_nCmdShow);
   m_pMainWnd->UpdateWindow();
   return TRUE;
}

CMyFrameWnd::CMyFrameWnd()
{
   Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault,
          NULL, "MainMenu");     
}

BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
   ON_COMMAND(IDM_ABOUT, OnAbout)
   ON_WM_PAINT()
END_MESSAGE_MAP()
1、当执行程序时,要产生全局对象,必然执行构造函数,我们没有定义CMyWinApp,于是其父类CWinApp的构造函数执行。(以下代码来自APPCORE.CPP)
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();
	ENSURE(pModuleState);
	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
	ENSURE(pThreadState);
	ASSERT(AfxGetThread() == NULL);
	pThreadState->m_pCurrentWinThread = this;
	ASSERT(AfxGetThread() == this);
	m_hThread = ::GetCurrentThread();
	m_nThreadID = ::GetCurrentThreadId();

	// initialize CWinApp state
	ASSERT(afxCurrentWinApp == NULL); 
	pModuleState->m_pCurrentWinApp = this;   //注意:这里的<span style="font-family: Arial, Helvetica, sans-serif;">m_pCurrentWinApp后面的代码中会见到,目前它的值已等于CMyWinApp*</span>
     
	ASSERT(AfxGetApp() == this);
2、CWinApp的成员变量将因为theAPP这个全局对象的诞生而获得配置与初值,theAPP配置完成后,WinMain登场,我们并未写WinMain程序代码,这是有MFC早已准备好的并由链接器直接加到应用程序代码中的。(以下代码来自WinMain.cpp,代码经过整改)

extern "c" int WINAPI
_tWinMain(HINATANCE hInstance,HINATANCE hPrevHinstance,LPTSTR lpCmdLine,int nCmdShow)
{
       return AFXWinMain(hInstance,hPrevHinstance,lpCmdLine,nCmdShow);
}
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	_In_ LPTSTR lpCmdLine, int nCmdShow)
{
	int nReturnCode = -1;
	CWinApp* pApp = AfxGetApp();

        AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)

        pApp->InitApplication())
      <pre name="code" class="cpp">        pApp-><span style="font-family: Arial, Helvetica, sans-serif;">InitInstance()
</span><span style="font-family: Arial, Helvetica, sans-serif;">        </span>
        pApp<span style="font-family: Arial, Helvetica, sans-serif;">->Run();</span>
AfxWinTerm();return nReturnCode;}

 
——AFXWIN_INIINE CWINAPP* AFXAPI AfxGetApp()
{
     return afxCurrentWinApp;//这是一个宏定义,#define afxCurrentWinApp AfxGetModuleState->m_pCurrentWinApp(该指针上面构造函数代码出现过)
}
根据以上代码的去伪存真,我们得知_WinMain函数实际上是进行了如下操作

CMyWinAPP::InitApplication();//实际上是调用CWinApp::<span style="font-family: Arial, Helvetica, sans-serif;">InitApplication(),因为CMyWinApp并没有改写InitApplication</span>
CMyWinAPP::InitInatance();
CMyWinAPP::Run();//<span style="font-family: Arial, Helvetica, sans-serif;">实际上是调用CWinApp::</span><span style="font-family: Arial, Helvetica, sans-serif;">Run;因为CMyWinApp并没有改写Run</span>

3、从上面的流程中看出,AfxWinInit是继CWinApp的构造函数和_WinMain之后在AfxWinMain中进行的第一个函数,它后面就是InitApplication函数:

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;

	LoadSysPolicies();

	return TRUE;
}
如上的这些操作都是MFC为了内部管理而做的。

4、继InitApplication之后,AfxWinMain函数中调用InitInstance函数,注意此处其实调用的是CMyWinApp改写的InitInstance函数,原因是C++的多态性。

BOOL CMyWinApp::InitInstance()
{
   m_pMainWnd = new CMyFrameWnd();
   m_pMainWnd->ShowWindow(m_nCmdShow);
   m_pMainWnd->UpdateWindow();
   return TRUE;
}
5、CMyWinApp::InitInstance一开始new了一个CMyFrameWnd对象,准备用作主框架窗口的C++对象,new会引发构造函数:

CMyFrameWnd::CMyFrameWnd()
{
   Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault,
          NULL, "MainMenu");    
}
这里Create是CFrameWnd的成员函数,他将产生一个窗口,第一个参数NULL表示使用MFC内建的窗口类。也许你会很奇怪,似乎我们并没有创建窗口类啊,事实上已经创建了,下面便来揭晓。

6、(以下代码来自WINFRM.CPP)

BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
	LPCTSTR lpszWindowName,
	DWORD dwStyle,
	const RECT& rect,
	CWnd* pParentWnd,
	LPCTSTR lpszMenuName,
	DWORD dwExStyle,
	CCreateContext* pContext)
{
	HMENU hMenu = NULL;
	if (lpszMenuName != NULL)
	{
		// load in a menu that will get destroyed when window gets destroyed
		HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU);
		if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
		{
			TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");
			PostNcDestroy();            // perhaps delete the C++ object
			return FALSE;
		}
	}

	m_strTitle = lpszWindowName;    // save title for later

	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;
	}

	return TRUE;
}
上面的代码有调用CreateEx函数,注意CFrameWnd的父类CWnd类中有成员函数CreateEx,但是其派生类CFrameWnd中并没有继承它,所以实际上调用的是父类CWnd的CreateEx函数。(以下代码来自WINCORE.CPP)
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
		LPCTSTR lpszWindowName, DWORD dwStyle,
		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 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 = ::AfxCtxCreateWindowEx(cs.dwExStyle, cs.lpszClass,
			cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
			cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
注意:上面函数中调用的PreCreateWindow是虚函数,其子类CFrameWnd也改写了它,由于指针pApp指向的是子类,所以此处调用的已应该是CFrameWnd函数中的PreCreateWindow,(以下代码来自WINFRM.CPP)
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
	if (cs.lpszClass == NULL)
	{
		VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)); <pre name="code" class="cpp"><pre name="code" class="cpp">//#define  <span style="font-family: Arial, Helvetica, sans-serif;">AfxDeferRegisterClass(fClass)    (afxRegisteredClasses & fClass)?TRUE:AfxEndDeferRegisterClass(</span><span style="font-family: Arial, Helvetica, sans-serif;">fClass</span><span style="font-family: Arial, Helvetica, sans-serif;">))</span>

 //这个宏表示,如果变量 
afxRegisteredClasses的值显示系统已经注册了fClass这种窗口类,MFC啥也不做,否则就调用 
AfxEndDeferRegisterClass( 
fClass 
) 
//其中 
afxRegisteredClasses是一个旗标变量,显示记录已经注册了哪些窗口类cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background}if (cs.style & FWS_ADDTOTITLE)cs.style |= FWS_PREFIXTITLE;cs.dwExStyle |= WS_EX_CLIENTEDGE;return TRUE;} 
 (以下代码来自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;
	}
	if (fToRegister & AFX_WNDCOMMCTL_UPDOWN_REG)
	{
		init.dwICC = ICC_UPDOWN_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_UPDOWN_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_TREEVIEW_REG)
	{
		init.dwICC = ICC_TREEVIEW_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TREEVIEW_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_TAB_REG)
	{
		init.dwICC = ICC_TAB_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TAB_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_PROGRESS_REG)
	{
		init.dwICC = ICC_PROGRESS_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_PROGRESS_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_LISTVIEW_REG)
	{
		init.dwICC = ICC_LISTVIEW_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_LISTVIEW_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_HOTKEY_REG)
	{
		init.dwICC = ICC_HOTKEY_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_HOTKEY_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_BAR_REG)
	{
		init.dwICC = ICC_BAR_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_BAR_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_ANIMATE_REG)
	{
		init.dwICC = ICC_ANIMATE_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_ANIMATE_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_INTERNET_REG)
	{
		init.dwICC = ICC_INTERNET_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_INTERNET_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_COOL_REG)
	{
		init.dwICC = ICC_COOL_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_COOL_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_USEREX_REG)
	{
		init.dwICC = ICC_USEREX_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_USEREX_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_DATE_REG)
	{
		init.dwICC = ICC_DATE_CLASSES;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_DATE_REG);
	}
	if (fToRegister & AFX_WNDCOMMCTL_LINK_REG)
	{
		init.dwICC = ICC_LINK_CLASS;
		fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_LINK_REG);
	}
	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 controls registered, 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 mamy classes as requested
	return (fToRegister & fRegisteredClasses) == fToRegister;
}
上述函数中,调用了两个函数完成了实际的窗口类注册操作,一个是RegisterWithIcon,一个是AfxRegisterClass。

7、继第四步new 一个CMyFrameWnd的构造函数结束后,窗口已经诞生出来了,程序流程又回到CMyWinApp::InitInstance函数中,于是调用ShowWindow函数另窗口显示出来,并调用UpdateWindo函数令程序发送WM_PAINT消息。那么现在问题来了,现在这个WM_PAINT消息时如何送到窗口函数手中,而且,窗口函数在哪里?

8、回到最前面,InitInstance执行完毕以后,程序继续执行CWinApp::RUN()函数。(以下代码来自APPCORE.CPP)

int CWinApp::Run()
{
	if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
	{
		// Not launched /Embedding or /Automation, but has no main window!
		TRACE(traceAppMsg, 0, "Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.\n");
		AfxPostQuitMessage(0);
	}
	return CWinThread::Run();
}
然后调用CWinThread::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));
	}
}
以上函数再调用到另外一个函数PumpMessage();

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;
	}

#ifdef _DEBUG
  if (pState->m_nDisablePumpCount != 0)
	{
	  TRACE(traceAppMsg, 0, "Error: CWinThread::PumpMessage called when not permitted.\n");
	  ASSERT(FALSE);
	}
#endif

#ifdef _DEBUG
	_AfxTraceMsg(_T("PumpMessage"), &(pState->m_msgCur));
#endif

  // process this message

	if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
	{
		::TranslateMessage(&(pState->m_msgCur));
		::DispatchMessage(&(pState->m_msgCur));
	}
  return TRUE;
}
获得的消息时如何交给适当的程序去处理的呢,跟SDK中一样,调用DispatchMessage,把消息丢给窗口函数,窗口函数事实上已经由MEC提供,不信看看之前注册窗口类时给出的代码,wndcls.lpfnWndProc=DefWindowProc。

9、至此,剩下的问题是,程序员如何为特定的消息设计特定的处理程序?MFC应用程序对消息的识别与判别是采用所谓的“MessageMap"机制。这种机制的初衷是为了提供更为方便的程序接口(宏或表格),让程序员可以很方便的建立起消息与处理程序的对应关系。MFC提供给应用程序的”很方便的接口“是指两组宏,一组是在CMyFrameWnd头文件加上DECLARE_MESSAGE_MAP

class CMyFrameWnd : public CFrameWnd
{
public:
   CMyFrameWnd();            // constructor
   afx_msg void OnPaint();   // for WM_PAINT
   afx_msg void OnAbout();   // for WM_COMMAND (IDM_ABOUT)
   void IdleTimeHandler(LONG lCount);  // we want it call by CMyWinApp::OnIdle

private:
   DECLARE_MESSAGE_MAP()     // Declare Message Map
   static VOID CALLBACK LineDDACallback(int,int,LPARAM);
};
另外一组是在CMyFrameWnd源文件加上的

BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
   ON_COMMAND(IDM_ABOUT, OnAbout)
   ON_WM_PAINT()
END_MESSAGE_MAP()
这么一来,就把消息WM_PAINT导到OnPaint函数,把WM_COMMAND(IDM_ABOUT)导到OnAbout函数去了,这里便是MFC的消息路由机制。


来龙去脉总整理

1:程序的诞生:

  • Application object产生,内存于是获得配置,初值亦设立了。
  • AfxWinMain执行AfxWinInit,后者又调用AfxInitThread,把消息队列尽量加大到96。
  • AfxWinMain执行InitApplication,这是CWinApp的虚函数,但我们通常不改写它。
  • AFXWinM执行InitInstance,这是CWinApp的虚函数,我们必须改写他。
  • CMyWinApp::InitInstance 'new'了一个CMyFrameWnd对象。
  • CMyFrameWnd构造函数调用Create,产生主窗口。我们在Create参数中指定的窗口类是NULL,于是MFC根据窗口类的品种,自行为我们注册一个名为”AfxFrameOrView42d“的窗口类。
  • 回到InitInstance中继续执行ShowWindow,显示窗口。
  • 执行UpdateWindow,于是发出WM_PAINT。
  • 回到AfxWinMain,执行Run,进入消息循环。

2:程序开始运行:

  • 程序获得WM_PAINT消息(由CWinApp::Run中的::GetMessage循环)。
  • WM_PAINT经由::DispatchMessage送到窗口函数CWnd::DefWindowProc中。
  • CWnd::DefWindowProc将消息传递过消息映射表(Message Map)。
  • 传递过程发现有相符项目,于是调用项目中对应的函数。此函数是应用程序利用BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间的宏设立起来的。
  • 标准消息的处理程序亦有标准命名,例如WM_PAINT必然由OnPaint处理。
3:程序的死亡:
  • 使用者单击File/Close,于是发出WM_CLOSE消息。
  • CMyFrameWnd并没有设置WM_CLOSE处理程序,于是交给默认的处理程序。
  • 默认函数对于WM_CLOSE的处理方式是调用::DestoryWindow,并因而发出WM_DESTORY。
  • 默认对于WM_DESTORY处理方式是调用::PostQuitMessage,因此发出WM_QUIT。
  • CWinApp::Run收到WM_QUIT后会结束其内部之消息循环,然后调用ExitInstance,这是CWinApp的一个虚拟函数。
  • 如果CMyWinApp改写了ExitInstance,那么久调用CMyWinApp::ExitInstance,否则就是CWinApp::ExitInstance。
  • 最后回到AfxWinMain,执行AfxWinMain,结束程序。








  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值