VC6中ON_COMMAND_RANGE在Release下容易出现的问题

  今天在写开启MySQL和Tomcat服务的一个小程序, 有段时间没有用VC了, 于是就用VC中的托盘实现了相应操作, 其中用到了托盘右键弹出菜单消息映射和消息响应, 如:

        消息申明:

        ON_COMMAND_RANGE(ID_CLOUD, ID_CLOUD + 4, OnCommandMy)   //托盘右键菜单响应消息

  消息响应函数申明:

       afx_msg void OnCommandMy(WPARAM wParam, LPARAM lParam );

       通过wParam参数的低字节判断菜单的ID, 进而来实现不同菜单的消息响应.

       因为以前写过类似的程序, 就直接Copy了过来, 在Debug下调试好了, 想应该OK了, 结果在Release版本

下只要对托盘右键菜单进行操作就发生内存错误, 搞得自己很是郁闷, 弄了很久, 没有办法, 就在Release版本下进行了调试, 结果发现是消息响应中出现了问题, 网上也找了很久, 没有找到解决的办法, 最后也只好老实看MSDN, 结果发现我的消息响应函数中的申明可能存在问题:

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下运行可能发生崩溃.

所以上面MSDN的东西说得模棱两可的, 明明ON_COMMAND_RANGE消息响应函数就只能那样定义, 他却说成那样, ......................

自定义的消息响应函数可以在AFXMSG.H下看到MFC默认的定义, 为了不引起上面谈到的问题, 最好遵照这个头文件里面说明的进行定义, 它可以在VC安装目录下的VC98/MFC/SRC查找到.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值