装了 Platform SDK 的进来讨论问题

ross33123 2004-01-09 08:26:06
如果有人给出比较清楚的答案,给 150 分

首先假定已经装了VC 6 和最新的 PSDK,并且设定了路径

然后假定你为这个工程设定了宏 _WIN32_WINNT=0x0500

在一个普通的GUI程序中下面简单的代码也会出异常

CFileDialog dlg(TRUE);
dlg.DoModal();

而且在 DEBUG 模式下如果没有 catch 这个异常

程序就会在 dlg 析构时出错退出

请试一下你是否也有这个问题

并且给出解决方法或者说明原因

谢谢
...全文
395 39 打赏 收藏 转发到动态 举报
写回复
用AI写文章
39 条回复
切换为时间正序
请发表友善的回复…
发表回复
ross33123 2004-01-16
  • 打赏
  • 举报
回复
欢迎牛人们继续发表意见
ross33123 2004-01-15
  • 打赏
  • 举报
回复
其实我也是看到这两段汇编才认为::~CFildDialog有两个版本的

继续 UP:)
zhangcrony 2004-01-15
  • 打赏
  • 举报
回复
刚下班先顶一下,
好长的贴看看先
lsaturn 2004-01-15
  • 打赏
  • 举报
回复
这个问题和我原来问的一样嘛
学习啊!
taianmonkey 2004-01-15
  • 打赏
  • 举报
回复
学习学习!
精彩!
fzd999 2004-01-15
  • 打赏
  • 举报
回复
上一次看这篇贴还只是讨论简单的外壳,现在居然已经深入到MFC的源代码拉?佩服!

帮你up吧,这块我也不熟悉了。
Zark 2004-01-15
  • 打赏
  • 举报
回复
我认为楼主的观点是正确的,析构函数有两个版本,实际情况中:

栈上的CFileDialog::~CFildDialog()部份情况:
00401D40 push ebp
00401D41 mov ebp,esp
00401D43 push 0FFh
00401D45 push offset __ehhandler$??1CFileDialog@@UAE@XZ (00403779)
00401D4A mov eax,fs:[00000000]
00401D50 push eax
00401D51 mov dword ptr fs:[0],esp
00401D58 sub esp,44h
00401D5B push ebx
00401D5C push esi
00401D5D push edi
00401D5E push ecx
00401D5F lea edi,[ebp-50h]
00401D62 mov ecx,11h
00401D67 mov eax,0CCCCCCCCh
00401D6C rep stos dword ptr [edi]
00401D6E pop ecx
00401D6F mov dword ptr [ebp-10h],ecx
00401D72 mov dword ptr [ebp-4],0
00401D79 mov ecx,dword ptr [ebp-10h]
00401D7C add ecx,0BCh
00401D82 call CString::~CString (004020b2)

在堆上的CFileDialog::~CFildDialog()部份情况:
5F409820 push ebp
5F409821 mov ebp,esp
5F409823 push 0FFh
5F409825 push offset $L88948 (5f4a8779)
5F40982A mov eax,fs:[00000000]
5F409830 push eax
5F409831 mov dword ptr fs:[0],esp
5F409838 push ecx
5F409839 mov dword ptr [ebp-10h],ecx
5F40983C mov dword ptr [ebp-4],0
5F409843 mov ecx,dword ptr [this]
5F409846 add ecx,0B0h
5F40984C call CString::~CString (5f42b2a2)

上面的两段执行代码中,在 add exc,0BCh(或0B0h)之前,ecx均正确地指向CFileDialog对象的起始地址,无论是堆上还是栈上.但两者向ecx中送入的偏移量是不同,相差了了0xC(12)字节,也就是那个OPENFILENAME结构中多出来的部份.所以我也认为CFileDialog对象是建在栈上还是建在堆上,它在析构时的表现是不同,我猜测造成这个不同的根本原因是MS的扩展DLL在设计中的缺陷,论据正在整理中.
蒋晟 2004-01-14
  • 打赏
  • 举报
回复
编译器是不会自动生成析构函数的

