本文剖析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:// AttributesHINSTANCE 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);elsem_pszAppName = NULL;// initialize CWinThread stateAFX_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 stateASSERT(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);elseCDocManager::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 destroyedHINSTANCE 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++ objectreturn FALSE;}}m_strTitle = lpszWindowName;    // save title for laterif (!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 parametersCREATESTRUCT 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 registeredAFX_MODULE_STATE* pModuleState = AfxGetModuleState();fToRegister &= ~pModuleState->m_fRegisteredClasses;if (fToRegister == 0)return TRUE;LONG fRegisteredClasses = 0;// common initializationWNDCLASS wndcls;memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULL defaultswndcls.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 goif (fToRegister & AFX_WND_REG){// Child windows - no brush, no icon, safest default class styleswndcls.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 speedwndcls.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 windowswndcls.style = 0;   // control bars don't handle double clickwndcls.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 colorswndcls.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() APIinit.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 controlspModuleState->m_fRegisteredClasses |= fRegisteredClasses;// special case for all common controls registered, turn on AFX_WNDCOMMCTLS_REGif ((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 requestedreturn (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 stateBOOL 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 workwhile (bIdle &&!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)){// call OnIdle while in bIdle stateif (!OnIdle(lIdleCount++))bIdle = FALSE; // assume "no idle" state}// phase2: pump messages while availabledo{// pump message, but quit on WM_QUITif (!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 _DEBUGTRACE(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 decrementedreturn FALSE;}#ifdef _DEBUGif (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 messageif (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();            // constructorafx_msg void OnPaint();   // for WM_PAINTafx_msg void OnAbout();   // for WM_COMMAND (IDM_ABOUT)void IdleTimeHandler(LONG lCount);  // we want it call by CMyWinApp::OnIdleprivate:DECLARE_MESSAGE_MAP()     // Declare Message Mapstatic 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,结束程序。

MFC程序的剖析及生死因果揭秘相关推荐

  1. 六、MFC 程序的生死因果 (学习笔记)

    MFC程序的生死因果 项目HELLO 理想如果不向实际做点妥协,理想就会归于尘土. 中华民国还得十次革命才得建立,对象导向怎能把一切传统都抛开. 以传统的C/SDK 撰写Windows 程序,最大的好 ...

  2. MFC技术内幕系列之(一)---MFC应用程序“生死因果”内幕

     ///                      /********* 文章系列:MFC技术内幕系列***********/                      /************ ...

  3. MFC应用程序“生死因果”内幕

    ///                      /********* 文章系列:MFC技术内幕系列***********/                      /************MFC ...

  4. 深入浅出MFC学习笔记(第6章 :MFC程序的生死因果)

    第六章:MFC程序的生死因果 本章主要是从MFC程序代码中,找出一个windows程序原本该有的程序入口点.窗口类注册.窗口产生.消息循环.窗口函数等操作.抽丝剥茧彻底理解一个MFC程序的诞生与结束. ...

  5. 《深入浅出MFC》第六章 MFC程序的生死因果

    SDK程序设计的第一要务是理解最重要的数个API函数的意义和用法,MFC程序设计的第一要务则是理解几个最重要的类,最基本的两个类为CWin App和CFrameWnd. 开发MFC程序需要的函数库:W ...

  6. MFC程序的生死因果

    MFC程序的来龙去脉(causal relations)  MFC的程序如何运行,第一件事情就是找出MFC程序的进入点.MFC程序也是Windows程序.所以它应该也有一个WinMain.但在程序中好 ...

  7. MFC程序执行过程剖析

    一 MFC程序执行过程剖析 1)我们知道在WIN32API程序当中,程序的入口为WinMain函数,在这个函数当中我们完成注册窗口类,创建窗口,进入消息循环,最后由操作系统根据发送到程序窗口的消息调用 ...

  8. VC++基于MFC的程序框架剖析(前言)

    目录 基于MFC的程序框架剖析 MFC类组织结构图:层次结构图 接上:VC++MFC应用程序向导 接下:VC++MFC程序中的WinMain函数(一) 基于MFC的程序框架剖析 MFC库是开发Wind ...

  9. mfc编程 孙鑫_孙鑫VC++视频教程笔记-(3)MFC程序框架的剖析 附1-SDI程序流程图

    1,寻找WinMain人口: 在安装目录下找到MFC文件夹下的SRC文件夹,SRC下是MFC源代码. 路径:MFC|SRC|APPMODUL.CPP: _tWinMain(HINSTANCE hIns ...

最新文章

  1. LMV358, LMV321, LMV324, LMV324S-注意事项
  2. Lucene学习总结之二:Lucene的总体架构
  3. windows无法安装到这个磁盘。选中的磁盘采用GPT分区形式
  4. ComponentName的意思
  5. java tts引擎_Android TTS系列二——如何开发一款系统级tts引擎?
  6. 月薪2万是一种怎样的人生体验?
  7. 学习CGI之前,需要配置阿帕奇---windows
  8. c#(asp.net/core)杂谈笔记
  9. qcom charger
  10. 愿你不会像我一样,活成狗一般的生活!
  11. 怎样把m4a转换mp3格式?
  12. Shell中的幽灵王者—JAVAWEB 内存马 【认知篇】
  13. html数独游戏制作,使用HTML5和ES6(JS)实现的在线数独游戏生成器和解答
  14. 398、Java框架52 -【Hibernate - 分页、两种获取方式】 2020.10.27
  15. vasp 模拟退火_VASP 计算问题小结
  16. 活着活着也许就明白了!
  17. 正则匹配空字符串 /(^\s)/
  18. JustOj 2040: 王胖子买零食 (贪心)
  19. 使用源码编译安装AMD ROCm
  20. appcrash php5ts.dll,appcrash

热门文章

  1. 23种设计模式都适用于哪些场景?
  2. 新的深度学习优化器Ranger: RAdam + LookAhead的协同组合,两者兼得。
  3. 【中亦安图】风险提醒之Oracle RAC高可用失效(2)
  4. CheatMaker教学进阶之一 - 基地址与指针
  5. 华为荣耀路由器虚拟服务器,荣耀路由器怎么设置?
  6. Root后安装Xposed框架卡Logo问题
  7. 3原色对照表.....
  8. 网课答案php源代码,知到网课答案WEB程序设计(PHP)2020章节测试答案
  9. 如何申请GlobalSign SSL证书,需要什么条件?
  10. Jupyter Notebook 输出有颜色的文字