本文描述了ON_COMMAND_RANGE多个按钮响应一个函数的解决方法。
开发人员需要注意在自定义消息响应函数的声明过程中,一定要注意参数的形式,稍微一疏忽就会导致莫须有的错误,具体以ON_COMMAND_RANGE为例说下。
1.声明消息响应函数:在要添加的工程上添加函数afx_msg void OnButtonPort();
2.消息映射:
1
2
3
4
5
6
7
8
9
10
11
|
BEGIN_MESSAGE_MAP(CXXXDlg, CDialog) //{{AFX_MSG_MAP(CXXXDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_TIMER() //}}AFX_MSG_MAP //这里的IDC_BUTTON_PORT_1和 IDC_BUTTON_START_ALL之间有很多个Button,并且ID连续 ON_COMMAND_RANGE(IDC_BUTTON_PORT_1, IDC_BUTTON_START_ALL, OnButtonPort) ON_WM_DEVICECHANGE() END_MESSAGE_MAP() |
3.映射函数的实现:实现你自己的响应函数 void CXXXDlg::OnButtonPort()
注:此代码DEBUG OK,Relase异常,不可直接参考,且听下面分解:
DEBUG通过,不料Release却直接崩溃,写了这么多年的CODE还真第一次遇到这种情况,为什么ON_COMMAND_RANGE Debug正常,Release不正常呢?
先MSDN:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Use this macro to map a contiguous range of command IDs to a single message handler function. ON_COMMAND_RANGE(id1, id2, memberFxn ) Parameters id1 Command ID at the beginning of a contiguous range of command IDs. id2 Command ID at the end of a contiguous range of command IDs. memberFxn The name of the message-handler function to which the commands are mapped. Remarks The range of IDs starts with id1 and ends with id2. Use ON_COMMAND_RANGE to map a range of command IDs to one member function. Use ON_COMMAND to map a single command to a member function. Only one message-map entry can match a given command ID. That is, you can't map a command to more than one handler. For more information on mapping message ranges, see Handlers for Message-Map Ranges. There is no automatic support for message map ranges, so you must place the macro yourself. |
MSDN也没有特别说明要注意什么的,我觉得我用的也很正常,于是在网上又搜了一大会,有一个网友非常专业的解释的原因,具体网址是:http://yiyunscu.blog.163.com/blog/static/3626332020099802057982/
以下是转载内容:
该网友定义如下:
1
|
afx_msg void OnCommandMy( WPARAM wParam, LPARAM lParam ); |
申明只适用于ON_COMMAND消息的函数申明, 而ON_COMMAND_RANGE的函数申明在MSDN中建议写成这样:
OnCommandMy(UINT nID);
通过switch(nID) case **:进行针对不同菜单进行消息响应.
nID就是菜单传入消息的ID号, 奇怪的是, 在Debug版本下, 先前的申明方式运行完全正常, 查阅了MSDN, 找出了可能的原因:
1
|
Handler functions for single commands normally take no parameters. With the exception of update handler functions, handler functions for message-map ranges require an extra parameter, nID, of type UINT. This parameter is the first parameter. The extra parameter accommodates the extra command ID needed to specify which command the user actually chose. |
针对单个Command消息响应函数可以不带参数, 但是对于多个Command消息如ON_COMMAND_RANGE申明的消息响应需要将函数参数列表中的第一个参数定义为UINT nID, 指明command 的ID号, 按照MSDN的理解, ON_COMMAND_RANGE也可以像ON_COMMAND那样在消息响应函数中定义两个参数, 如afx_msg void OnCommandMy(WPARAM wParam, LPARAM lParam );在Debug和Release下, 编译不会出现问题, 在Debug下运行也不会出现问题, 但是在Release下面却出现内存错误, 所以可以带多个参数感觉只能在Debug下可以行的能, 在Release下就没失效了.
查阅相关的资料并利用VC查看相应的汇编代码发现, 应该是函数调用和返回时栈操作不平衡导致Release版本下出现了内存错误的问题, ON_COMMAND_RANGE在MFC默认的消息响应函数中, 参数只有一个, 如:
1
2
3
4
5
|
#define ON_COMMAND_RANGE(id, idLast, memberFxn) \ { WM_COMMAND, CN_COMMAND, ( WORD )id, ( WORD )idLast, AfxSig_vw, \ (AFX_PMSG)( void (AFX_MSG_CALL CCmdTarget::*)( UINT ))&memberFxn }, // ON_COMMAND_RANGE(id, idLast, OnFoo) is the same as // ON_CONTROL_RANGE(0, id, idLast, OnFoo) |
函数调用过程中, 会将传入的参数进行压栈操作, 因为MFC默认的传入参数只有一个, 因此调用OnCommandMy时会有系统传入的一个消息参数进行压栈操作. 在函数返回时, 应该进行出栈操作, 并且保证调用完成后栈维持平衡, 否则会出现可能的内存错误.在DEBUG上没有出现内存错误在于在调用OnCommandMy函数返回时编译器在返回代码处添加了如下的汇编代码:
1
2
3
4
5
6
7
8
9
|
pop edi pop esi pop ebx add esp, 48h cmp ebp, esp call __chkesp (0041e680) mov esp, ebp pop ebp ret 8(两个参数出栈) |
此汇编代码的作用就是在函数返回时检查调用中和调用返回时的栈是否一致, 如果不一致, 就强制平栈操作, 因为在这个调用过程中, 传入OnCommandMy的消息参数只有一个(只是申明成两个, 实际只有一个参数传入), 所以存在栈不一致的情况, 但是强制平栈可以避免由此引起的错误.
在Release版本下, 就没有了检测栈的操作,
只是简单的下面几句汇编代码完成出栈操作:
1
2
3
|
mov esp, ebp pop ebp ret 8两个参数出栈) |
可以明显看到, Release下出现了栈操作不平衡的情况, 即入栈数小于出栈数, 从而导致栈区地址错误, 当其它函数两次对栈区进行地址访问时就极有可能出现内存错误的现象了.
所以, 平时写程序时在Debug下高度完成之后, 最好还在Release下看一下, 因为有些时候, Debug下对函数参数的检查不是那么严格, 并且在栈的操作上, Debug可以帮助我们解决很多隐藏的问题, 但是Release下就不会了. 另外在自定义的消息响应函数中, Debug和Release都不会对响应函数的参数列表与MFC默认参数列表进行一致性检测, 从而可能隐藏重大的内存出错的可能性, 导致最终软件在Release下运行可能发生崩溃.
终于明白了,原来是ON_COMMAND_RANGE只能带一个参数,带两个或不带都会异常所以重新定义:
1
|
afx_msg void OnButtonPort( UINT nID); |
而且此nID就是你点击的按钮ID值,再也不用之前的麻烦代码了
1
2
|
CWnd *pWnd = GetFocus(); int nPortID = pWnd->GetDlgCtrlID() ; |
问题解决!
附加:
1、ON_COMMAND(ID_VIEW_CUSTOMIZE, OnViewCustomize)==>void CMainFrame::OnViewCustomize();或void CMainFrame::OnViewCustomize(WPARAM wParam, LPARAM lParam);
2、ON_REGISTERED_MESSAGE(AFX_WM_RESETTOOLBAR, OnToolbarReset)==>afx_msg LRESULT CMainFrame::OnToolbarReset(WPARAM /*wp*/,LPARAM);
3、ON_COMMAND_RANGE(ID_SHORTCUT_1, ID_SHORTCUT_5, OnOutlookBarShortcut)==>void CMainFrame::OnOutlookBarShortcut(UINT id);
4、ON_UPDATE_COMMAND_UI(ID_VIEW_CAPTIONBAR, OnUpdateViewCaptionBar)==>void CMainFrame::OnUpdateViewCaptionBar(CCmdUI* pCmdUI);