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.
- CObject:CObject类是MFC的万类之首,派生自CObject的类,得以继承数个面相对象的重要性质,包括RTTI(运行时类型识别)、Persistence(对象保存)、Dynamic Creation(动态创建)等。
- 数据处理类:数据处理类主要包括了数组(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处理。
- 使用者单击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程序的剖析及生死因果揭秘相关推荐
- 六、MFC 程序的生死因果 (学习笔记)
MFC程序的生死因果 项目HELLO 理想如果不向实际做点妥协,理想就会归于尘土. 中华民国还得十次革命才得建立,对象导向怎能把一切传统都抛开. 以传统的C/SDK 撰写Windows 程序,最大的好 ...
- MFC技术内幕系列之(一)---MFC应用程序“生死因果”内幕
/// /********* 文章系列:MFC技术内幕系列***********/ /************ ...
- MFC应用程序“生死因果”内幕
/// /********* 文章系列:MFC技术内幕系列***********/ /************MFC ...
- 深入浅出MFC学习笔记(第6章 :MFC程序的生死因果)
第六章:MFC程序的生死因果 本章主要是从MFC程序代码中,找出一个windows程序原本该有的程序入口点.窗口类注册.窗口产生.消息循环.窗口函数等操作.抽丝剥茧彻底理解一个MFC程序的诞生与结束. ...
- 《深入浅出MFC》第六章 MFC程序的生死因果
SDK程序设计的第一要务是理解最重要的数个API函数的意义和用法,MFC程序设计的第一要务则是理解几个最重要的类,最基本的两个类为CWin App和CFrameWnd. 开发MFC程序需要的函数库:W ...
- MFC程序的生死因果
MFC程序的来龙去脉(causal relations) MFC的程序如何运行,第一件事情就是找出MFC程序的进入点.MFC程序也是Windows程序.所以它应该也有一个WinMain.但在程序中好 ...
- MFC程序执行过程剖析
一 MFC程序执行过程剖析 1)我们知道在WIN32API程序当中,程序的入口为WinMain函数,在这个函数当中我们完成注册窗口类,创建窗口,进入消息循环,最后由操作系统根据发送到程序窗口的消息调用 ...
- VC++基于MFC的程序框架剖析(前言)
目录 基于MFC的程序框架剖析 MFC类组织结构图:层次结构图 接上:VC++MFC应用程序向导 接下:VC++MFC程序中的WinMain函数(一) 基于MFC的程序框架剖析 MFC库是开发Wind ...
- mfc编程 孙鑫_孙鑫VC++视频教程笔记-(3)MFC程序框架的剖析 附1-SDI程序流程图
1,寻找WinMain人口: 在安装目录下找到MFC文件夹下的SRC文件夹,SRC下是MFC源代码. 路径:MFC|SRC|APPMODUL.CPP: _tWinMain(HINSTANCE hIns ...
最新文章
- LMV358, LMV321, LMV324, LMV324S-注意事项
- Lucene学习总结之二:Lucene的总体架构
- windows无法安装到这个磁盘。选中的磁盘采用GPT分区形式
- ComponentName的意思
- java tts引擎_Android TTS系列二——如何开发一款系统级tts引擎?
- 月薪2万是一种怎样的人生体验?
- 学习CGI之前,需要配置阿帕奇---windows
- c#(asp.net/core)杂谈笔记
- qcom charger
- 愿你不会像我一样,活成狗一般的生活!
- 怎样把m4a转换mp3格式?
- Shell中的幽灵王者—JAVAWEB 内存马 【认知篇】
- html数独游戏制作,使用HTML5和ES6(JS)实现的在线数独游戏生成器和解答
- 398、Java框架52 -【Hibernate - 分页、两种获取方式】 2020.10.27
- vasp 模拟退火_VASP 计算问题小结
- 活着活着也许就明白了!
- 正则匹配空字符串 /(^\s)/
- JustOj 2040: 王胖子买零食 (贪心)
- 使用源码编译安装AMD ROCm
- appcrash php5ts.dll,appcrash
热门文章
- 23种设计模式都适用于哪些场景?
- 新的深度学习优化器Ranger: RAdam + LookAhead的协同组合,两者兼得。
- 【中亦安图】风险提醒之Oracle RAC高可用失效(2)
- CheatMaker教学进阶之一 - 基地址与指针
- 华为荣耀路由器虚拟服务器,荣耀路由器怎么设置?
- Root后安装Xposed框架卡Logo问题
- 3原色对照表.....
- 网课答案php源代码,知到网课答案WEB程序设计(PHP)2020章节测试答案
- 如何申请GlobalSign SSL证书,需要什么条件?
- Jupyter Notebook 输出有颜色的文字