MFC中,Release版出错Debug版不出错的一个最常见原因之深入剖析

topwork 2003-12-24 12:59:46
加精
也不知道网上有没有类似的文章,小弟斗胆在这里献丑一回;
最近一段时间,许多人发帖子说自己的MFC程序Release版会出错,而Debug版不会出错,记得在两年前我也曾遇到过类似的问题,但是没有进行深入研究,这两天我对这个问题作了一个深入的探讨发现了一个非常容易犯的错误,这也与VC编译器有关(不知道是微软的BUG还是怎么回事),首先我们看一个事例工程:
用VC新建一个Dialog工程,然后加入一个新的对话窗,并且生成一个对话窗类;然后在主对话窗的OnOK事件中建立那个新对话窗的非模态对话窗,例如下面:
void CADlg::OnOK()
{
m_pDlg = new CDlg1;//m_pDlg是类成员变量,新对话窗的指针
m_pDlg->Create(IDD_DIALOG1);
m_pDlg->ShowWindow(SW_SHOW);
}

然后加一个自定义消息:WM_MYMSG;
在新的对话窗的OnOK事件中向主对话窗发送WM_MYMSG消息;
void CDlg1::OnOK()
{
CWnd *pWnd = GetParent();
pWnd->SendMessage(WM_MYMSG);
}
下面我要说的就是最关键的地方了,我们为了响应WM_MYMSG消息通常有两种做法,一种是重载主对话窗的WindowProc虚函数,然后在函数内部响应这个消息,例如下面:
LRESULT CADlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
Switch(message)
{
case WM_MYMSG:
{
……
break;
}
}
return CDialog::WindowProc(message, wParam, lParam);
}
这种做法通常不会出错;
下面我们看第二种响应这个消息的方法;
首先在主对话窗中加入一个函数,例如下面:
VOID CADlg::OnMyMsg()
{
}
然后在主对话窗的消息映射表中加一项:
BEGIN_MESSAGE_MAP(CADlg, CDialog)
//{{AFX_MSG_MAP(CADlg)
ON_MESSAGE(WM_MYMSG , OnMyMsg)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
这样当我们的子对话窗向主对话窗发送WM_MYMSG消息的时候,MFC就会调用我们的OnMyMsg函数,于是错误出现了,首先我们看看ON_MESSAGE宏的定义;
#define ON_MESSAGE(message, memberFxn) \
{ message, 0, 0, 0, AfxSig_lwl, \
(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&memberFxn }
熟悉宏定义的朋友一看就明白,这个宏在展开的时候实际上是将两个参数压栈(WPARAM和LPARAM),然后调用函数指针;
而我们的函数OnMyMsg确没有参数定义,换句话说,函数返回的时候不会平栈,这就是Release版程序非法操作的原因;
再说具体些,我们把上面的OnMyMsg函数写成这样:
VOID CADlg::OnMyMsg()
{
MessageBox("测试");
}
然后看看它的汇编代码:
push 0
push 0
push 403020h
call 004017CA
ret
前面我们就不看了,看最后一句:
ret
完了,它直接ret了(当然了,直接ret是我们函数定义的结果),而不是比如说什么
ret 8
之类的语句(这是因为我们的函数没有定义参数,因此直接ret)。
这样我们进入函数前压栈的两个参数就没有进行平栈动作了;函数返回,栈不平当然就会非法操作了;
换句话说,如果我们的程序写成这样:
VOID CADlg::OnMyMsg()
{
MessageBox("测试");
__asm ret 8;
}
那么Release版就不会报错了(相反Debug版就会报错),不信请试验一下,但是这样写是不对的,请朋友们在编写程序的时候不要这样写,这只是说明消息映射函数平栈情况的一个证据罢了;
话分两头,为什么Debug版没有问题呢?
先看看下面的汇编:
mov ecx,dword ptr [ebp-0Ch]
mov dword ptr fs:[0],ecx
pop edi
pop esi
pop ebx
add esp,5Ch
cmp ebp,esp
call _chkesp (004022fc)
mov esp,ebp
pop ebp
VC在对MFC的Debug版程序进行编译的时候,会在函数的后面加上一段类似上面的代码,那段代码的功能就是检测esp,看看栈是否是平的,如果不平则强行平栈,因此Debug版程序不会出这种错误,至于微软为什么要这样做,我实在也是想不明白,请各位朋友也一起琢磨一下吧(欢迎跟贴讨论);
综上所述,我们在编写MFC程序,映射自己的消息函数的时候要么采用第一种方法,重载WindowProc虚函数,要么采用第二种方法,但是函数要定义两个参数(WPARAM和LPARAM),即使没有用处也要这样定义;这样就可以避免Release版出错Debug版不出错的绝大部分情况了;
另外,我这里再提一下这个宏:
ON_MESSAGE_VOID
这个宏定义在"afxpriv.h",这个宏与ON_MESSAGE相反,他的消息映射函数不能带参数。即如果用这个宏进行消息映射,那么那个消息映射函数就不能带参数,如果带了参数就会发生Release版出错,Debug版不出错的情况了;
最后,我们不管用上面那个宏映射消息响应函数,而你的消息响应函数不管定义成什么样子,VC在进行编译的时候都不会报错,因此这个错误将隐藏的很深,直到你即将发布Release版的时候才发现,程序会非法操作的;
以上所述仅代表个人看法,如有不同意的朋友,欢迎参加讨论;
...全文
556 64 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
64 条回复
切换为时间正序
请发表友善的回复…
发表回复
filter023 2003-12-30
  • 打赏
  • 举报
回复
mark
lsaturn 2003-12-30
  • 打赏
  • 举报
回复
学习!
banalman 2003-12-29
  • 打赏
  • 举报
回复
这个问题经常出现。由于debug 版的内存堆栈照顾经常造成应用的异常。
kwest 2003-12-29
  • 打赏
  • 举报
回复
收藏!!!
yinx 2003-12-29
  • 打赏
  • 举报
回复
遇到并解决的第一个release mode bug

“在 Class Wizard 添加的响应函数中使用手动添加的参数将导致 Debug 模式运行正常,但Release 模式运行时非法操作。”

估计原因:MFC默认的 ON_CONTROL 消息响应函数原型为 (void)pfn(void), 因此在未改变MFC函数类型声明时,用额外的参数调用会导致Release mode下,程序堆栈上的函数返回地址被作为函数参数来错误使用,而函数返回地址也就自然不对了,从而导致Access Violation。



解决办法:

1.把消息响应函数声明对应的 AfxSig_vv,改为相应函数类型的 AfxSig_xx,然后用新的语句(原来的宏展开后把AfxSig_vv换成AfxSig_XX)替代ON_CONTROL等宏。

2.把消息响应函数的函数体移到一个自定义的一般类函数中,在类函数中使用参数,消息响应函数只对类函数进行调用。(此方法仅适用于在消息响应函数中添加默认参数的情况)
jackwuwei 2003-12-29
  • 打赏
  • 举报
回复
收藏……
CounterHack 2003-12-29
  • 打赏
  • 举报
回复
好贴,不过好像有更多错误没提及到,等待中。
哟西哟西地 2003-12-29
  • 打赏
  • 举报
回复
goooopoooooooooooooooooooooooooooood..
leon_z 2003-12-29
  • 打赏
  • 举报
回复
感谢楼主及各位牛人,再顶
louifox 2003-12-28
  • 打赏
  • 举报
回复
topwork 2003-12-28
  • 打赏
  • 举报
回复
to WindsonZhL(风之子) ;
非法操作的原因是栈不平,而栈不平不是返回类型的问题,返回类型是放在EAX中的,与平栈无关;
另外像您那样定义,release版一定会出问题的?难道您没有么?
请试验一下;
您在类定义内加入
afx_msg LRESULT OnMyMsg();
然后在消息影射表中加入:
ON_MESSAGE(WM_MYMSG , OnMyMsg)
看看是否会出错?
  • 打赏
  • 举报
回复
我是建好程序就换成 Release 模式编译,至今还未碰到这类问题(我定义消息响应函数,
若不需要传参数就写成 afx_msg void OnMyMsg() )。

我想应该是跟返回类型是 void 还是 LRESULT 有关,而与是否有参数无关。
sunyou 2003-12-28
  • 打赏
  • 举报
回复
好贴
zx_sanjin 2003-12-28
  • 打赏
  • 举报
回复
我补充一句,微软的编译器有一个编译选项是GZ,它能将未经初始化的变量初始化为0xcc。
详情可参照MSDN的:-GZ compiler option
topwork 2003-12-27
  • 打赏
  • 举报
回复
感谢 changlele(梦幻水晶) 的补充;
Hyrut 2003-12-27
  • 打赏
  • 举报
回复
好文章
我也mark一下
中间的几位也都是高手呀
呵呵
EricTangHL 2003-12-26
  • 打赏
  • 举报
回复
must mark
woaini5994 2003-12-26
  • 打赏
  • 举报
回复
好久没上CSDN(正在找工作,)
精彩文章,以后一定多多专研
谢谢
好技术+好人品=真正的高手。
popyyb 2003-12-26
  • 打赏
  • 举报
回复
mark
kongyunzhongque 2003-12-26
  • 打赏
  • 举报
回复
好文章,收了
加载更多回复(44)

16,548

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • AIGC Browser
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