ON_COMMAND_RANGE多个按钮响应一个函数的解决方法

 更新时间:2020年4月25日 17:42  点击:1954

本文描述了ON_COMMAND_RANGE多个按钮响应一个函数的解决方法。

开发人员需要注意在自定义消息响应函数的声明过程中,一定要注意参数的形式,稍微一疏忽就会导致莫须有的错误,具体以ON_COMMAND_RANGE为例说下。

1.声明消息响应函数:在要添加的工程上添加函数afx_msg void OnButtonPort();

2.消息映射:

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:

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/

以下是转载内容:

该网友定义如下:

afx_msg void OnCommandMy(WPARAM wParam, LPARAM lParam );

申明只适用于ON_COMMAND消息的函数申明, 而ON_COMMAND_RANGE的函数申明在MSDN中建议写成这样:
OnCommandMy(UINT nID);
通过switch(nID) case **:进行针对不同菜单进行消息响应.
nID就是菜单传入消息的ID号, 奇怪的是, 在Debug版本下, 先前的申明方式运行完全正常, 查阅了MSDN, 找出了可能的原因:

 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默认的消息响应函数中, 参数只有一个, 如:

#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函数返回时编译器在返回代码处添加了如下的汇编代码:

pop edi
pop esi
pop ebx
add esp, 48h
cmp ebp, esp
call __chkesp (0041e680)
mov esp, ebp
pop ebp
ret 8(两个参数出栈)

此汇编代码的作用就是在函数返回时检查调用中和调用返回时的栈是否一致, 如果不一致, 就强制平栈操作, 因为在这个调用过程中, 传入OnCommandMy的消息参数只有一个(只是申明成两个, 实际只有一个参数传入), 所以存在栈不一致的情况, 但是强制平栈可以避免由此引起的错误.
在Release版本下, 就没有了检测栈的操作,
只是简单的下面几句汇编代码完成出栈操作:

mov esp, ebp
pop ebp
ret 8两个参数出栈)

可以明显看到, Release下出现了栈操作不平衡的情况, 即入栈数小于出栈数, 从而导致栈区地址错误, 当其它函数两次对栈区进行地址访问时就极有可能出现内存错误的现象了.
所以, 平时写程序时在Debug下高度完成之后, 最好还在Release下看一下, 因为有些时候, Debug下对函数参数的检查不是那么严格, 并且在栈的操作上, Debug可以帮助我们解决很多隐藏的问题, 但是Release下就不会了. 另外在自定义的消息响应函数中, Debug和Release都不会对响应函数的参数列表与MFC默认参数列表进行一致性检测, 从而可能隐藏重大的内存出错的可能性, 导致最终软件在Release下运行可能发生崩溃.

终于明白了,原来是ON_COMMAND_RANGE只能带一个参数,带两个或不带都会异常所以重新定义:

afx_msg void OnButtonPort(UINT nID);

而且此nID就是你点击的按钮ID值,再也不用之前的麻烦代码了

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);

[!--infotagslink--]

