DLL中单例模式存在的非模态CPropertySheet对话框在程序关闭时产生内存泄漏

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 

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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值