最近一段时间,许多人发帖子说自己的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版的时候才发现,程序会非法操作的;
以上所述仅代表个人看法,如有不同意的朋友,欢迎参加讨论;
---------------------------------------------------------------
http://expert.csdn.net/Expert/topic/2539/2539864.xml?temp=.9315149
---------------------------------------------------------------
哈,vc7就会报错了,我还真不知道,看来微软也意识到这是个BUG了;
---------------------------------------------------------------
这是 MFC 的一个 BUG,看看 MSDN 文档说的:
ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/vccore/html/vcrefwhatsnewlibrariesvisualc70.htm#vcrefwhatsnewlibrariesmfcvisualc70
Visual C++ .NET 中包含的下列库可能是新增的,也可能是经过更改的。
.......
Microsoft 基础类 (MFC) 库
* 有关 MFC 的参考主题包含数百个新的代码示例。
* 静态强制类型转换和 MFC 消息映射 从 Visual C++ .NET 开始,MFC 对消息处理函数的返回类型和参数类型进行更严格的类型检查。这些新增行为通过使用错误信息标记潜在不安全的消息处理函数,来通知开发人员可能会遇到的问题。MFC 现对 ON_MESSAGE、ON_REGISTERED_MESSAGE、ON_THREAD_MESSAGE 和 ON_REGISTERED_THREAD_MESSAGE 使用静态强制类型转换。
例如,过去开发人员可以对 ON_MESSAGE 或 ON_REGISTERED_MESSAGE 使用返回 void 而非 LRESULT 的成员函数,并且编译时不报告任何错误。而使用 Visual C++ .NET,则可以捕获潜在的错误强制类型转换,并将它标记为错误。开发人员可以通过替换返回类型(用 LRESULT 替换)并重新编译来修复这种潜在的问题。
* DHTML 编辑组件:CHtmlEditCtrl、CHTMLEditView、CHtmlEditDoc。
.........
VC7 的界面用不惯,而且速度慢,否则值得升级。
---------------------------------------------------------------
对了,建议大家以后做东西的时候都用Release版本做,这样像上面的问题就不会出现,有的人
可能会说用Release版本怎么调试啊。下面的办法可以解决。调试完了改回原来的设置再发布
http://expert.csdn.net/Expert/TopicView1.asp?id=2555224
看看我的回答
Release版本单步跟踪方法:(可能对大家来说早就是小菜一碟了)
选中Win32 Release然后
Project-》setting-》C/C++ -》Category-》General
-》Optimization-》Disable(Debug)
-》Debug Info-》Program DataBase
-》Link---》Generate Debug Info打上钩
另外请朋友们去捧捧这两个帖子。这个帖子我的回复花了我长时间。也许对那些
Debug和release不太懂得人有帮助。虽然不是我的帖子,但是所涉及到的知识
挺全的。
http://expert.csdn.net/Expert/TopicView1.asp?id=2539864
http://expert.csdn.net/Expert/topic/2553/2553540.xml?temp=.2079431
本人关心技术。希望和有志之士(关心技术)成为朋友
很同意woaini5994(孤独的猪) 的话
这不算是作广告吧。希望版主不要删除
---------------------------------------------------------------
遇到并解决的第一个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.把消息响应函数的函数体移到一个自定义的一般类函数中,在类函数中使用参数,消息响应函数只对类函数进行调用。(此方法仅适用于在消息响应函数中添加默认参数的情况)
消息响应函数申明:
下只要对托盘右键菜单进行操作就发生内存错误, 搞得自己很是郁闷, 弄了很久, 没有办法, 就在Release版本下进行了调试, 结果发现是消息响应中出现了问题, 网上也找了很久, 没有找到解决的办法, 最后也只好老实看MSDN, 结果发现我的消息响应函数中的申明可能存在问题:
afx_msg void On
On
通过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 On
今天又花了整整一天上网查阅相关的资料并利用VC查看相应的汇编代码发现, 应该是函数调用和返回时栈操作不平衡导致Release版本下出现了内存错误的问题, ON_COMMAND_RANGE在MFC默认的消息响应函数中, 参数只有一个, 如:
#define ON_COMMAND_RANGE(id, idLast, memberFxn) \
函数调用过程中, 会将传入的参数进行压栈操作, 因为MFC默认的传入参数只有一个, 因此调用OnCommandMy时会有系统传入的一个消息参数进行压栈操作. 在函数返回时, 应该进行出栈操作,
在DEBUG上没有出现内存错误在于在调用OnCommandMy函数返回时编译器在返回代码处添加了如下的汇编代码:
pop
此汇编代码的作用就是在函数返回时检查调用中和调用返回时的栈是否一致, 如果不一致, 就强制平栈操作, 因为在这个调用过程中, 传入OnCommandMy的消息参数只有一个(只是申明成两个, 实际只有一个参数传入), 所以存在栈不一致的情况, 但是强制平栈可以避免由此引起的错误.
在Release版本下, 就没有了检测栈的操作,
只是简单的下面几句汇编代码完成出栈操作:
mov
可以明显看到, Release下出现了栈操作不平衡的情况, 即入栈数小于出栈数, 从而导致栈区地址错误, 当其它函数两次对栈区进行地址访问时就极有可能出现内存错误的现象了.
所以, 平时写程序时在Debug下高度完成之后, 最好还在Release下看一下, 因为有些时候, Debug下对函数参数的检查不是那么严格, 并且在栈的操作上, Debug可以帮助我们解决很多隐藏的问题, 但是Release下就不会了. 另外在自定义的消息响应函数中, Debug和Release都不会对响应函数的参数列表与MFC默认参数列表进行一致性检测, 从而可能隐藏重大的内存出错的可能性, 导致最终软件在Release下运行可能发生崩溃.
所以上面MSDN的东西说得模棱两可的, 明明ON_COMMAND_RANGE消息响应函数就只能那样定义, 他却说成那样, ......................
自定义的消息响应函数可以在AFXMSG.H下看到MFC默认的定义, 为了不引起上面谈到的问题, 最好遵照这个头文件里面说明的进行定义, 它可以在VC安装目录下的VC98\MFC\SRC查找到.