相关文章

  • php 中file_get_contents超时问题的解决方法

    file_get_contents超时我知道最多的原因就是你机器访问远程机器过慢,导致php脚本超时了,但也有其它很多原因,下面我来总结file_get_contents超时问题的解决方法总结。...2016-11-25
  • HTTP 408错误是什么 HTTP 408错误解决方法

    相信很多站长都遇到过这样一个问题,访问页面时出现408错误,下面一聚教程网将为大家介绍408错误出现的原因以及408错误的解决办法。 HTTP 408错误出现原因: HTT...2017-01-22
  • 利用JS实现点击按钮后图片自动切换的简单方法

    下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
  • 安卓手机wifi打不开修复教程,安卓手机wifi打不开解决方法

    手机wifi打不开?让小编来告诉你如何解决。还不知道的朋友快来看看。 手机wifi是现在生活中最常用的手机功能,但是遇到手机wifi打不开的情况该怎么办呢?如果手机wifi...2016-12-21
  • PHP 验证码不显示只有一个小红叉的解决方法

    最近想自学PHP ,做了个验证码,但不知道怎么搞的,总出现一个如下图的小红叉,但验证码就是显示不出来,原因如下 未修改之前,出现如下错误; (1)修改步骤如下,原因如下,原因是apache权限没开, (2)点击打开php.int., 搜索extension=ph...2013-10-04
  • 连接MySql速度慢的解决方法(skip-name-resolve)

    最近在Linux服务器上安装MySql5后,本地使用客户端连MySql速度超慢,本地程序连接也超慢。 解决方法:在配置文件my.cnf的[mysqld]下加入skip-name-resolve。原因是默认安装的MySql开启了DNS的反向解析。如果禁用的话就不能...2015-10-21
  • 总结android studio注意事项及打不开等问题解决方法

    经过一段时间的使用,总结了android studio打不开等问题的6种解决方法及android studio注意事项,希望对大家有所帮助。 1 首次运行,建立好项目需要下载一些东西,如果...2016-09-20
  • MySQL ERROR 2013 (HY000)错误解决方法

    当通过 TCP/IP 连接 MySQL 远程主机时,出现 ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 104 。如果是在linux shell命令行中直接打 mysql 命令,...2015-03-15
  • IE6-IE9中tbody的innerHTML不能赋值的解决方法

    IE6-IE9中tbody的innerHTML不能赋值,重现代码如下 复制代码 代码如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>IE6-IE9中tbody的innerHTML不能复制bug</title> </head> <body style="height:3...2014-06-07
  • Mysql修改datadir导致无法启动问题解决方法

    centos6.2,停止mysqld然后修改/etc/my.cnf datadir的位置,启动mysqld提示FAILED,查看日志 复制代码 代码如下: 120609 11:31:31 mysqld_safe mysqld from pid file /var/run/mysqld/mysqld.pid ended 120609 11:35:12 my...2015-03-15
  • Photoshop提示“此产品的许可证已过期"及“无法开始您的Adobe Photoshop CS5.1 订阅”解决方法

    首先不要鄙视我用Photoshop盗版,实在是贵。现在我们来看看如果你的Photoshop提示“此产品的许可证已过期",还有“无法开始您的Adobe Photoshop CS5.1 订阅”如何解决吧...2016-09-14
  • 小米解锁验证失败怎么办 小米解锁工具登录失败解决方法

    小米手机如果想要刷机就必须要先解锁验证才可以,那么,如果遇到小米解锁验证失败以及小米解锁工具登录失败的现象怎么办呢?对此,本文就为大家进行解答,有需要的朋友来看看。...2016-12-21
  • BootStrap栅格系统、表单样式与按钮样式源码解析

    这篇文章主要为大家详细解析了BootStrap栅格系统、表单样式与按钮样式源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2017-01-23
  • photoshop字体显示乱码解决方法

    今天小编在这里就来给各位photoshop的这一款软件的使用者们来详细的说一下软件的字体出现了乱码这一问题的解决方法,那么各位有出现这个问题的,下面就来跟着小编一起看...2016-09-14
  • 51安卓模拟器启动失败解决方法

    有部分小伙伴表示自己遇到了51模拟器程序不能打开的情况,那么51模拟器打不开怎么回事?下面我就来为大家分享一下解决的方法,有需要的小伙伴就来看一看吧。 &#8195;&...2017-07-06
  • C#使用浏览按钮获得文件路径和文件夹路径的方法

    这篇文章主要介绍了C#使用浏览按钮获得文件路径和文件夹路径的方法,结合实例形式分析了C#浏览器事件响应及文件操作相关技巧,需要的朋友可以参考下...2020-06-25
  • CentOS下php使用127.0.0.1不能连接mysql的解决方法

    php代码很简单:复制代码 代码如下: $server="127.0.0.1"; println("Begin"); $link = mysql_connect($server,"mysql","mysql"); if (!$link) { die('Could not connect: ' . mysql_error().mysql_errno()); } lin...2015-03-15
  • 基于JavaScript实现手机短信按钮倒计时(超简单)

    在淘宝等购物网站,我们都会看到一个发送短信倒计时的按钮,究竟是如何实现的呢?下面小编通过本篇文章给大家分享一段代码关于js实现手机短信按钮倒计时,需要的朋友参考下...2016-01-02
  • python编程PyQt5创建按钮及触发点击事件示例解析

    这篇文章主要为大家介绍了python编程使用PyQt5如何创建按钮及触发点击事件的示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步...2021-10-29
  • PHP is_subclass_of函数的一个BUG和解决方法

    is_subclass_of的作用:复制代码 代码如下:bool is_subclass_of ( object object, string class_name )如果对象 object 所属类是类 class_name 的子类,则返回 TRUE,否则返回 FALSE。注: 自 PHP 5.0.3 起也可以用一个字符...2014-06-07