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

 更新时间:2014年07月18日 09:29:27   投稿:shichen2014  
这篇文章主要介绍了ON_COMMAND_RANGE多个按钮响应一个函数的解决方法,需要的朋友可以参考下

本文描述了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);

相关文章

  • C语言之实现控制台光标随意移动的实例代码

    C语言之实现控制台光标随意移动的实例代码

    下面小编就为大家带来一篇C语言之实现控制台光标随意移动的实例代码。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-07-07
  • C语言全面梳理文件操作方法

    C语言全面梳理文件操作方法

    这篇文章主要为大家详细介绍了C语言的文件操作,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-05-05
  • C++实现二维图形的傅里叶变换

    C++实现二维图形的傅里叶变换

    这篇文章主要介绍了C++实现二维图形的傅里叶变换的方法,是C++程序设计里一个重要的应用,需要的朋友可以参考下
    2014-08-08
  • C语言动态分配二维字符串数组的方法

    C语言动态分配二维字符串数组的方法

    小编最近忙里偷闲,给大家整理一份教程关于C语言动态分配二维字符串数组的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-10-10
  • C++小游戏教程之猜数游戏的实现

    C++小游戏教程之猜数游戏的实现

    这篇文章主要和大家详细介绍如何利用C++做一个简易的猜数游戏,分为用户猜数和系统猜数。文中的示例代码讲解详细 ,感兴趣的小伙伴可以尝试一下
    2022-11-11
  • C++详细分析讲解函数参数的扩展

    C++详细分析讲解函数参数的扩展

    在C++中,定义函数时可以给形参指定一个默认的值,这样调用函数时如果没有给这个形参赋值(没有对应的实参),那么就使用这个默认的值。也就是说,调用函数时可以省略有默认值的参数
    2022-04-04
  • C++实现简单的图书管理系统

    C++实现简单的图书管理系统

    本文给大家分享的是使用C++实现简单的图书管理系统的代码,本系统采用了面向对象的程序设计方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2015-08-08
  • 详解C语言结构体的定义和使用

    详解C语言结构体的定义和使用

    这篇文章主要为大家介绍了C语言结构体的定义和使用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-12-12
  • C++实现LeetCode(152.求最大子数组乘积)

    C++实现LeetCode(152.求最大子数组乘积)

    这篇文章主要介绍了C++实现LeetCode(152.求最大子数组乘积),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C语言结构体详细图解分析

    C语言结构体详细图解分析

    C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许你存储不同类型的数据项,本篇让我们来了解C 的结构体
    2022-03-03

最新评论