CPrintDialog之内存与GDI对象泄漏

来源:互联网 发布:大数据时代txt百度网盘 编辑:程序博客网 时间:2024/06/04 18:47

        做过打印的同志们应该都有过CPrintDialog类吧,它用起来简单,省时间,如果不需要做非常专业的打印,那么用它就足够了。对于用完CPrintDialog后的内存与GDI对象的释放问题,我断断续续研究了好久了,下面的绪论不敢说正确,目的是与大家讨论,希望大家发现我的问题并指教,谢谢。

         注:本文讨论的都是在对话框里实现打印,也就是说,在没有任何MFC生成的代码支持的情况下做打印,这样也可以让我们更好的理解打印的实质。

网上关于CPrintDialog使用的例子,都像是一个人写的一样,基本上都是:
CPrintDialog SetupDlg(TRUE, PD_RETURNDC);
SetupDlg.DoModal();
get printer dc
StartDoc
StartPage
use dc
EndPage
EndDoc
end
...

上面的程序存在严重的资源泄漏,它没有释放SetupDlg.m_pd.hDevMode与SetupDlg.m_pd.hDevNames,MSDN中说:
If you do not supply your own handles in m_pd for the hDevMode and hDevNames members,
be sure to call the Windows function GlobalFree for these handles when you are done with the dialog box.
When using the framework's Print Setup implementation provided by CWinApp::OnFilePrintSetup, you do not have to free these handles.
注:m_pd为CPrintDialog类的一个成员变量,PRINTDLG类型。

上面的意思是,需要释放SetupDlg.m_pd.hDevMode与SetupDlg.m_pd.hDevNames,那么,上面的程序段应该加上:

if (SetupDlg.m_pd.hDevMode) //加判断是考虑到用户选择取消打印这种情况,下同
 ::GlobalFree(SetupDlg.m_pd.hDevMode);
if (SetupDlg.m_pd.hDevNames)
 ::GlobalFree(SetupDlg.m_pd.hDevNames);

是不是这样就行了呢,答案是否,请大家做下面的实验:
把下面两行程序放在一个按钮响应函数里面
CPrintDialog SetupDlg(TRUE, PD_RETURNDC);
SetupDlg.DoModal();
然后编译运行程序,打开任务管理器,点击查看-》选择列,然后选中“GDI对象”,这样,就可以在任务管理器里面看到程序使用的GDI对象了。
接下来,每点击一次按钮(注意要在打印设置框里选择确定),GDI对象会增加1(第一次点会增加几十个GDI,这是正常的,以后每次点击都会增加1)!

其实,增加的这个GDI是SetupDlg.m_pd.hDC这个打印机DC没有释放,解决的办法就是:
if (SetupDlg.m_pd.hDC)
 DeleteObject(SetupDlg.m_pd.hDC);
或:
CDC dc;
dc.Attach(SetupDlg.m_pd.hDC);
use dc;
这样,在dc被析构的时候,自动会DeleteObject掉SetupDlg.m_pd.hDC。

大家注意到了CPrintDialog SetupDlg(TRUE, PD_RETURNDC);这行程序的PD_RETURNDC参数了吗?MSDN中的解释如下:
PD_RETURNDC
Causes PrintDlg to return a device context matching the selections the user made in the dialog box. The device context is returned in hDC.
同时,对m_pd.hDC解释如下:
hDC
Handle to a device context or an information context, depending on whether the Flags member specifies the PD_RETURNDC or PC_RETURNIC flag.
If neither flag is specified, the value of this member is undefined. If both flags are specified, PD_RETURNDC has priority.

上面的意思是,如果设置了PD_RETURNDC参数,hDC将返回打印机DC,如果设置PC_RETURNIC,hDC将返回打印机context,如果两者都不设置,hDC无效!
那么大家在上面的按钮响应程序里,改为如下:
CPrintDialog PrintDlg(FALSE);
PrintDlg.DoModal();
if (PrintDlg.m_pd.hDevMode)
 ::GlobalFree(PrintDlg.m_pd.hDevMode);
if (PrintDlg.m_pd.hDevNames)
 ::GlobalFree(PrintDlg.m_pd.hDevNames);
同样用任务管理器来观看GDI数量,你会发现,每点击按钮一次,GDI对象也加1!
后来我发再,不设置PC_RETURNIC,hDC也会返回打印机DC,这个对象也需要释放,你可以做如下实验:
CPrintDialog PrintDlg(FALSE);
ASSERT(0 == PrintDlg.m_pd.hDC);
PrintDlg.DoModal();
ASSERT(0 != PrintDlg.m_pd.hDC);
if (PrintDlg.m_pd.hDevMode)
 ::GlobalFree(PrintDlg.m_pd.hDevMode);
if (PrintDlg.m_pd.hDevNames)
 ::GlobalFree(PrintDlg.m_pd.hDevNames);
你会发再,两个ASSERT都不会出错,可见hDC中的确生成了一个打印机DC对象(当然,在打印对话框里,需要选择确定按钮)!
所以再加一行程序:
if (PrintDlg.m_pd.hDC)
 DeleteObject(PrintDlg.m_pd.hDC);
GDI对象将不会无限制的增加了(第一次会猛增,这是正常现象,只要不无限制增加就行了),各位可以试试。从这里看来,不管加不加PD_RETURNDC,都要释放打印机DC,难道我们被MSDN忽悠了,还是我没看懂英文的意思?

以上完全属于个人观点,正确与否,欢迎大家评论! 

原创粉丝点击