-
MFC中的消息映射
2019-08-24 17:08:48MFC中消息处理是封装好的,他会根据消息是调用对应的对象的处理函数,而MFC是怎么做到的呢? MFC是会自己建一张消息映射表,而自己分发消息就得先获取消息了,这就用到了HOOK了,这样获取到消息后,自己就可以对照...MFC中消息处理是封装好的,他会根据消息是调用对应的对象的处理函数,而MFC是怎么做到的呢?
MFC是会自己建一张消息映射表,而自己分发消息就得先获取消息了,这就用到了HOOK了,这样获取到消息后,自己就可以对照MFC中的消息映射表去执行对应的对象的处理函数
MFC消息映射机制执行步骤是:当消息发生,我们用HOOK技术把本来要发送到窗口过程的消息抓获,然后对照一下MFC窗口的消息映射表,如果是表里面有的消息,就执行其对应的函数
我们分析一下MFC中的消息映射表是怎么做到的:主要是是BEGIN_MESSAGE_MAP和END_MESSAGE_MAP两个宏,其实宏的作用也就是给消息映射表结构体 struct AFX_MSGMAP赋值,一个是自己的消息映射表,一个是基类的消息映射表地址,这样的话就形成了一个单链表,自己的没有的就可以去链表中一层一层查找,其实这种机制用虚函数也可以实现,但是为什么不用虚函数呢,因为MFC中的继承封装的比较深,而且消息处理函数也多,如果基类都来一个虚函数处理的空响应,而我子类只需要响应一个消息处理,那每创建一个基类的对象就会有多余的虚表,虚表就很多多余的函数,这样的话开销太大,而用消息映射表的话就不会有这个问题,因为他是静态的,所有这个类型的对象都是共享着一张表,只会生成一份
消息映射表结构体 struct AFX_MSGMAP
//消息映射表结构 struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows message Windows消息 UINT nCode; // control code or WM_NOTIFY code 控制代码或WM_NOTIFY代码 UINT nID; // control ID (or 0 for windows messages) 控件ID(或Windows消息为0) UINT nLastID; // used for entries specifying a range of control id's 用于指定控件ID范围的条目 UINT_PTR nSig; // signature type (action) or pointer to message #签名类型(操作)或指向消息的指针 AFX_PMSG pfn; // routine to call (or special value) 函数指针 }; struct AFX_MSGMAP { const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//获取基类的消息映射表 const AFX_MSGMAP_ENTRY* lpEntries;//本类的消息映射表 };
下面看代码宏展开例子:我有两个类CMFCRemoteControlClientDlg和基类CDialogEx
#define DECLARE_MESSAGE_MAP() //消息映射声明 protected: static const AFX_MSGMAP* PASCAL GetThisMessageMap();//获取自己类的消息映射 virtual const AFX_MSGMAP* GetMessageMap() const; //封装了一个函数,调用就是GetThisMessageMap //实现需要三个宏进行初始化和定义函数,主要功能是把消息和消息处理函数形成一个对应的表 下面例子就是把IDC_BUTTON1消息对应&CMFCRemoteControlClientDlg::OnBnClickedButton1处理函数,这个表是AFX_MSGMAP类型结构体,保存本类的消息映射表和指向父类的消息映射 //实现的3个宏 BEGIN_MESSAGE_MAP(CMFCRemoteControlClientDlg, CDialogEx) ON_BN_CLICKED(IDC_BUTTON1, &CMFCRemoteControlClientDlg::OnBnClickedButton1) END_MESSAGE_MAP() //宏展开如下,功能主要就是给AFX_MSGMAP messageMap结构体赋值形成消息映射表 #define BEGIN_MESSAGE_MAP(CMFCRemoteControlClientDlg, CDialogEx) const AFX_MSGMAP* CMFCRemoteControlClientDlg::GetMessageMap() const { return GetThisMessageMap(); } const AFX_MSGMAP* PASCAL CMFCRemoteControlClientDlg::GetThisMessageMap() { typedef CMFCRemoteControlClientDlg ThisClass;//本类取个别名 typedef CDialogEx TheBaseClass; //基类取个别名 static const AFX_MSGMAP_ENTRY _messageEntries[] = { //定义_messageEntries ON_BN_CLICKED(IDC_BUTTON1, &CMFCRemoteControlClientDlg::OnBnClickedButton1) { WM_COMMAND, (WORD)wNotifyCode, (WORD)IDC_BUTTON1, (WORD)IDC_BUTTON1, AfxSigCmd_v, (static_cast< AFX_PMSG > (OnBnClickedButton1)) }, #define END_MESSAGE_MAP() {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } }; //初始化消息映射表,并保存父类的 static const AFX_MSGMAP messageMap = { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; return &messageMap; //返回消息映射图表 }
模拟MFC消息映射,假设我的窗口过程函数已经使用HOOK替换了原来的窗口过程函数,这时我只需要在自己的窗口过程函数中
查找自己的表去处理就可以了
CWnd.h
//处理函数声明的结构类型 enum AfxSig { AfxSig_end, AfxSig_v_v, //void(void) AfxSig_v_iii,//void(int, int, int) AfxSig_v_ii,//void(int, int) AfxSig_i_p //void(LPCREATESTRUCT) }; //消息映射表结构体 class CWnd; typedef void (CWnd::*PWNDMSG)(void); struct AFXMSG_ENTRY { UINT nMessage;//消息Id PWNDMSG pfn;//消息处理函数指针 UINT nSig;//函数声明类型 }; //函数指针联合体 union MessageFunctions { void(CWnd::*pfn_v_v)(); void(CWnd::*pfn_v_iii)(int, int, int); void(CWnd::*pfn_v_ii)(int, int); int(CWnd::*pfn_v_p)(LPCREATESTRUCT); }; //消息映射表链表结构节点 struct AFX_MSGMAP { AFXMSG_ENTRY* pEntries; AFX_MSGMAP* pBaseMsgMap; }; class CWnd : public CCmdTarget { virtual AFX_MSGMAP* GetMessagemap(); //获取消息映射表 static AFX_MSGMAP messageMap;//消息映射表结构体 static AFXMSG_ENTRY _messages[];//实际消息映射表结构体 //消息处理函数 void OnClose(); void OnDestroy(); void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags); void OnLButtonDown(UINT nX, UINT nY); void OnLButtonUp(UINT nX, UINT nY); int OnCreate(LPCREATESTRUCT lpCreateStruct); //窗口处理函数 LRESULT WindowProc( HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ); };
CWnd.cpp
AFX_MSGMAP* CWnd::GetMessagemap() { return &s_messageMap; } AFX_MSGMAP CWnd::s_messageMap = {s_messages, NULL}; AFXMSG_ENTRY CWnd::s_messages[] = { {WM_CLOSE, CWnd::OnClose, AfxSig_v_v}, {WM_DESTROY, CWnd::OnDestroy, AfxSig_v_v}, {WM_KEYDOWN, (PWNDMSG)CWnd::OnKeyDown, AfxSig_v_iii}, {WM_KEYUP, (PWNDMSG)CWnd::OnKeyUp, AfxSig_v_iii}, {WM_LBUTTONDOWN, (PWNDMSG)CWnd::OnLButtonDown, AfxSig_v_ii}, {WM_LBUTTONUP, (PWNDMSG)CWnd::OnLButtonUp, AfxSig_v_ii}, {WM_CREATE, (PWNDMSG)CWnd::OnCreate, AfxSig_i_p}, {0, (PWNDMSG)0, AfxSig_end} }; LRESULT CWnd::WindowProc( HWND hwnd, UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ) { AFX_MSGMAP* pMsgmap = GetMessagemap(); //从子类的消息映射表开始,向上逐表查询 while(pMsgmap != NULL) { // 查表 AFXMSG_ENTRY* pEntries = pMsgmap->pEntries; for(int i = 0; pEntries[i].nSig != AfxSig_end; ++i) { if(pEntries[i].nMessage == uMsg) { //函数指针的联合体,根据不同函数声明,调用不同的函数指针 MessageFunctions mmf; mmf.pfn_v_v = pEntries[i].pfn; LRESULT lResult = 0; switch(pEntries[i].nSig) { case AfxSig_v_v: { (this->*pEntries[i].pfn)(); break; } case AfxSig_v_iii: { (this->*mmf.pfn_v_iii)(wParam, LOWORD(lParam), HIWORD(lParam)); break; } case AfxSig_v_ii: { (this->*mmf.pfn_v_ii)(LOWORD(lParam), HIWORD(lParam)); break; } case AfxSig_i_p: { lResult = (this->*mmf.pfn_v_p)((LPCREATESTRUCT)lParam); break; } } return lResult; } } pMsgmap = pMsgmap->pBaseMsgMap; } //这个是用HOOK替换前的窗口过程,如果没有自己定义消息处理函数,调用原来的消息处理函数 return m_pOldProc(hwnd, uMsg, wParam, lParam); }
-
有关于MFC映射多重消息的问题
2016-04-04 16:05:44用MFC的消息映射调用消息的响应,同时调用多个消息函数,每个函数可以得到自己的调用顺序么?怎么同时调用呢? -
MFC之消息映射的实现(2)
2010-11-14 21:49:001.MFC窗口过程 前文曾经提到,所有的消息都送给窗口过程处理,MFC的所有窗口都使用同一窗口过程,消息或者直接由窗口过程调用相应的消息处理函数处理,或者按MFC命令消息派发路径送给指定的命令目标...1.MFC窗口过程
前文曾经提到,所有的消息都送给窗口过程处理,MFC的所有窗口都使用同一窗口过程,消息或者直接由窗口过程调用相应的消息处理函数处理,或者按MFC命令消息派发路径送给指定的命令目标处理。
那么,MFC的窗口过程是什么?怎么处理标准Windows消息?怎么实现命令消息的派发?这些都将是下文要回答的问题。1.MFC窗口过程的指定
从前面的讨论可知,每一个“窗口类”都有自己的窗口过程。正常情况下使用该“窗口类”创建的窗口都使用它的窗口过程。
MFC的窗口对象在创建HWND窗口时,也使用了已经注册的“窗口类”,这些“窗口类”或者使用应用程序提供的窗口过程,或者使用Windows提供的窗口过程(例如Windows控制窗口、对话框等)。那么,为什么说MFC创建的所有HWND窗口使用同一个窗口过程呢?
在MFC中,的确所有的窗口都使用同一个窗口过程:AfxWndProc或AfxWndProcBase(如果定义了_AFXDLL)。它们的原型如下:
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
LRESULT CALLBACK AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
如果动态链接到MFC DLL(定义了_AFXDLL),则AfxWndProcBase被用作窗口过程,否则AfxWndProc被用作窗口过程。AfxWndProcBase首先使用宏AFX_MANAGE_STATE设置正确的模块状态,然后调用AfxWndProc。
下面,假设不使用MFC DLL,讨论MFC如何使用AfxWndProc取代各个窗口的原窗口过程。
窗口过程的取代发生在窗口创建的过程时,使用了子类化(Subclass)的方法。所以,从窗口的创建过程来考察取代过程。从前面可以知道,窗口创建最终是通过调用CWnd::CreateEx函数完成的,分析该函数的流程,如图4-1所示。在创建窗口之前,创建了一个WH_CBT类型的钩子(Hook)。这样,创建窗口时所有的消息都会被钩子过程函数_AfxCbtFilterHook截获。
AfxCbtFilterHook函数首先检查是不是希望处理的 Hook──HCBT_CREATEWND。如果是,则先把MFC窗口对象(该对象必须已经创建了)和刚刚创建的Windows窗口对象捆绑在一起,建立它们之间的映射(见后面模块-线程状态);然后,调用::SetWindowLong设置窗口过程为AfxWndProc,并保存原窗口过程在窗口类成员变量m_pfnSuper中,这样形成一个窗口过程链。需要的时候,原窗口过程地址可以通过窗口类成员函数GetSuperWndProcAddr得到。
这样,AfxWndProc就成为CWnd或其派生类的窗口过程。不论队列消息,还是非队列消息,都送到AfxWndProc窗口过程来处理(如果使用MFC DLL,则AfxWndProcBase被调用,然后是AfxWndProc)。经过消息分发之后没有被处理的消息,将送给原窗口过程处理。
最后,有一点可能需要解释:为什么不直接指定窗口过程为AfxWndProc,而要这么大费周折呢?这是因为原窗口过程(“窗口类”指定的窗口过程)常常是必要的,是不可缺少的。
接下来,讨论AfxWndProc窗口过程如何使用消息映射数据实现消息映射。Windows消息和命令消息的处理不一样,前者没有消息分发的过程。2.对Windows消息的接收和处理
Windows消息送给AfxWndProc窗口过程之后,AfxWndProc得到HWND窗口对应的MFC窗口对象,然后,搜索该MFC窗口对象和其基类的消息映射数组,判定它们是否处理当前消息,如果是则调用对应的消息处理函数,否则,进行缺省处理。
以一个应用程序的视窗口创建时,对WM_CREATE消息的处理为例,详细地讨论Windows消息的分发过程。
类CTview要处理WM_CREATE消息,使用ClassWizard加入消息处理函数CTview::OnCreate。看这个函数怎么被调用:视窗口最终调用::CreateEx函数来创建。由Windows系统发送WM_CREATE消息给视的窗口过程AfxWndProc,参数1是创建的视窗口的句柄,参数2是消息ID(WM_CREATE),参数3、4是消息参数。首先,分析AfxWndProc窗口过程函数。
AfxWndProc的原型如下:
LRESULT AfxWndProc(HWND hWnd,UINT nMsg, WPARAM wParam, LPARAM lParam)
如果收到的消息nMsg不是WM_QUERYAFXWNDPROC(该消息被MFC内部用来确认窗口过程是否使用AfxWndProc),则从hWnd得到对应的MFC Windows对象(该对象必须已存在,是永久性<Permanent>对象)指针pWnd。pWnd所指的MFC窗口对象将负责完成消息的处理。这里,pWnd所指示的对象是MFC视窗口对象,即CTview对象。
然后,把pWnd和AfxWndProc接受的四个参数传递给函数AfxCallWndProc执行。
AfxCallWndProc原型如下:
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd,UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0)
MFC使用AfxCallWndProc函数把消息送给CWnd类或其派生类的对象。该函数主要是把消息和消息参数(nMsg、wParam、lParam)传递给MFC窗口对象的成员函数WindowProc(pWnd->WindowProc)作进一步处理。如果是WM_INITDIALOG消息,则在调用WindowProc前后要作一些处理。
WindowProc的函数原型如下:
LRESULT CWnd::WindowProc(UINT message,WPARAM wParam, LPARAM lParam)
这是一个虚拟函数,程序员可以在CWnd的派生类中覆盖它,改变MFC分发消息的方式。例如,MFC的CControlBar就覆盖了WindowProc,对某些消息作了自己的特别处理,其他消息处理由基类的WindowProc函数完成。
但是在当前例子中,当前对象的类CTview没有覆盖该函数,所以CWnd的WindowProc被调用。这个函数把下一步的工作交给OnWndMsg函数来处理。如果OnWndMsg没有处理,则交给DefWindowProc来处理。OnWndMsg和DefWindowProc都是CWnd类的虚拟函数。
OnWndMsg的原型如下:
BOOL CWnd::OnWndMsg( UINT message,WPARAM wParam, LPARAM lParam,RESULT*pResult );
和WindowProc一样,由于当前对象的类CTview没有覆盖该函数,所以CWnd的OnWndMsg被调用。
在CWnd中,MFC使用OnWndMsg来分别处理各类消息:
如果是WM_COMMAND消息,交给OnCommand处理;然后返回。
如果是WM_NOTIFY消息,交给OnNotify处理;然后返回。
如果是WM_ACTIVATE消息,先交给_AfxHandleActivate处理(后面5.3.3.7节会解释它的处理),再继续下面的处理。
如果是WM_SETCURSOR消息,先交给_AfxHandleSetCursor处理;然后返回。
如果是其他的Windows消息(包括WM_ACTIVATE),则
首先在消息缓冲池进行消息匹配,若匹配成功,则调用相应的消息处理函数;
若不成功,则在消息目标的消息映射数组中进行查找匹配,看它是否处理当前消息。这里,消息目标即CTview对象。
如果消息目标处理了该消息,则会匹配到消息处理函数,调用它进行处理;
否则,该消息没有被应用程序处理,OnWndMsg返回FALSE。3.Windows消息的查找和匹配
CWnd或者派生类的对象调用OnWndMsg搜索本对象或者基类的消息映射数组,寻找当前消息的消息处理函数。如果当前对象或者基类处理了当前消息,则必定在其中一个类的消息映射数组中匹配到当前消息的处理函数。
消息匹配是一个比较耗时的任务,为了提高效率,MFC设计了一个消息缓冲池,把要处理的消息和匹配到的消息映射条目(条目包含了消息处理函数的地址)以及进行消息处理的当前类等信息构成一条缓冲信息,放到缓冲池中。如果以后又有同样的消息需要同一个类处理,则直接从缓冲池查找到对应的消息映射条目就可以了。
MFC用哈希查找来查询消息映射缓冲池。消息缓冲池相当于一个哈希表,它是应用程序的一个全局变量,可以放512条最新用到的消息映射条目的缓冲信息,每一条缓冲信息是哈希表的一个入口。
采用AFX_MSG_CACHE结构描述每条缓冲信息,其定义如下:
struct AFX_MSG_CACHE
{
UINT nMsg;
const AFX_MSGMAP_ENTRY* lpEntry;
const AFX_MSGMAP* pMessageMap;
};
nMsg存放消息ID,每个哈希表入口有不同的nMsg。lpEnty存放和消息ID匹配的消息映射条目的地址,它可能是this所指对象的类的映射条目,也可能是这个类的某个基类的映射条目,也可能是空。
pMessageMap存放消息处理函数匹配成功时进行消息处理的当前类(this所指对象的类)的静态成员变量messageMap的地址,它唯一的标识了一个类(每个类的messageMap变量都不一样)。this所指对象是一个CWnd或其派生类的实例,是正在处理消息的MFC窗口对象。
哈希查找:使用消息ID的值作为关键值进行哈希查找,如果成功,即可从lpEntry获得消息映射条目的地址,从而得到消息处理函数及其原型。
如何判断是否成功匹配呢?有两条标准:
第一,当前要处理的消息message在哈希表(缓冲池)中有入口;第二,当前窗口对象(this所指对象)的类的静态变量messageMap的地址应该等于本条缓冲信息的pMessagMap。MFC通过虚拟函数 GetMessagMap得到messageMap的地址。
如果在消息缓冲池中没有找到匹配,则搜索当前对象的消息映射数组,看是否有合适的消息处理函数。
如果匹配到一个消息处理函数,则把匹配结果加入到消息缓冲池中,即填写该条消息对应的哈希表入口:
nMsg=message;
pMessageMap=this->GetMessageMap;
lpEntry=查找结果
然后,调用匹配到的消息处理函数。否则(没有找到),使用_GetBaseMessageMap得到基类的消息映射数组,查找和匹配;直到匹配成功或搜寻了所有的基类(到CCmdTarget)为止。如果最后没有找到,则也把该条消息的匹配结果加入到缓冲池中。和匹配成功不同的是:指定lpEntry为空。这样OnWndMsg返回,把控制权返还给AfxCallWndProc函数,AfxCallWndProc将继续调用DefWndProc进行缺省处理。消息映射数组的搜索在CCmdTarget::OnCmdMsg函数中也用到了,而且算法相同。为了提高速度,MFC把和消息映射数组条目逐一比较、匹配的函数AfxFindMessageEntry用汇编书写。
const AFX_MSGMAP_ENTRY* AFXAPIAfxFindMessageEntry(const AFX_MSGMAP_ENTRY* lpEntry,UINT nMsg, UINT nCode, UINT nID)
第一个参数是要搜索的映射数组的入口;第二个参数是Windows消息标识;第三个参数是控制通知消息标识;第四个参数是命令消息标识。
对Windows消息来说,nMsg是每条消息不同的,nID和nCode为0。
对命令消息来说,nMsg固定为WM_COMMAND,nID是每条消息不同,nCode都是CN_COMMAND(定义为0)。
对控制通知消息来说,nMsg固定为WM_COMMAND或者WM_NOTIFY,nID和nCode是每条消息不同。
对于Register消息,nMsg指定为0XC000,nID和nCode为0。在使用函数AfxFindMessageEntry得到匹配结果之后,还必须判断nSig是否等于message,只有相等才调用对应的消息处理函数。2.对命令消息的接收和处理
1.MFC标准命令消息的发送
在SDI或者MDI应用程序中,命令消息由用户界面对象(如菜单、工具条等)产生,然后送给主边框窗口。主边框窗口使用标准MFC窗口过程处理命令消息。窗口过程把命令传递给MFC主边框窗口对象,开始命令消息的分发。MFC边框窗口类CFrameWnd提供了消息分发的能力。
下面,还是通过一个例子来说明命令消息的处理过程。
使用AppWizard产生一个单文档应用程序t。从help菜单选择“About”,就会弹出一个ABOUT对话框。下面,讨论从命令消息的发出到对话框弹出的过程。
首先,选择“ About”菜单项的动作导致一个Windows命令消息ID_APP_ABOUT的产生。Windows系统发送该命令消息到边框窗口,导致它的窗口过程AfxWndProc被调用,参数1是边框窗口的句柄,参数2是消息ID(即WM_COMMAND),参数3、4是消息参数,参数3的值是 ID_APP_ABOUT。AfxWndProc根据HWND句柄得到的MFC窗口对象是MFC边框窗口对象。
如果CWnd::OnWndMsg判断要处理的消息是命令消息(WM_COMMAND),就调用OnCommand进一步处理。由于OnCommand是虚拟函数,当前MFC窗口对象是边框窗口对象,它的类从 CFrameWnd类导出,没有覆盖 CWnd的虚拟函数OnCommand,而CFrameWnd覆盖了CWnd的OnCommand,所以,CFrameWnd的OnCommand被调用。换句话说,CFrameWnd的OnCommand被调用是动态约束的结果。接着介绍的本例子的有关调用,也是通过动态约束而实际发生的函数调用。
(1)CFrameWnd的OnCommand函数
BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
参数wParam的低阶word存放了菜单命令nID或控制子窗口ID;如果消息来自控制窗口,高阶word存放了控制通知消息;如果消息来自加速键,高阶word值为1;如果消息来自菜单,高阶word值为0。如果是通知消息,参数lParam存放了控制窗口的句柄hWndCtrl,其他情况下lParam是0。
在这个例子里,低阶word是ID_APP_ABOUT,高阶word是1;lParam是0。
MFC对CFrameWnd的缺省实现主要是获得一个机会来检查程序是否运行在HELP状态,需要执行上下文帮助,如果不需要,则调用基类的CWnd::OnCommand实现正常的命令消息发送。
(2)CWnd的OnCommand函数
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
它按一定的顺序处理命令或者通知消息,如果发送成功,返回TRUE,否则,FALSE。处理顺序如下:
如果是命令消息,则调用OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL)测试nID命令是否已经被禁止,如果这样,返回FALSE;否则,调用OnCmdMsg进行命令发送。关于CN_UPDATE_COMMAND_UI通知消息,见后面用户界面状态的更新处理。
如果是控制通知消息,则先用ReflectLastMsg反射通知消息到子窗口。如果子窗口处理了该消息,则返回 TRUE;否则,调用OnCmdMsg进行命令发送。关于通知消息的反射见后面4.4.4.3节。OnCommand给OnCmdMsg传递四个参数:nID,即命令消息ID;nCode,如果是通知消息则为通知代码,如果是命令消息则为NC_COMMAND(即0);其余两个参数为空。
(3)CFrameWnd的OnCmdMsg函数
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
参数1是命令ID;如果是通知消息(WM_COMMAND或者WM_NOTIFY),则参数2表示通知代码,如果是命令消息,参数2是0;如果是WM_NOTIFY,参数3包含了一些额外的信息;参数4在正常消息处理中应该是空。在这个例子里,参数1是命令ID,参数2为0,参数3空。
OnCmdMsg是虚拟函数,CFrameWnd覆盖了该函数,当前对象(this所指)是MFC单文档的边框窗口对象。故CFrameWnd的OnCmdMsg被调用。CFrameWnd::OnCmdMsg在MFC消息发送中占有非常重要的地位,MFC对该函数的缺省实现确定了MFC的标准命令发送路径:
1. 送给活动(Active)视处理,调用活动视的OnCmdMsg。由于当前对象是MFC视对象,所以,OnCmdMsg将搜索CTview及其基类的消息映射数组,试图得到相应的处理函数。
2. 如果视对象自己不处理,则视得到和它关联的文档,调用关联文档的OnCmdMsg。由于当前对象是MFC视对象,所以,OnCmdMsg将搜索CTdoc及其基类的消息映射数组,试图得到相应的处理函数。
3. 如果文档对象不处理,则它得到管理文档的文档模板对象,调用文档模板的OnCmdMsg。由于当前对象是MFC文档模板对象,所以,OnCmdMsg将搜索文档模板类及其基类的消息映射数组,试图得到相应的处理函数。
4. 如果文档模板不处理,则把没有处理的信息逐级返回:文档模板告诉文档对象,文档对象告诉视对象,视对象告诉边框窗口对象。最后,边框窗口得知,视、文档、文档模板都没有处理消息。
5. CFrameWnd的OnCmdMsg继续调用CWnd::OnCmdMsg(斜体表示有类属限制)来处理消息。由于CWnd没有覆盖OnCmdMsg,故实际上调用了函数CCmdTarget::OnCmdMsg。由于当前对象是MFC边框窗口对象,所以 OnCmdMsg函数将搜索CMainFrame类及其所有基类的消息映射数组,试图得到相应的处理函数。CWnd没有实现OnCmdMsg却指定要执行其OnCmdMsg函数,可能是为了以后MFC给CWnd实现了OnCmdMsg之后其他代码不用改变。
这一步是边框窗口自己尝试处理消息。
2. 如果边框窗口对象不处理,则送给应用程序对象处理。调用CTApp的OnCmdMsg,由于实际上CTApp 及其基类CWinApp没有覆盖OnCmdMsg,故实际上调用了函数CCmdTarget::OnCmdMsg。由于当前对象是MFC应用程序对象,所以OnCmdMsg函数将搜索CTApp类及其所有基类的的消息映射入口数组,试图得到相应的处理函数
3. 如果应用程序对象不处理,则返回FALSE,表明没有命令目标处理当前的命令消息。这样,函数逐级别返回,OnCmdMsg告诉 OnCommand消息没有被处理,OnCommand告诉OnWndMsg消息没有被处理,OnWndMsg告诉WindowProc消息没有被处理,于是WindowProc调用DefWindowProc进行缺省处理。
应用程序对ID_APP_ABOUT消息作了处理。它找到处理函数CTApp::OnAbout,使用DispatchCmdMsg派发消息给该函数处理。CCmdTarget的静态成员函数DispatchCmdMsg用来派发命令消息给指定的命令目标的消息处理函数。
static BOOL DispatchCmdMsg(CCmdTarget* pTarget,
UINT nID, int nCode,
AFX_PMSG pfn, void* pExtra, UINT nSig,
AFX_CMDHANDLERINFO* pHandlerInfo)3.对控制通知消息的接收和处理
1.WM_COMMAND控制通知消息的处理
WM_COMMAND控制通知消息的处理和WM_COMMAND命令消息的处理类似,但是也有不同之处。
首先,分析处理WM_COMMAND控制通知消息和命令消息的相似处。如前所述,命令消息和控制通知消息都是由窗口过程给OnCommand处理(参见CWnd::OnWndMsg的实现),OnCommand通过wParam和lParam参数区分是命令消息或通知消息,然后送给OnCmdMsg处理(参见CWnd::OnCommnd的实现)。
其次,两者的不同之处是:
* 命令消息一般是送给主边框窗口的,这时,边框窗口的OnCmdMsg被调用;而控制通知消息送给控制子窗口的父窗口,这时,父窗口的OnCmdMsg被调用。
* OnCmdMsg处理命令消息时,通过命令分发可以由多种命令目标处理,包括非窗口对象如文档对象等;而处理控制通知消息时,不会有消息分发的过程,控制通知消息最终肯定是由窗口对象处理的。
不过,在某种程度上可以说,控制通知消息由窗口对象处理是一种习惯和约定。当使用ClassWizard进行消息映射时,它不提供把控制通知消息映射到非窗口对象的机会。但是,手工地添加消息映射,让非窗口对象处理控制通知消息的可能是存在的。例如,对于 CFormView,一方面它具备接受WM_COMMAND通知消息的条件,另一方面,具备把WM_COMMAND消息派发给关联文档对象处理的能力,所以给CFormView的通知消息是可以让文档对象处理的。
事实上,BN_CLICKED控制通知消息的处理和命令消息的处理完全一样,因为该消息的通知代码是0,ON_BN_CLICKED(id,memberfunction)和ON_COMMAND(id,memberfunction)是等同的。此外,MFC的状态更新处理机制就是建立在通知消息可以发送给各种命令目标的基础之上的。关于MFC的状态更新处理机制,见后面4.4.4.4节的讨论。
* 控制通知消息可以反射给子窗口处理。OnCommand判定当前消息是WM_COMAND通知消息之后,首先它把消息反射给控制子窗口处理,如果子窗口处理了反射消息,OnCommand不会继续调用OnCmdMsg让父窗口对象来处理通知消息2.WM_NOTIFY消息及其处理
(1)WM_NOTIFY消息
还有一种通知消息WM_NOTIFY,在Win32中用来传递信息复杂的通知消息。WM_NOTIFY消息怎么来传递复杂的信息呢?WM_NOTIFY的消息参数wParam包含了发送通知消息的控制窗口ID,另一个参数lParam包含了一个指针。该指针指向一个 NMHDR结构,或者更大的结构,只要它的第一个结构成员是NMHDR结构。
NMHDR结构:
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
上述结构有三个成员,分别是发送通知消息的控制窗口的句柄、ID和通知消息代码。
举一个更大、更复杂的结构例子:列表控制窗发送LVN_KEYDOWN控制通知消息,则lParam包含了一个指向LV_KEYDOWN结构的指针。其结构如下:
typedef struct tagLV_KEYDOWN {
NMHDR hdr;
WORD wVKey;
UINT flags;
}LV_KEYDOWN;
它的第一个结构成员hdr就是NMHDR类型。其他成员包含了更多的信息:哪个键被按下,哪些辅助键(SHIFT、CTRL、ALT等)被按下。
(2)WM_NOTIFY消息的处理
在分析CWnd::OnWndMsg函数时,曾指出当消息是WM_NOTIFY时,它把消息传递给OnNotify虚拟函数处理。这是一个虚拟函数,类似于OnCommand,CWnd和派生类都可以覆盖该函数。OnNotify的函数原型如下:
BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
参数1是发送通知消息的控制窗口ID,没有被使用;参数2是一个指针;参数3指向一个long类型的数据,用来返回处理结果。
WM_NOTIFY消息的处理过程如下:
1. 反射消息给控制子窗口处理。
2. 如果子窗口不处理反射消息,则交给OnCmdMsg处理。给OnCmdMsg的四个参数分别如下:第一个是命令消息ID,第四个为空;第二个高阶word是WM_NOTIFY,低阶word是通知消息;第三个参数是指向AFX_NOTIFY结构的指针。第二、三个参数有别于 OnCommand送给OnCmdMsg的参数。
AFX_NOTIFY结构:
struct AFX_NOTIFY
{
LRESULT* pResult;
NMHDR* pNMHDR;
};
pNMHDR的值来源于参数2 lParam,该结构的域pResult用来保存处理结果,域pNMHDR用来传递信息。
OnCmdMsg后续的处理和WM_COMMAND通知消息基本相同,只是在派发消息给消息处理函数时,DispatchMsdMsg的第五个参数pExtra指向OnCmdMsg传递给它的AFX_NOTIFY类型的参数,而不是空指针。这样,处理函数就得到了复杂的通知消息信息。 -
MFC学习小记(1) MFC的入口点与消息循环,消息映射
2017-02-20 21:26:17以前一直用的Qt,最近找工作后,基本就定在windows平台...MFC隐藏了windows程序的入口点winMain,其实是在appmodule.cpp文件下,该入口点调用MFC的全局函数AfxWinMain作为MFC的入口点 然后AfxWinMain会进行一些初以前一直用的Qt,最近找工作后,基本就定在windows平台上了,无聊之中研究了下以前没怎么学的MFC。现在看看没有以前那么深奥了。
1.MFC的入口点与简单的执行过程
MFC隐藏了windows程序的入口点winMain,其实是在appmodule.cpp文件下,该入口点调用MFC的全局函数AfxWinMain作为MFC的入口点
然后AfxWinMain会进行一些初始化操作,并执行全局CWinApp的InitInstance函数,即我们重写的一个虚函数
在CWinApp函数中,会初始化我们的窗口指针,调用了该窗口的构造函数,在构造函数中会有一个Create函数,该函数会注册一个窗口类,但是还没有类名和窗口过程,接着会执行一个PreCreateWindow的函数,处理好后,会给一个类名和一个DefWindowProc,并注册好这个窗口类,紧接着,会执行AfxHookWindowCreate函数,作用是将该窗口的窗口过程变为AfxWindowProc,即为一个全局的窗口过程函数,即MFC下所有窗口都共享一个窗口过程。窗口类注册好后,就用CreateWindow的api函数创建了一个窗口,窗口的创建就完成了。
这是由入口点大概的执行流程,中间也忽略了不少函数,但是差不多就是windows sdk写程序的大概流程了,注册窗口类之类的全在里面了。
最主要的还是窗口的消息循环,由上述步骤可以发现,MFC的窗口的WindowProc是一个函数,即全局的AfxWindowProc,接下来记录下MFC的消息流向即消息映射。
- AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
- {
- // special message which identifies the window as using AfxWndProc
- if (nMsg == WM_QUERYAFXWNDPROC)
- return 1;
- // all other messages route through message map
- CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
- ASSERT(pWnd != NULL);
- ASSERT(pWnd==NULL || pWnd->m_hWnd == hWnd);
- if (pWnd == NULL || pWnd->m_hWnd != hWnd)
- return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
- return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
- }
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) { // special message which identifies the window as using AfxWndProc if (nMsg == WM_QUERYAFXWNDPROC) return 1; // all other messages route through message map CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); ASSERT(pWnd != NULL); ASSERT(pWnd==NULL || pWnd->m_hWnd == hWnd); if (pWnd == NULL || pWnd->m_hWnd != hWnd) return ::DefWindowProc(hWnd, nMsg, wParam, lParam); return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam); }
这是MFC窗体所共有的窗口过程,即任何窗体接受到的消息,最终都会流向这里,首先 该函数会由消息的接受句柄来得到它的窗体指针,接着会调用AfxCallWndProc函数.
- LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
- WPARAM wParam = 0, LPARAM lParam = 0)
- {
- _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
- MSG oldState = pThreadState->m_lastSentMsg; // save for nesting
- pThreadState->m_lastSentMsg.hwnd = hWnd;
- pThreadState->m_lastSentMsg.message = nMsg;
- pThreadState->m_lastSentMsg.wParam = wParam;
- pThreadState->m_lastSentMsg.lParam = lParam;
- #ifdef _DEBUG
- _AfxTraceMsg(_T("WndProc"), &pThreadState->m_lastSentMsg);
- #endif
- // Catch exceptions thrown outside the scope of a callback
- // in debug builds and warn the user.
- LRESULT lResult;
- TRY
- {
- #ifndef _AFX_NO_OCC_SUPPORT
- // special case for WM_DESTROY
- if ((nMsg == WM_DESTROY) && (pWnd->m_pCtrlCont != NULL))
- pWnd->m_pCtrlCont->OnUIActivate(NULL);
- #endif
- // special case for WM_INITDIALOG
- CRect rectOld;
- DWORD dwStyle = 0;
- if (nMsg == WM_INITDIALOG)
- _AfxPreInitDialog(pWnd, &rectOld, &dwStyle);
- // delegate to object's WindowProc
- lResult = pWnd->WindowProc(nMsg, wParam, lParam);
- // more special case for WM_INITDIALOG
- if (nMsg == WM_INITDIALOG)
- _AfxPostInitDialog(pWnd, rectOld, dwStyle);
- }
- CATCH_ALL(e)
- {
- lResult = AfxProcessWndProcException(e, &pThreadState->m_lastSentMsg);
- TRACE(traceAppMsg, 0, "Warning: Uncaught exception in WindowProc (returning %ld).\n",
- lResult);
- DELETE_EXCEPTION(e);
- }
- END_CATCH_ALL
- pThreadState->m_lastSentMsg = oldState;
- return lResult;
- }
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg, WPARAM wParam = 0, LPARAM lParam = 0) { _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); MSG oldState = pThreadState->m_lastSentMsg; // save for nesting pThreadState->m_lastSentMsg.hwnd = hWnd; pThreadState->m_lastSentMsg.message = nMsg; pThreadState->m_lastSentMsg.wParam = wParam; pThreadState->m_lastSentMsg.lParam = lParam; #ifdef _DEBUG _AfxTraceMsg(_T("WndProc"), &pThreadState->m_lastSentMsg); #endif // Catch exceptions thrown outside the scope of a callback // in debug builds and warn the user. LRESULT lResult; TRY { #ifndef _AFX_NO_OCC_SUPPORT // special case for WM_DESTROY if ((nMsg == WM_DESTROY) && (pWnd->m_pCtrlCont != NULL)) pWnd->m_pCtrlCont->OnUIActivate(NULL); #endif // special case for WM_INITDIALOG CRect rectOld; DWORD dwStyle = 0; if (nMsg == WM_INITDIALOG) _AfxPreInitDialog(pWnd, &rectOld, &dwStyle); // delegate to object's WindowProc lResult = pWnd->WindowProc(nMsg, wParam, lParam); // more special case for WM_INITDIALOG if (nMsg == WM_INITDIALOG) _AfxPostInitDialog(pWnd, rectOld, dwStyle); } CATCH_ALL(e) { lResult = AfxProcessWndProcException(e, &pThreadState->m_lastSentMsg); TRACE(traceAppMsg, 0, "Warning: Uncaught exception in WindowProc (returning %ld).\n", lResult); DELETE_EXCEPTION(e); } END_CATCH_ALL pThreadState->m_lastSentMsg = oldState; return lResult; }
最主要的是lResult = pWnd->WindowProc(nMsg, wParam, lParam)这句,将会执行由消息接收句柄对应窗体的窗口过程函数。- LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
- {
- // OnWndMsg does most of the work, except for DefWindowProc call
- LRESULT lResult = 0;
- if (!OnWndMsg(message, wParam, lParam, &lResult))
- lResult = DefWindowProc(message, wParam, lParam);
- return lResult;
- }
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // OnWndMsg does most of the work, except for DefWindowProc call LRESULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult)) lResult = DefWindowProc(message, wParam, lParam); return lResult; }
在这里又被转发到该窗体的OnWndMsg函数内。该函数很长,实现在wincore.cpp内,由此我们可以所有的MFC窗体共享一个窗口过程,即AfxWindowProc,然后由这个中转站根据接收句柄来得到需要得到这个消息的对象,然后执行该对象内的窗口过程。
OnWndMsg又是MFC消息映射的实现函数,首先我们得看下消息映射的一系列宏定义。
- #define DECLARE_MESSAGE_MAP() \
- protected: \
- static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
- virtual const AFX_MSGMAP* GetMessageMap() const; \
#define DECLARE_MESSAGE_MAP() \ protected: \ static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \
- struct AFX_MSGMAP
- {
- <span style="white-space:pre"> </span>const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
- <span style="white-space:pre"> </span>const AFX_MSGMAP_ENTRY* lpEntries;
- };
struct AFX_MSGMAP { const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); const AFX_MSGMAP_ENTRY* lpEntries; };
- struct AFX_MSGMAP_ENTRY
- {
- <span style="white-space:pre"> </span>UINT nMessage; // windows message
- <span style="white-space:pre"> </span>UINT nCode; // control code or WM_NOTIFY code
- <span style="white-space:pre"> </span>UINT nID; // control ID (or 0 for windows messages)
- <span style="white-space:pre"> </span>UINT nLastID; // used for entries specifying a range of control id's
- <span style="white-space:pre"> </span>UINT_PTR nSig; // signature type (action) or pointer to message #
- <span style="white-space:pre"> </span>AFX_PMSG pfn; // routine to call (or special value)
- };
struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows message UINT nCode; // control code or WM_NOTIFY code UINT nID; // control ID (or 0 for windows messages) UINT nLastID; // used for entries specifying a range of control id's UINT_PTR nSig; // signature type (action) or pointer to message # AFX_PMSG pfn; // routine to call (or special value) };
这是在定义一个支持消息映射的类必须要写的宏定义。可见这个宏展开就是声明了两个protected权限的函数,一个为静态函数,一个为虚函数。其中两者返回的都是AFX_MSGMAP的结构体,结构体成员为1个函数指针和一个结构体,这个结构体保存了消息与对应执行函数的对应关系。要使用一个消息循环,得添加一个宏。
- #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
- PTM_WARNING_DISABLE \
- const AFX_MSGMAP* theClass::GetMessageMap() const \
- { return GetThisMessageMap(); } \
- const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
- { \
- typedef theClass ThisClass; \
- typedef baseClass TheBaseClass; \
- static const AFX_MSGMAP_ENTRY _messageEntries[] = \
- {
- #define END_MESSAGE_MAP() \
- {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
- }; \
- static const AFX_MSGMAP messageMap = \
- { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
- return &messageMap; \
- } \
- PTM_WARNING_RESTORE
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \ PTM_WARNING_DISABLE \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return GetThisMessageMap(); } \ const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \ { \ typedef theClass ThisClass; \ typedef baseClass TheBaseClass; \ static const AFX_MSGMAP_ENTRY _messageEntries[] = \ { #define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \ static const AFX_MSGMAP messageMap = \ { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \ return &messageMap; \ } \ PTM_WARNING_RESTORE
首先是BEGIN_MESSAGE_MAP这个宏,由于得对照这来,得喝END_MESSAGE_MAP()一起来看。这两个宏的主要目的就是为了实现在类声明时候加入的DECLARE_MESSAGEMAP这个宏的两个函数。实现的第一个虚函数即为调用了这个类的GetThisMessageMap这个静态函数。下面来看这个静态函数的实现,在这个静态函数中有一个静态的数组,该数组保存着消息对应执行的消息函数,在譬如ON_WM_LBUTTONDOWN这些宏的帮助下把AFX_MSGMAP_ENTRY这个结构初始化。
举个例子,ON_WM_LBUTTONDOWN展开是
- #define ON_WM_LBUTTONDOWN() \
- { WM_LBUTTONDOWN, 0, 0, 0, AfxSig_vwp, \
- (AFX_PMSG)(AFX_PMSGW) \
- (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > ( &ThisClass :: OnLButtonDown)) },
#define ON_WM_LBUTTONDOWN() \ { WM_LBUTTONDOWN, 0, 0, 0, AfxSig_vwp, \ (AFX_PMSG)(AFX_PMSGW) \ (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > ( &ThisClass :: OnLButtonDown)) },
即将WM_LBUTTONDOWN这个消息和这个类想要对应的OnLButtonDown这个函数绑定在一起了,消息映射表中就存在着了一个对应关系。其中的AfxSig代表了不同类型的消息。其中将派生类的OnLButton进行了三次强转,第一次是转为CWnd成员函数指针类型,参数不变,第二次转为了无参版本,第三次转为了CCmdTarget无参版本,无论怎么转换,它都是个成员函数,始终是派生类的成员函数地址。而AfxSig会记录下具体的参数,在OnWndMsg中会解析出来并还原执行。当住消息循环收到WM_LBUTTONDOWN这个消息时,会根据接收的句柄来调用本窗口对应的窗口过程,即OnWndMsg这个函数,当是下面子控件的通知消息时,就交给自身的OnCommand等函数处理。
- if (message == WM_COMMAND)
- {
- if (OnCommand(wParam, lParam))
- {
- lResult = 1;
- goto LReturnTrue;
- }
- return FALSE;
- }
- // special case for notifies
- if (message == WM_NOTIFY)
- {
- NMHDR* pNMHDR = (NMHDR*)lParam;
- if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
- goto LReturnTrue;
- return FALSE;
- }
if (message == WM_COMMAND) { if (OnCommand(wParam, lParam)) { lResult = 1; goto LReturnTrue; } return FALSE; } // special case for notifies if (message == WM_NOTIFY) { NMHDR* pNMHDR = (NMHDR*)lParam; if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult)) goto LReturnTrue; return FALSE; }
在消息映射表中,指定了特定的消息类型,LButtonDown最终会被此处执行。
- case AfxSig_v_u_p:
- {
- CPoint point(lParam);
- (this->*mmf.pfn_v_u_p)(static_cast<UINT>(wParam), point);
- }
- break;
case AfxSig_v_u_p: { CPoint point(lParam); (this->*mmf.pfn_v_u_p)(static_cast<UINT>(wParam), point); } break;
最终执行到了重写的OnLButtonDown这个函数。union MessageMapFunctions是一个联合体,大小为一个指针大小,存放着一个函数指针,也就是对应的消息响应函数(这里很特别,联合体的定义是所有的消息响应函数,直接根据sig可以获得具体的参数及返回值后无需强转就执行到了那个函数)。假如直接用WM_消息的话,还得每次都强转为相应的指针,而这里只需要依据特定的sig就能执行。 -
请教C++ 6.0里关于消息处理一对多的问题
2016-04-04 10:39:04C++ 6.0里同一个消息怎么能调用多个类里面的函数,可以消息映射支持映射一对多消息么? -
在WM_PAINT处理函数OnPaint()中未构造CPaintDC对象造成死循环
2012-12-29 20:22:46在程序中动态创建了一个基类为CWnd的窗口,由于VC不知怎么回事添加不了消息处理,所以就自已增加了WM_PAINT消息映射,悲剧发生了,一运行就死循环,调试知不断的调用了OnPaint()函数,把OnPaint()函数缩减就几行代码...在程序中动态创建了一个基类为CWnd的窗口,由于VC不知怎么回事添加不了消息处理,所以就自已增加了WM_PAINT消息映射,悲剧发生了,一运行就死循环,调试知不断的调用了OnPaint()函数,把OnPaint()函数缩减就几行代码还是看不出有什么漏洞的。
这个问题检查了一早上差点无果,突然想起来以前在用C写windows程序的时候,处理WM_PAINT消息时有beginpaint() 和endpaint()的调用,作用是通知程序无效区已经有效等,如果不调用系统将不断的发送WM_PAINt消息,回来代码一眼看到第一行代码:CClientDC dc(this),,暗自高兴了一下,原来是我构造错对象了,应该构造CPaintDC对象,这个对象在构造函数中会调用beginpaint,在析构的时候会调用endpaint,这样无效区就会恢复有效。而CClientDC没有这个功能。
有时越是简单的问题获是不容易查出啊。
-
Windows SDK窗口创建和消息机制理解
2019-06-25 23:34:32Windows是由消息驱动的操作系统,消息类似于发一条指令,操作系统收到这条指令调用自己的窗口过程函数去处理这条指令, 事件:点击鼠标左键,右键,输入键盘都是一系列动作称为事件 消息:事件的映射(把事件封装... -
VC++七种延时方式
2009-08-01 01:37:00今天我要做微机接口与技术的试验,需要延时,一时想不起来,因此想看看VC++怎么... VC++七种延时方式 方式一:VC中的WM_TIMER消息映射能进行简单的时间控制。首先调用函数SetTimer()设置定时间隔,如SetTimer(0,200, -
Visual C++程序员实用大全(精华版).(水利水电.邓劲生.张晓明译).part3
2016-06-21 20:50:3940 理解声明:在函数调用中声明一个变量 41 理解声明:函数的原型化 42 理解#define编译器指令:定义常数 43 理解#define编译器指令:定义宏 44 理解存储类变量:自动变量 45 理解存储类变量:寄存器变量 46 理解... -
MFC深入浅出带目录完整版(李久进chm版)
2011-02-28 17:59:00并揭示了MFC通过消息映射手段实现C++虚拟函数机制的原理。 第五章和第六章,分析MFC编程框架启动和关闭一个应用程序的过程,揭示MFC框架的内幕,剖析以文档模板为核心创建基于文档-视的应用程序的过程,展示MFC框架... -
Visual C++程序员实用大全(精华版).(水利水电.邓劲生.张晓明译).part4
2016-06-21 21:13:2718 深入程序流:理解调用函数 19 理解程序的结构 20 理解C/C++中的函数 21 在函数中书写可重用代码 22 使用库函数 第三章 C编程语言 23 C/C++中的数据类型 24 定义自己的数据类型 25 理解运算符 26 赋值运算符 27 ... -
Visual C++程序员实用大全(精华版).(水利水电.邓劲生.张晓明译).part1
2016-06-21 21:05:5418 深入程序流:理解调用函数 19 理解程序的结构 20 理解C/C++中的函数 21 在函数中书写可重用代码 22 使用库函数 第三章 C编程语言 23 C/C++中的数据类型 24 定义自己的数据类型 25 理解运算符 26 赋值运算符 27 ... -
Visual C++程序员实用大全(精华版).(水利水电.邓劲生.张晓明译).part2
2016-06-21 21:09:5418 深入程序流:理解调用函数 19 理解程序的结构 20 理解C/C++中的函数 21 在函数中书写可重用代码 22 使用库函数 第三章 C编程语言 23 C/C++中的数据类型 24 定义自己的数据类型 25 理解运算符 26 赋值运算符 27 ... -
Windows 程序设计(第5版)(上、下册)--详细书签版
2012-04-22 18:40:0411.3.5 只调用一个函数的windows程序 第十二章 剪贴板 12.1 剪贴板的简单使用 12.1.1 标准剪贴板数据格式 12.1.2 内存分配 12.1.3 将文本传送到剪贴板 12.1.4 从剪贴板上获取文本 12.1.5 打开和... -
Windows 程序设计(第5版)(上、下册)--源代码
2012-04-22 19:21:4511.3.5 只调用一个函数的windows程序 第十二章 剪贴板 12.1 剪贴板的简单使用 12.1.1 标准剪贴板数据格式 12.1.2 内存分配 12.1.3 将文本传送到剪贴板 12.1.4 从剪贴板上获取文本 12.1.5 打开和... -
VC之美化界面篇本文专题讨论VC中的界面美化,适用于具有中等VC水平的读者。读者最好具有以下VC基础:
2009-06-17 10:17:322. 熟悉Windows消息机制,熟悉MFC的消息映射和反射机制; 3. 熟悉OOP理论和技术; 本文根据笔者多年的开发经验,并结合简单的例子一一展开,希望对读者有所帮助。 1 美化界面之开题篇 相信使用过《金山毒霸》、... -
你必须知道的495个C语言问题
2015-10-16 14:14:284.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 4.13 通用指针类型是什么?当我把函数指针赋向void*类型的时候,编译通不过。 4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针变量中,... -
你必须知道的495个C语言问题(高清版)
2010-03-31 16:24:094.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 50 4.13 通用指针类型是什么?当我把函数指针赋向void *类型的时候,编译通不过。 51 4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针... -
《你必须知道的495个C语言问题》
2010-03-20 16:41:184.12 我看到了用指针调用函数的不同语法形式。到底怎么回事? 50 4.13 通用指针类型是什么?当我把函数指针赋向void *类型的时候,编译通不过。 51 4.14 怎样在整型和指针之间进行转换?能否暂时把整数放入指针... -
Visual C++ 2008入门经典--详细书签版
2013-02-02 16:07:155.4 递归函数调用 233 5.5 C++/CLI编程 235 5.5.1 接受数量可变实参的函数 236 5.5.2 main( )的实参 237 5.6 小结 238 5.7 练习 238 第6章 程序结构(2) 240 6.1 函数指针 240 6.1.1 声明函数指针 241 6.1.2... -
Visual C++ 2008入门经典--源代码及课后练习答案
2013-02-02 16:13:255.4 递归函数调用 233 5.5 C++/CLI编程 235 5.5.1 接受数量可变实参的函数 236 5.5.2 main( )的实参 237 5.6 小结 238 5.7 练习 238 第6章 程序结构(2) 240 6.1 函数指针 240 6.1.1 声明函数指针 241 6.1.2... -
Windows.环境下32位汇编语言程序设计(第2版)扫描版带书签2/2
2012-10-29 08:48:574.1.3 窗口程序是怎么工作的 92 4.2分析窗口程序 99 4.2.1 模块和句柄 99 4.2.2 创建窗口 101 4.2.3 消息循环 107 4.2.4 窗口过程 109 4.3窗口间的通信 114 4.3.1 窗口间的消息互发 114 4.3.2 在窗口间传递数据 117 ... -
Windows.环境下32位汇编语言程序设计(第2版)扫描版带书签 1/2
2012-10-29 08:43:104.1.3 窗口程序是怎么工作的 92 4.2分析窗口程序 99 4.2.1 模块和句柄 99 4.2.2 创建窗口 101 4.2.3 消息循环 107 4.2.4 窗口过程 109 4.3窗口间的通信 114 4.3.1 窗口间的消息互发 114 4.3.2 在窗口间传递数据 117 ... -
error C2039: “setWindowText”: 不是“ATL::CStringT,StringTraits>”的成员
2019-08-11 10:02:37// 生成的消息映射函数 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() ... -
Win32位汇编语言程序设计(第2版)完整高清版(共四部分)第四部分
2009-05-22 17:04:264.1.3 窗口程序是怎么工作的 92 4.2 分析窗口程序 99 4.2.1 模块和句柄 99 4.2.2 创建窗口 101 4.2.3 消息循环 107 4.2.4 窗口过程 109 4.3 窗口间的通信 114 4.3.1 窗口间的消息互发 114 4.3.2 在窗口间传递数据 ... -
vc++ 应用源码包_1
2012-09-15 14:22:12代码里用了备份dll的方法,因此在自定义的函数中可以直接调用在内存中备份的dll代码,而不需要再把函数头部改来改去。 IOCP反弹远控客户端模型,外加上线服务端,全部代码注释! 如题。这个是IOCP远程控制软件的... -
vc++ 应用源码包_2
2012-09-15 14:27:40代码里用了备份dll的方法,因此在自定义的函数中可以直接调用在内存中备份的dll代码,而不需要再把函数头部改来改去。 IOCP反弹远控客户端模型,外加上线服务端,全部代码注释! 如题。这个是IOCP远程控制软件的...
-
JMETER 性能测试基础课程
-
基于Qt的LibVLC开发教程
-
MySQL Router 实现高可用、负载均衡、读写分离
-
【考研初试】安徽建筑大学903流体力学考研真题库资料
-
龙芯生态应用开发基础:C语言精要
-
linux老男孩 基础部分章节05-----系统优化操作说明
-
这里是最近重新学习Spring boot关于注入相关的经验博客,特此记录一下:
-
日本三菱公司FX系列PLC通信调试工具(含源代码)
-
jn82901336.github.io-源码
-
Docker从入门到精通
-
DHCP 动态主机配置服务(在Linux环境下,配置单网段或跨网段提)
-
Java讲座-源码
-
NFS 实现高可用(DRBD + heartbeat)
-
【考研初试】安徽建筑大学702公共管理学考研真题库资料
-
自动化测试Python3+Selenium3+Unittest
-
2014阿里巴巴校园招聘数据分析师职位笔试题目(回忆版).pdf
-
SQL入门之MySQL
-
MySQL 事务和锁
-
MySQL 高可用工具 DRBD 实战部署详解
-
公共信用信息分类与编码规范(试行).pdf