构造函数实际上是void AFXEXPORT CFileDialog(CFileDialog* this,...)
析构函数实际上是void AFXEXPORT ~CFileDialog(CFileDialog* this)
都是MFC42.dll导出的函数
ross33123 2004-01-14
  • 打赏
  • 举报
回复
谢谢了!

我刚刚发现,CFileDialog 的析构函数在连接库里好像是有的

但是如果用 CFileDialog dlg;

调用的是编译器自动生成的版本

如果用 new ,调用的好像就是连接库版本了
蒋晟 2004-01-14
  • 打赏
  • 举报
回复
afxwin.h之后可以undef _WIN32_WINNT来使得CFileDialog的大小复原
#define _WIN32_WINNT 0x0500 // allow Win2000 specific calls
#include <afxwin.h> // MFC core and standard components
#undef _WIN32_WINNT // allow CFileDialog to build with the correct size

这样可以解决这个问题。我仍然在研究上面提到的问题的原因
蒋晟 2004-01-14
  • 打赏
  • 举报
回复

为了明确是否是析构函数的问题,我编写了两个测试代码

奇怪的是,如下代码并未在我的测试程序中产生异常(无论是Debug还是Release版本)

CFileDialog* pdlg=new CFileDialog (TRUE);
pdlg->DoModal();
delete pdlg;
而如下代码会产生异常(无论是Debug还是Release版本)

CFileDialog dlg(TRUE);
dlg.DoModal();

我正在进一步分析这个问题。
我的stdafx.h开头的版本需求定义

#ifndef WINVER
#define WINVER 0x0500
#endif

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif

#ifndef _WIN32_WINDOWS
#define _WIN32_WINDOWS 0x0500
#endif

#ifndef _WIN32_IE #define _WIN32_IE 0x0501
#endif
我的VC和SDK头文件都是最新的版本
头文件和库文件的包含顺序
Microsoft SDK
DXSDK
VC98\INCLUDE
VC98\MFC\INCLUDE
ross33123 2004-01-14
  • 打赏
  • 举报
回复
以上是我的理解,如果有不对的地方请高手们指正
ross33123 2004-01-14
  • 打赏
  • 举报
回复
多谢 Zark(金陵五月)关注这个帖子

按照你的线索,我跟踪了半天,好像搞明白了

我认为 jiangsheng(蒋晟.Net) 说得也不算错

你说的 mfc42d.dll 中的析构函数我认为是不存在的

如果真的存在的话,如 jiangsheng 所说可能也不会出问题

我认为 。。。。。。。。

CFileDialog 没有定义析构函数,这才是关键

因为没有定义,那么编译器会自动给它生成一个

这样,构造过程调用的是库函数的版本

析构过程调用的是编译器生成的版本。

构造和析构牵涉到子对象CString m_strFilter的构造和析构

而调用构造函数和析构函数都要传递 this 指针。

CFileDialog 对象构造 m_strFilter 时,传递给 CString 构造函数的 this 指针是相对于 CFileDialog 自己的 this 指针的一个偏移量

如果头文件没改动过,那么 CFileDialog 析构导致 m_strFilter 析构时,传递给 CString 析构函数的 this 指针应该是同一个偏移量

但是头文件改过了,析构函数又是编译器自动生成的,导致传递给 CString 的 this 指针与连接库中 CFileDialog 构造函数不同

在一个没有构造 CString 的地方析构 CString,结果可想而知
ross33123 2004-01-14
  • 打赏
  • 举报
回复
可是很多类的声明里并没有析构函数呀

包括 CFileDialog

即使是库函数里的析构函数

恐怕也是编译器自动生成后放进去的吧
Zark 2004-01-14
  • 打赏
  • 举报
回复
关于蒋晟先生的回复.

