MFC (微軟)
作为一个应用程序的开发框架,必须满足各方面的功能需求。
应用程序启动
编辑
基于MFC开发的应用程序在启动时,Windows操作系统:
首先调用WinMain函数(位于appmodul.cpp中,封装到mfc80.dll(VS2005版)),WinMain函数内调用了AfxWinMain函数。
AfxWinMain函数(位于WinMain.cpp中)调用了
该应用程序自定义的App类(这个类派生于CWinApp的,CWinApp又是派生于CWinThread,因此代表了应用程序的主线程)的InitInstance函数,该函数注册并创建窗口(通过AppUI2.cpp中的ProcessShellCommmand函数),然后ShowWindow、UpdateWindow;
CWinThread的InitInstance函数;
CWinThread的Run函数(位于thrdcore.cpp中)。该函数内部是Windows的消息循环。 当应用程序收到WM_QUIT消息后,CWinThread::Run函数返回,紧接着CWinThread::ExitInstance被调用,该函数可被覆盖。程序至此退出运行。 消息循环是一个for(;;)的死循环,该死循环内部包含了一个do...while的循环结构。while循环条件是调用PeekMessage函数的返回值,如果当前UI线程消息队列为空就返回到外层的死循环;while循环体内做两件事:
PumpMessage()。实际调用AfxInternalPumpMessage函数实现其功能:GetMessage()、AfxPreTranslateMessage()、TranslateMessage()、DispatchMessage().即:从UI线程消息队列移除一条消息、遍历该消息的CWnd类直到该窗口的各级别父窗口的CWnd类以提供预处理该消息的机会、如果该消息是按键消息则翻译为WM_CHAR消息、把该消息给相应的窗口函数。
IsIdleMessage():实际调用了AfxInternalIsIdleMessage函数,对于WM_PAINT、WM_SYSTIMER、以及光标位置没有变化的WM_MOUSEMOVE或WM_NCMOUSEMOVE,为Idle Message。
各个窗口函数(WndProc)内部首先获取对应当前窗口句柄的CWnd类的指针,然后调用AfxCallWndProc函数。
应用程序结束
编辑
如果是点击了IDOK按钮,默认是调用OnOK(),然后是OnDestory(),最后是PostNcDestroy()
如果点击IDCANCEL按钮,默认调用OnCancel(),然后是OnDestory(),最后是PostNcDestroy()
如果点击右上角的关闭按钮:先OnClose(),然后是OnCancel(),再然后是OnDestory() ,最后是PostNcDestroy()
消息循环与消息映射
编辑
参见:Windows消息循环
MFC Class hierarchy for Windows message process flow
消息分类
编辑
分类
消息
映射宏
消息处理函数原型
注释
系统消息
标准Windows消息
MFC使用专用的相关的宏。如ON_WM_CREATE()
MFC使用专用的消息处理成员函数
一般地由窗口对象来处理这类消息
命令消息 WM_COMMAND
ON_COMMAND(id, memberFxn)
void OnXXX ()
通过标识符ID来区分来自哪个菜单项、工具栏按钮或者加速键等
ON_COMMAND_RANGE(id, idLast, memberFxn)
void OnXXX (UINT id)
批量处理一定范围内的标示符ID
ON_UPDATE_COMMAND_UI(id, idLast, memberFxn)
void OxXXX(CCmdUI* pCmdUI)
程序空闲时发的界面更新消息的宏
ON_UPDATE_COMMAND_UI_RANGE(id, idLast, memberFxn)
void OxXXX(CCmdUI* pCmdUI)
ON_CONTROL(id, idLast, memberFxn)
void OnXXX ()
父窗口响应控件发送的消息
ON_CONTROL_RANGE(id, idLast, memberFxn)
void OnXXX (UINT id)
ON_CONTROL_REFLECT(id, idLast, memberFxn)
void OnXXX ()
子控件响应父窗口反射回来的通知消息
通知消息WM_NOTIFY
ON_NOTIFY(wNotifyCode,id,memberFxn)
afx_msg void memberFxn(NMHDR * pNotifyStruct, LRESULT* result)
wParam为控件ID,lParam指向NMHDR结构体,结构体的code域值为控件通知码用来表示控件上的动作,如NM_CLICK.一般地由父窗口对象来处理这类消息。
ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )
可手工添加(class wizard不支持),处理控件id连续的一批控件的同一通知码消息。
ON_NOTIFY_EX
允许通知消息在多处被处理
ON_NOTIFY_EX_RANGE
ON_NOTIFY_REFLECT
afx_msg void memberFxn(NMHDR * pNotifyStruct, LRESULT* result)
复杂子控件响应父窗口反射回来的通知消息
自定义消息
窗口类内部自定义消息WM_USER到WM_APP-1
ON_MESSAGE(消息名,memberFxn)
afx_msg LRESULT OnMyMessageXXX(WPARAM wParam, LPARAM lParam)
示例
程序内部自定义消息WM_APP到0xBFFF
ON_THREAD_MESSAGE(WM_THREADMSG,OnThreadMessage)
afx_msg void OnThreadMessage(WPARAM wParam,LPARAM lParam);
示例
ON_REGISTERED_THREAD_MESSAGE
afx_msg void OnMyRegisterdThreadMsg(WPARAM, LPARAM);
RegisterWindowMessage()使用一个字符串来登记一个自定义的消息ID
应用程序之间自定义消息0xC000到0xFFFF
ON_REGISTERED_MESSAGE
LRESULT OnMyMessageXXX(WPARAM wParam, LPARAM lParam)
RegisterWindowMessage()使用一个字符串来登记一个自定义的消息ID,便于跨进程应用
控件的通知消息与反射消息
编辑
窗体上的控件,应当向父窗体通报控件发生的各种事件,如被点击、绘制、内容改变等等,称为通知消息(notification message )。在 Windows 3.x的16位程序设计时代,控件向父窗体发送WM_COMMAND消息,由父窗体的代码负责实现这些事件。其中wParam的低16位是 control ID,高16位是notification code (例如BN_CLICKED);lParam是控件句柄。因此,再无可能传递其它信息给父窗体。为此,为传递具有特别内容的控件事件,Windows 3.x定义了一批特殊的通知消息(notification messages):
WM_PARENTNOTIFY:子窗口的某些重大事件发生时通知父窗口,包括创建、销毁、鼠标各键按下等事件
子窗口的滚动情况的通知消息
WM_VSCROLL
WM_HSCROLL
子窗口的绘制通知消息
WM_DRAWITEM,
WM_MEASUREITEM,
WM_COMPAREITEM,
WM_DELETEITEM,
WM_CHARTOITEM,
WM_VKEYTOITEM
WM_CTLCOLOR:设置按钮、编辑框、ListBox、Static、滚动条控件与MessageBox、DialogBox的前景色、背景色、背景模式、字体,并返回一把Brush,用于控件背景绘制。
早于4.0版本的MFC,在控件类提供了虚函数处理这些通知消息,这一办法已经被下述的“消息反射”取代(但仍然向后兼容继续支持)。
随着Windows 95开始了32位程序时代,伴之而来的是Win32 API 与 MFC 4.0。 Win32增加了很多复杂的控件,需要使用更多的通知消息传递很多复杂的数据给父窗体。Win32 API仅仅增加了一个消息WM_NOTIFY,就实现了这些功能。lParam参数开头是NMHDR数据结构,其后是与该通知类型相关的特定数据结构。
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
CWnd::OnNotify函数处理通知消息。它的默认实现是检查消息映射表(message map)查找通知的处理器函数并调用。一般说来,不必覆盖OnNotify;而应该写一个处理器函数并增加为该窗口类的消息映射表条目。
ON_NOTIFY(wNotifyCode, id, memberFxn)
成员函数应该写为:
afx_msg void memberFxn(NMHDR* pNotifyStruct, LRESULT* result);
MFC 4.0提供了一种特性“消息反射”(message reflection),[19]允许控件通知消息既可以在父窗口中,也可以在控件中被处理。可以对控件创建一个派生的控件类,实现对从父窗口反射回来的指定类型消息的处理。消息反射是MFC而不是Win32的特性,因此父窗口的类必须是从CWnd派生,从而父窗口在CWnd::OnNotify函数中处理控件的WM_NOTIFY时,首先调用CWnd::ReflectLastMsg把消息反射回控件的CWnd::SendChildNotifyLastMsg函数去处理;ReflectLastMsg返回值就是在控件的消息映射中使用ON_NOTIFY_REFLECT_EX()声明的反射消息处理函数的返回值,可以通知父窗口该消息是否已经被控件处理。控件的CWnd::SendChildNotifyLastMsg函数,首先获得线程的最后一条message,然后调用发送窗口的虚函数OnChildNotify函数。在子窗口处理反射回来的控件消息,第一种方法是重载控件窗口的OnChildNotify虚函数;第二种办法是由CWnd::OnChildNotify默认处理去调用CWnd::ReflectChildNotify函数,进入控件子窗口的MFC消息映射的标准处理(子窗口处理的消息被译成WM_REFLECT_BASE+WM_NOTIFY消息)。对于WM_NOTIFY,仅当在控件的消息映射(message map)中,控件没有通过ON_NOTIFY_REFLECT()声明的反射消息处理函数,父窗口的相应的通知消息处理函数才会被调用(在父窗口消息映射中使用宏ON_NOTIFY声明)。控件中通过ON_NOTIFY_REFLECT_EX()声明的反射消息处理函数可以返回真或假,以决定父窗口是否继续处理该通知消息。WM_NOTIFY以外的其它通知消息,父窗口在第一时间有机会处理它,控件对它的处理排在第二位。反射消息处理函数通常使用特定的名字,对应的消息反射宏的名字是在消息名字加上前缀ON_,后缀_REFLECT。如WM_CTLCOLOR对应ON_WM_CTLCOLOR_REFLECT。但以下三种情况,反射消息处理函数可以随意自行起名,对应的消息反射宏的名字分别为:
WM_COMMAND使用ON_CONTROL_REFLECT
WM_NOTIFY使用ON_NOTIFY_REFLECT
ON_UPDATE_COMMAND_UI使用ON_UPDATE_COMMAND_UI_REFLECT
消息传递处理机制
编辑
MFC类体系中,Windows消息传递处理机制是基于CCmdTarget类及其派生类的静态成员函数GetThisMessageMap()内部定义的静态数据成员:
成员类型为AFX_MSGMAP_ENTRY的数组_messageEntries。在类的实现文件中,在BEGIN_MESSAGE_MAP与END_MESSAGE_MAP之间的内容来初始化消息映射入口项数组。
数据类型为AFX_MSGMAP的变量messageMap。该结构包含两项,分别是直接基类GetThisMessageMap函数指针与本类的_messageEntries数组首元素地址。
在头文件的类定义中使用宏DECLARE_MESSAGE_MAP()来声明静态成员函数GetThisMessageMap与虚函数GetMessageMap
用户所写的类的Windows消息处理函数(例如OnCommand)必须转换为CCmdTarget::*的成员函数指针类型AFX_PMSG,保存在该类的_messageEntries数组中。
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows消息代号
UINT nCode; // WM_NOTIFY的控制代码
UINT nID; // WM_COMMAND下面的ID号,如果为其他的消息,则这个数字为0
UINT nLastID; //和前面的ID一起组成一个范围,用于发送一次消息,处理执行多次
UINT nSig; // 标志消息处理函数的类型
AFX_PMSG pfn; // 函数调用指针
};
typedef void (CCmdTarget::*AFX_PMSG)(void);
调用用户类中该消息处理函数时,根据该函数保存在_messageEntries中的signature(一个无符号整型表示的函数的形参类型列表与返回值类型),把类型为void (CCmdTarget::*AFX_PMSG)(void)的成员函数指针强制转为其它类型的CCmdTarget成员函数指针(例如void (AFX_MSG_CALL CWnd::*pfn_v_i_i)(int, int),目前在union MessageMapFunctions中列出了近百种CCmdTarget成员函数指针),然后调用转换后的成员函数指针。这是基于Visual C++编译器把单继承的成员函数指针编译为只保存了函数的内存起始地址,因此可以在同一个单继承类中把一种类型的成员函数指针强制转换为另一种成员函数指针,或者把单继承派生类的成员函数指针强制转换为基类成员函数指针。这是打破了C++标准的违例办法。例如,对于CWnd::OnCommand函数,转换过程是:
BOOL (CWnd::*)(WPARAM, LPARAM lParam) => void (CWnd::*)() => void (CCmdTarget::*)()