1. 问题描述
在实际编程过程中需要在dll内部提供继承自CPropertySheet的对话框进行参数设置,由于需要在很多地方调用同一个对话框,为了同时只显示一个对话框实例,采用了如下所示的单例实现方法
class CPage1: public CPropertyPage
class CPage2: public CPropertyPage
class CSettingDlg: public CPropertySheet
{
private:
CSettingDlg();
private:
CPage1 m_page1;
CPage2 m_page2;
public:
static CSettingDlg * GetInstance()
{
static CSettingDlg dlg;
return &dlg;
}
BOOL ShowDlg();
}
同时为了能够在显示标签页是正确查找到资源在构造函数中将CPropertyPage::m_PSP.m_hInstance设置为当前模块,即
CSettingDlg::CSettingDlg()
{
m_page1.GetPSP().m_hInstance = theApp.m_hInstance;
m_page2.GetPSP().m_hInstance = theApp.m_hInstance;
}
另外提供了对话框显示函数
BOOL CSettingDlg::ShowDlg()
{
if(!GetSafeHwnd())
{
if(!Create(...)) //调用CPropertySheet::Create创建窗口
return FALSE;
}
//利用AddPage/RemovePage设置需要显示的标签页,并利用SetActivePage设置激活的标签页
return TRUE;
}
使用时直接点用
CSettingDlg::GetInstance()->ShowDlg()
可以显示出对话框,但是在关闭程序时会产生内存泄漏,类似于:
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(195) : {16605} normal block at 0x03689160, 128 bytes long.
Data: < # > 95 23 00 00 00 00 00 00 FF FF FF FF 00 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(181) : {16604} normal block at 0x036890E0, 68 bytes long.
Data: < > FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\dlgprop.cpp(244) : {16603} normal block at 0x03689090, 16 bytes long.
Data: < h ` h > 00 00 00 00 E0 90 68 03 10 00 00 00 60 91 68 03
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(195) : {16599} normal block at 0x03688F98, 184 bytes long.
Data: < # o# > 95 23 00 00 00 00 00 00 6F 23 00 00 00 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(181) : {16598} normal block at 0x036B9A30, 96 bytes long.
Data: < > FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\dlgprop.cpp(244) : {16597} normal block at 0x036B9620, 16 bytes long.
Data: < 0 k h > 00 00 00 00 30 9A 6B 03 17 00 00 00 98 8F 68 03
Data: < # > 95 23 00 00 00 00 00 00 FF FF FF FF 00 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(181) : {16604} normal block at 0x036890E0, 68 bytes long.
Data: < > FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\dlgprop.cpp(244) : {16603} normal block at 0x03689090, 16 bytes long.
Data: < h ` h > 00 00 00 00 E0 90 68 03 10 00 00 00 60 91 68 03
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(195) : {16599} normal block at 0x03688F98, 184 bytes long.
Data: < # o# > 95 23 00 00 00 00 00 00 6F 23 00 00 00 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\occmgr.cpp(181) : {16598} normal block at 0x036B9A30, 96 bytes long.
Data: < > FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\dlgprop.cpp(244) : {16597} normal block at 0x036B9620, 16 bytes long.
Data: < 0 k h > 00 00 00 00 30 9A 6B 03 17 00 00 00 98 8F 68 03
2. 问题查找
根据内存泄漏信息,可以发现产生内存泄漏以3条为一组,并且在代码中的位置相同,由于泄漏信息里的文件路径不存在要直接打开occmgr.cpp/dlgprog.cpp文件查看不太方便,将断点设置在了~CSettingDlg中,并通过Step Into方式让调试器帮我们找到文件,首先出现的是dlgprog.cpp文件,找到244行,其内容为:
const
DLGTEMPLATE
*
CPropertyPage
::
InitDialogInfo
(
const
DLGTEMPLATE
*
pTemplate
)
{
// cleanup from previous run, if any
Cleanup
();
m_pOccDialogInfo
= (
_AFX_OCC_DIALOG_INFO
*)
malloc
(
sizeof
(
_AFX_OCC_DIALOG_INFO
));
return
afxOccManager
->
PreCreateDialog
(
m_pOccDialogInfo
,
pTemplate
);
}
可以知道内存泄漏产生的原因是利用malloc生成的内存
m_pOccDialogInfo
,没有在合适的时候释放,在同文件内搜索free(
m_pOccDialogInfo
找到唯一一个结果:
void
CPropertyPage
::
Cleanup
()
{
COccManager
*
pOccManager
=
afxOccManager
;
if
((
pOccManager
!=
NULL
) && (
m_pOccDialogInfo
!=
NULL
))
{
pOccManager
->
PostCreateDialog
(
m_pOccDialogInfo
);
free
(
m_pOccDialogInfo
);
m_pOccDialogInfo
=
NULL
;
}
}
在两处分别设置断点并运行程序发现,在调用CPropertySheet::Create和
每次AddPage时都
会调用InitDialogInfo,并正常调用Clean是否内存,但是当前关闭程序时虽然调用了Cleanup函数但却没有跳入到free(m_pOccDialogInfo);
重新运行并调试发现关闭程序时获得值为NULL,其他情况不为NULL,进一步跟踪金afxOccManager发现两者在afxOccManager中调用的AfxGetModuleState的返回值不同,正常情况指向应用程序所在的模块,关闭时指向了自身模块。
因此如果能够让
CSettingDlg
在析构时AfxGetModuleState值仍为应用程序模块就可以避免产生该内存泄漏。
另外调试时发现,
CSettingDlg的析构是在应用程序调用
AfxFreeLibrary()
卸载DLL时发生的,此时已经无法将ModuleState设置为应用程序模块。
3. 问题解决
采用另外的方法实现了单例模式,并在对话框OnDestroy时利用delete 销毁对话框,保证销毁时ModuleState仍指向应用程序模块,经运行验证,不再产生内存泄漏问题。解决方法如下:(未写出的代码同上)
class CSettingDlg: public CPropertySheet
{
static CSettingDlg * g_SettingDlg;
static CSettingDlg * GetInstance()
{
if(!g_SettingDlg)g_SettingDlg = new CSettingDlg;return g_SettingDlg;
}
afx void OnDestroy()
{
CPropertySheet::OnDestroy();
delete g_SettingDlg;
g_SettingDlg = 0;
}
}
CSettingDlg* CSettingDlg::
g_SettingDlg = 0;