讨论前提:
1.操作系统: WINDOWS XP,(NT5.1)
2.编程环境为VC6.
3.include路径顺序为SDK(2003年3月),VC6原有的include.
4.lib路径顺序为SDK(2003年3月),VC6原有的lib.
5.测试程序为MFC DIALOG BASED.在对话框中只含有一个按钮,其处理程序如下:
void CMfcTestDlg::OnButton1()
{
// TODO: Add your control notification handler code here
int i=100;
CFileDialog dlg(TRUE);
int j=sizeof(dlg);

dlg.DoModal();
}
5.上例中设定i,j的目的是为了把dlg对象卡住,以便能更清楚地说明问题.
6.OPENFILENAME结构如下所述:
typedef struct tagOFN {
DWORD lStructSize;
HWND hwndOwner;
HINSTANCE hInstance;
LPCTSTR lpstrFilter;
LPTSTR lpstrCustomFilter;
DWORD nMaxCustFilter;
DWORD nFilterIndex;
LPTSTR lpstrFile;
DWORD nMaxFile;
LPTSTR lpstrFileTitle;
DWORD nMaxFileTitle;
LPCTSTR lpstrInitialDir;
LPCTSTR lpstrTitle;
DWORD Flags;
WORD nFileOffset;
WORD nFileExtension;
LPCTSTR lpstrDefExt;
LPARAM lCustData;
LPOFNHOOKPROC lpfnHook;
LPCTSTR lpTemplateName;
#if (_WIN32_WINNT >= 0x0500)
void * pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)
} OPENFILENAME, *LPOPENFILENAME;

7.CFileDialog的定义如下:
class CFileDialog : public CCommonDialog
{
DECLARE_DYNAMIC(CFileDialog)

public:
// Attributes
OPENFILENAME m_ofn; // open file parameter block

// Constructors
CFileDialog(BOOL bOpenFileDialog, // TRUE for FileOpen, FALSE for FileSaveAs
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
CWnd* pParentWnd = NULL);

// Operations
virtual int DoModal();

// Helpers for parsing file name after successful return
// or during Overridable callbacks if OFN_EXPLORER is set
CString GetPathName() const; // return full path and filename
CString GetFileName() const; // return only filename
CString GetFileExt() const; // return only ext
CString GetFileTitle() const; // return file title
BOOL GetReadOnlyPref() const; // return TRUE if readonly checked

// Enumerating multiple file selections
POSITION GetStartPosition() const;
CString GetNextPathName(POSITION& pos) const;

// Helpers for custom templates
void SetTemplate(UINT nWin3ID, UINT nWin4ID);
void SetTemplate(LPCTSTR lpWin3ID, LPCTSTR lpWin4ID);

// Other operations available while the dialog is visible
CString GetFolderPath() const; // return full path
void SetControlText(int nID, LPCSTR lpsz);
void HideControl(int nID);
void SetDefExt(LPCSTR lpsz);

// Overridable callbacks
protected:
friend UINT CALLBACK _AfxCommDlgProc(HWND, UINT, WPARAM, LPARAM);
virtual UINT OnShareViolation(LPCTSTR lpszPathName);
virtual BOOL OnFileNameOK();
virtual void OnLBSelChangedNotify(UINT nIDBox, UINT iCurSel, UINT nCode);

// only called back if OFN_EXPLORER is set
virtual void OnInitDone();
virtual void OnFileNameChange();
virtual void OnFolderChange();
virtual void OnTypeChange();

// Implementation
#ifdef _DEBUG
public:
virtual void Dump(CDumpContext& dc) const;
#endif

protected:
BOOL m_bOpenFileDialog; // TRUE for file open, FALSE for file save
CString m_strFilter; // filter string
// separate fields with '|', terminate with '||\0'
TCHAR m_szFileTitle[64]; // contains file title after return
TCHAR m_szFileName[_MAX_PATH]; // contains full path name after return

OPENFILENAME* m_pofnTemp;

virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
};


讨论1: dlg对象倒底有多大?

从测试程序来看,j清楚地表明了dlg的大小.

测试表明:
在含有_WIN32_WINNT >= 0x0500条件下,j值为520;在不含有_WIN32_WINNT >= 0x0500条件下,j值为508.

原因:
多出来的12字节为OPENFILENAME结构中的pvReserved,dwReserved,FlagsEx.

