CFileDialog 是 MFC 对 OPENFILENAME 及 GetOpenFileName/GetSaveFileName 的封装。在 VC6(MFC 4.2)之中,其定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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(); // … }; |
让我们来看看这处隐藏得极深的崩溃,先从 OPENFILENAME 的定义开始:
1 2 3 4 5 6 7 8 | typedef struct tagOFN { // … #if (_WIN32_WINNT >= 0×0500) void * pvReserved; DWORD dwReserved; DWORD FlagsEx; #endif // (_WIN32_WINNT >= 0×0500) } OPENFILENAME, *LPOPENFILENAME; |
也就是说,随着 _WIN32_WINNT 定义的不同,OPENFILENAME 的大小将是不同的。又由于 OPENFILENAME 是 CFileDialog 的一个成员变量,所以在 OPENFILENAME 之后定义的成员变量偏移量也有可能是不同的。
更糟糕的是,CFileDialog 没有实现虚构函数,这就意味着当我们在代码中使用 CFileDialog 的时候,编译器将会为其实现一个默认的虚构函数。
好了,现在我们来看看 MFC42D.dll 之中,CFileDialog 构造函数的汇编代码:
1 2 3 | mov ecx, [ebp+this] add ecx, 0B0h ; offset of m_strFilter call ??0CString@@QAE@XZ_0 ; CString::CString(void) |
而编译器实现的析构函数是:
1 2 3 | mov ecx,dword ptr [ebp-10h] add ecx,offset <Unloaded_RT40.dll>+0xbb (000000bc) ; oops!! call MaxDU!CString::~CString (006b29b2) |
如你所见,当应用程序代码与 MFC42D.dll 的 _WIN32_WINNT 不一致的时候,在析构 m_strFilter 的时候将导致崩溃。
最新的 MFC 代码将 OPENFILENAME 修改成为了指针成员,解决了此问题。
哎呀。。我那个插件为什么会引起崩溃啊?