本文剖析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:
// 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处理。
- 使用者单击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,结束程序。