结论:
蒋晟先生所说的"因为CFileDialog的实现也是很久之前写好的,所以实现里面分配的OPENFILENAME结构的大小也应该是旧的大小.如果定义了_WIN32_WINNT=0x0500,那么就表明是在为Windows2000及其以上版本编写程序,根本不应该有结构的大小的问题"是与事实有出入的,因为在编译测试程序时,才发生分配dlg分配空间这一事件,编译是依据头文件的定义来分配空间的,因为头文件不同,所以产生了dlg对象大小不同的问题.


讨论2: 就算是对象大小不同,现在是分配内存大于实际需要的内存,CFileDialog应该只操作520字节中的508部份,为什么会出错呢?

结论: 这个崩溃并不是来源于dlg对象中OPENFILENAME结构部分,而是其他的成员变量,黑手是CString,由于测试程序调用的mfc42d.dll对dlg对象的理解与测试程序本身对dlg对象的理解不同,导致了mfc42d.dll在对dlg进行析构时出现了地址错误,这个错误导致了崩溃.

分析过程:

对于这个dlg对象,请注意它的成员变量CString m_strFilter.

当dlg开始构造时,dlg的地址为0x0012F4A4,dlg.m_strFilter的地址是0x0012F554.

当构造完成后0x0012F554处的值是0x5F4CCB14,根据MFC中CString的定义,这是它的缓冲区,它实际上目前是指向一个全局性的空字符串.
这时记下地址0x0012F560,这是一个特殊的地方,后面要用到,它与m_szFilter的偏移量为12字节,它的值是0xCCCCCCCC(随机值).

执行DoModal(), 然后退出"打开文件对话框",现在开始析构.

当析构到CString m_strFilter,它的地址不再是0x0012F554,而是0x0012F560.因为mfc42d.dll认为的dlg各成员变量与实际上的各成员变量均相差12字节.由于CString析构时必须先查看它的缓冲区是不是指向全局性的空字符串(0x5F4CCB14),当然现在不是,现在是指向0xCCCCCCCC,于是它错误地认为这是个非空的字符串,因此它要先对缓冲区进行解锁,对0xCCCCCCCC进行解锁违反了WINDOWS的规定,0xCCCCCCCC是系统区,不允许操作,这个时候就产生了崩溃.

最后总结:
这个问题的由来实际上是违反了DLL的编程规则造成的.作者实际上是在DLL(mfc42d.dll)发布后,擅自更动了其头文件(commdlg.h),在未重新编译DLL情况下使用它而造成.
ross33123 2004-01-13
  • 打赏
  • 举报
回复
回 Zark(金陵五月)

makefile 是 VC 自带的

在 MFC 的 SRC 目录下
rivershan 2004-01-13
  • 打赏
  • 举报
回复
学习学习~
celerityok 2004-01-13
  • 打赏
  • 举报
回复
请看源代码
http://expert.csdn.net/Expert/topic/2659/2659611.xml?temp=.9618341
Zark 2004-01-13
  • 打赏
  • 举报
回复
可以把那个命令行上执行的MAKEFILE帖出来,或是发给我tohny_cn@yahoo.com吗?

等有空再回来写关于jiangsheng(蒋晟.Net)的回复.
ross33123 2004-01-13
  • 打赏
  • 举报
回复
学习ing^_^

先回答Zark的一个小问题

我重新编译 MFC 不是新加了一个类

而是修改了 MFC 的类的一个BUG(正是你痛恨的 CString)

重新编译不是用 dsp 而是用 makefile 在命令行做的


然后回 jiangsheng(蒋晟.Net):

CFileDialog dlg(TRUE);
dlg.DoModal();

这两行代码不会出错吧

你只要这两行放到任何一个会被调用的函数里

在我在顶楼描述的条件下肯定会有异常,一试便知


再提个问题-----

我用VC6写程序从来没有想过用 MFC50

可是如果用的话好像只要把相关的 DLL 随程序发布

或者静态连接就可以了

会不会有其他问题,有人试过吗?
加载更多回复(19)

16,471

社区成员

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

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

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