一、DestroyWindow:
The MFC framework manages window destruction as well as creation for those windows associated with framework documents and views. If you create additional windows, you are responsible for destroying them.
In the framework, when the user closes the frame window, the window's default OnClose handler calls DestroyWindow. The last member function called when the Windows window is destroyed is OnNcDestroy, which does some cleanup, calls the Default member function
to perform Windows cleanup, and lastly calls the virtual member function PostNcDestroy. The CFrameWnd implementation ofPostNcDestroy deletes the C++ window object. You should never use the C++delete operator on a frame window.
Use DestroyWindow instead.
When the main window closes, the application closes. If there are modified unsaved documents, the framework displays a message box to ask if the documents should be saved and ensures that the appropriate documents are saved if necessary.
总之:函数的功能就是销毁指定的窗口。通过发送WM_DESTROY 消息和 WM_NCDESTROY 消息使窗口无效并移除其键盘焦点。这个函数还销毁窗口的菜单,清空线程的消息队列,销毁与窗口过程相关的定时器,解除窗口对剪贴板的拥有权,打断剪贴板器的查看链。
原型:
BOOL DestroyWindow(HWND hWnd);
返回值:
如果函数成功,返回值为非零:如果函数失败,返回值为零。若想获得更多错误信息,请调用GetLastError函数。
备注说明:
一个线程不能使用本函数销毁别的线程创建的窗口
如果这个窗口是一个不具有WS_EX_NOPARENTNOTIFY 样式的子窗口,则销毁窗口时将发WM_PARENTNOTIFY 消息给其父窗口。
===========================================================================================
二、CWnd::DestroyWindow
MSDN:
Destroys the Windows window attached to the CWnd object.
销毁Windows窗口附加到 CWnd 对象。
原型:
virtual BOOL DestroyWindow( );
返回值:
非零,则销毁窗口;否则为0
备注说明:
1、DestroyWindow 成员函数将相应信息到窗口停用并移除输入焦点。 如果 CWnd 是在浏览器链的顶部,它销毁windows的菜单中,对于应用程序队列,销毁处理计时器,还移除剪贴板所有权,以及中断剪贴板浏览器链。 其发送 WM_DESTROY 和 WM_NCDESTROY 信息到窗口。 但不销毁 CWnd 对象。
2、DestroyWindow 是进行清理一个位置容纳器。 由于 DestroyWindow 是虚函数,在所有 CWnd-在选件类视图的派生类公开。 但,即使您重写在您的 CWnd的此功能的派生类,DestroyWindow 不一定调用。 如果 DestroyWindow 在MFC代码未调用,则必须显式调用它在代码中,如果您希望此调用。
3、假定,例如,重写了 CView的 DestroyWindow 派生类。 因为MFC源代码不对 DestroyWindow 其任何 CFrameWnd派生类,重写的 DestroyWindow 不会调用,除非您显式调用它。
4、如果窗口是任何窗口的父级,自动销毁。这些子窗口,当销毁时父窗口。 DestroyWindow 成员函数首先然后销毁子窗口。
5、DestroyWindow 成员函数来销毁 CDialog::Create创建的无模式对话框。
6、如果销毁的 CWnd 是子窗口,并没有 WS_EX_NOPARENTNOTIFY 样式设置,则 WM_PARENTNOTIFY 发送到父级。
三、CWnd::OnClose
WM_CLOSE 的响应函数
afx_msg void OnClose( );
说明:
The default implementation calls DestroyWindow.
默认实现调用 DestroyWindow
四、CWnd::OnDestroy 框架调用该成员函数通知 CWnd 对象销毁它。
WM_DESTROY 的响应函数
afx_msg void OnDestroy( );
说明:
OnDestroy is called after the CWnd object is removed from the screen.
OnDestroy is called first for the CWnd being destroyed, then for the child windows ofCWnd as they are destroyed. It can be assumed that all child windows still exist whileOnDestroy runs.
If the CWnd object being destroyed is part of the Clipboard-viewer chain (set by calling the SetClipboardViewer member function), theCWnd must remove itself from the Clipboard-viewer chain by calling the ChangeClipboardChain
member function before returning from theOnDestroy function.
OnDestroy在窗口对象离开屏幕后调用
当销毁时,OnDestroy 首先调用为销毁的 CWnd,然后为子窗口 CWnd 它们。 可以假定,所有子窗口仍存在,则 OnDestroy 运行时。
如果销毁的 CWnd 对象是剪贴板浏览器链的一部分(设置通过调用 SetClipboardViewer 成员函数),CWnd 必须从剪贴板浏览器链中移除其自身通过调用 ChangeClipboardChain 成员函数中返回从 OnDestroy 功能之前。
=============================================================================================
下面介绍下三个关闭消息:
WM_CLOSE:
在系统菜单里选择了“关闭”或者点击了窗口右上角的“X”按钮,你的窗口过程就会收到WM_CLOSE。DefWindowProc对WM_CLOSE的处理是调用DestroyWindow。当然,你可以不让DefWindowProc处理,而是自己处理,例如询问用户是否保存更改等。如果用户选择“取消”,
你忽略此消息,那么程序照常运行;如果用户确认要退出,你就调用DestroyWindow。
WM_DESTROY:
接下来,DestroyWindow完成窗口的清理工作,最后像窗口过程发送WM_DESTROY。对于 WM_DESTROY,DefWindowProc不会处理。也就是说,你如果不处理这个消息,虽然你的窗口已经销毁,但进程并不会结束。一般处理 WM_DESTROY时都是释放资源(例如申请的内存等),
然后调用PostQuitMessage。
WM_QUIT:
PostQuitMessage会发送WM_QUIT给消息队列。注意,WM_QUIT永远不会到达窗口过程,因为GetMessage得到WM_QUIT后就会返回FALSE,从而结束消息循环,最后进程结束,程序退出。
假设使用者执行HELLOWIN,并且使用者最终单击了 Close按钮,或者假设用键盘或鼠标从系统菜单中选择了Close,DefWindowProc处理这一键盘或者鼠标输入,在检测到使用者选择了Close选项之后,它给窗口消息处理程序发送一条WM_SYSCOMMAND消息。WndProc将这个消息
传给DefWindowProc。 DefWindowProc给窗口消息处理程序发送一条WM_CLOSE消息来响应之。WndProc再次将它传给DefWindowProc。 DestroyWindow呼叫DestroyWindow来响应这条WM_CLOSE消息。DestroyWindow导致Windows给窗口消息处理程序发送一条WM_DESTROY消息。WndProc再呼叫PostQuitMessage,将一条WM_QUIT消息放入消息队列中,以此来响应此消息。这个消息导致WinMain中的消息循环终止,然后程序结束。
言而简之:
(1)用户点击退出按钮,发送了WM_CLOSE消息
(2)在WM_CLOSE消息的处理函数中,调用DestroyWindow()
(3)在DestroyWindow()中发送了WM_DESTROY消息
(4)在WM_DESTROY消息中调用PostQuitMessage(),发送WM_QUIT消息,结束消息循环
综上,程序先调用OnClose()(也可能不调用),然后调用OnDestroy()(必调用),
所以,如果要进行程序结束时的清理工作,应该在OnDestroy()中,而不是在OnClose(),否则就有可能会出现内存泄漏的危险了!
=============================================================================================
五、WM_QUIT
The WM_QUIT message indicates a request to terminate an application and is generated when the application calls thePostQuitMessage function. It causes theGetMessage
function to return zero.
wParam
Specifies the exit code given in the PostQuitMessage function.
lParam
This parameter is not used.
Return Value
This message does not have a return value because it causes the message loop to terminate before the message is sent to the application's window procedure.
这个消息没有返回值,因为它使消息循环终止在消息被发送到应用程序的窗口程序
注意事项:
The WM_QUIT message is not associated with a window and therefore will never be received through a window's window procedure. It is retrieved only by theGetMessage orPeekMessage
functions.
Do not post the WM_QUIT message using the PostMessage function; usePostQuitMessage.
六、总结,比较异同
1、对话框中 点“确定”、“取消”时的关闭路由为
OnOK()或OnCancel() ---> EndDialog() ---> DestroyWindow() ---> OnDestroy() ---> PostNcDestroy()
点“关闭”标题栏按钮的关闭路由为
OnClose()---> DestroyWindow() ---> OnDestroy() ---> PostNcDestroy()
所以OnClose()并不是关闭路由的必经路径,OnDestroy() 才是程序关闭的必经路径,因此重写OnDestroy() ,把我需要在程序结束的操作全部放到了这个函数里面。
2、在 OnDestroy 中调用 PostQuitMessage() 发送 WM_QUIT 消息,结束消息循环,结束程序。
3、DestroyWindow 在运用过程分析
1、【单窗口】通过new创建了一个窗口对象pWnd,然后pWnd->Create。则销毁窗口的调用次序
1.1、手动调用 pWnd->DestroyWindow(),DestroyWindow会发送WM_DESTROY
1.2、WM_DESTROY对应的消息处理函数是OnDestroy()
1.3、DestroyWindow会发送WM_NCDESTROY,WM_NCDESTROY对应的消息处理函数是OnNcDestroy
1.4、OnNcDestroy最后会调用PostNcDestroy-------------------------------可以在 \src\WINCORE.CPP中 查看源码
通过这种方式,窗口对象对应的窗口和窗口对象本身都被释放了
2、【含子窗口】则调用父窗口的DestroyWindow时,它会向子窗口发送WM_DESTROY和WM_NCDESTROY消息。
PostNcDestroy:源码
void CWnd::PostNcDestroy()
{
// default to nothing
}
void CView::PostNcDestroy()
{
// default for views is to allocate them on the heap
// the default post-cleanup is to 'delete this'.
// never explicitly call 'delete' on a view
delete this;
}
void CFrameWnd::PostNcDestroy()
{
// default for frame windows is to allocate them on the heap
// the default post-cleanup is to 'delete this'.
// never explicitly call 'delete' on a CFrameWnd, use DestroyWindow instead
delete this;
}
很明显:delete会对DestroyWindow产生影响
delete会引起析构函数,CWnd的析构函数中有对DestroyWindow的调用
CWnd::~CWnd()
{
if (m_hWnd != NULL &&
this != (CWnd*)&wndTop && this != (CWnd*)&wndBottom &&
this != (CWnd*)&wndTopMost && this != (CWnd*)&wndNoTopMost)
{
TRACE(_T("Warning: calling DestroyWindow in CWnd::~CWnd; ")
_T("OnDestroy or PostNcDestroy in derived class will not be called.\n"));
DestroyWindow();
}
#ifndef _AFX_NO_OCC_SUPPORT
// cleanup control container,
// including destroying controls
delete m_pCtrlCont;
// cleanup control site
if (m_pCtrlSite != NULL && m_pCtrlSite->m_pWndCtrl == this)
m_pCtrlSite->m_pWndCtrl = NULL;
#endif
}
CDialog的析构函数 有对DestroyWindow的调用,但条件比较松,只需要m_hWnd != NULL。DoModal也会调用DestroyWindow。
CDialog::~CDialog()
{
if (m_hWnd != NULL)
{
TRACE0("Warning: calling DestroyWindow in CDialog::~CDialog --\n");
TRACE0("\tOnDestroy or PostNcDestroy in derived class will not be called.\n");
DestroyWindow();
}
}
int CDialog::DoModal()
{
...
// destroy modal window
DestroyWindow();
PostModal();
...
}
CFrameWnd------------OnClose中会调用DestroyWindow,但其析构中不会调用DestroyWindow
void CFrameWnd::OnClose()
{
....
// then destroy the window
DestroyWindow();
}
CFrameWnd::~CFrameWnd()
{
RemoveFrameWnd();
if( AfxGetThreadState()->m_pRoutingFrame == this )
AfxGetThreadState()->m_pRoutingFrame = NULL;
if (m_phWndDisable != NULL)
delete[] (void*)m_phWndDisable;
}
CView的析构没有调用DestroyWindow。
CView::~CView()
{
if (AfxGetThreadState()->m_pRoutingView == this)
AfxGetThreadState()->m_pRoutingView = NULL;
if (m_pDocument != NULL)
m_pDocument->RemoveView(this);
}
以SDI程序的销毁过程为例:
点击退出按钮,CMainFrame会收到WM_CLOSE消息。
CFrameWnd(CMainFrame的父类)间接会调用CWnd::DestroyWindow;
它首先向CMyView发送WM_DESTORY和WM_NCDESTROY消息,并引发相应的处理函数;
然后向CXXXDlg发送WM_DESTORY和WM_NCDESTROY消息,并引发相应的处理函数;
然后向CMyWnd发送WM_DESTORY和WM_NCDESTROY消息,并引发相应的处理函数。
DestroyWindow 执行过程:
1、调用CMainFrame::DestroyWindow 调用CWnd::DestroyWindow (虚函数)
2、调用基类的 CFrameWnd::OnDestroy
3、 CMyView::OnDestroy
4、 CMyWnd::OnDestroy
5、 CXXXDlg::OnDestroy
6、CMyWnd::PostNcDestroy
7、CMyWnd的析构
8、CXXXDlg::OnDestroy // 执行基类 CWnd::PostNcDestroy 虚函数 也可以重写
9、CXXXDlg的析构
10、CMyView::PostNcDestroy
11、CMyView的析构
12、CMainFrame的析构
13、CMainFrame::DestroyWindow退出
上面情况是假设我们在CMyWnd和CXXXDlg的PostNcDestroy中添加了delete this。如果没有添加,则7,9不会执行。
因为CView::PostNcDestroy中调用了delete this,所以然后会执行CMyView的析构操作。
因为CFrameWnd::PostNcDestroy中调用了delete this,所以最后执行CMainFrame的析构操作。
如果自己的CMyDlg和CMyWnd在PostNcDestroy中有delete this;则二者会被析构。否则内存泄漏。当然delete也可以放在CMyView的析构中做,只是不够好。
总结:
可以有两种方法销毁窗口对象对应的窗口和释放窗口对象指针。一种是通过DestroyWindow。这是比较好的方法,因为最后MFC会自动相应WM_CLOSE导致CFrameWnd::DestroyWindow被调用,然后会一次释放所有子窗口的句柄。用户需要做的是在PostNcDestroy中释放堆窗口对象指针。但因为某些对象是在栈中申请的,所以delete this可能出错。这就要保证写程序时自己创建的窗口尽量使用堆申请。
另一种是delete。Delete一个窗口对象指针,有的窗口类(如CWnd,CDialog)会间接调用DestroyWindow,有的窗口类(如CView,CFrameWnd)不会调用DestroyWindow。所以要小心应对。
===========================================================================================================================
附其他资料:作者:闻怡洋
一个MFC窗口对象包括两方面的内容:
一是窗口对象封装的窗口,即存放在m_hWnd成员中的HWND(窗口句柄),
二是窗口对象本身是一个C++对象。要删除一个MFC窗口对象,应该先删除窗口对象封装的窗口,然后删除窗口对象本身。
删除窗口最直接方法是调用CWnd::DestroyWindow或::DestroyWindow,前者封装了后者的功能。前者不仅会调用后者,而且会使成员m_hWnd保存的HWND无效(NULL)。如果DestroyWindow删除的是一个父窗口或拥有者窗口,则该函数会先自动删除所有的子窗口或被拥有者,然后再删除父窗口或拥有者。
在一般情况下,在程序中不必直接调用DestroyWindow来删除窗口,因为MFC会自动调用DestroyWindow来删除窗口。
例如,当用户退出应用程序时,会产生WM_CLOSE消息,该消息会导致MFC自动调用CWnd::DestroyWindow来删除主框架窗口,当用户在对话框内按了OK或Cancel按钮时,MFC会自动调用CWnd::DestroyWindow来删除对话框及其控件。
窗口对象本身的删除则根据对象创建方式的不同,分为两种情况。在MFC编程中,会使用大量的窗口对象,有些窗口对象以变量的形式嵌入在别的对象内或以局部变量的形式创建在堆栈上,有些则用new操作符创建在堆中。
1>>对于一个以变量形式创建的窗口对象,程序员不必关心它的删除问题,因为该对象的生命期总是有限的,若该对象是某个对象的成员变量,它会随着父对象的消失而消失,若该对象是一个局部变量,那么它会在函数返回时被清除。
2>>对于一个在堆中动态创建的窗口对象,其生命期却是任意长的。初学者在学习C++编程时,对new操作符的使用往往不太踏实,因为用new在堆中创建对象,就不能忘记用delete删除对象。读者在学习MFC的例程时,可能会产生这样的疑问,为什么有些程序用new创建了一个窗口对象,却未显式的用delete来删除它呢?问题的答案就是有些MFC窗口对象具有自动清除的功能。
自动清除,当调用CWnd::DestroyWindow或::DestroyWindow删除一个窗口时,被删除窗口的PostNcDestroy成员函数会被调用。缺省的PostNcDestroy什么也不干,但有些MFC窗口类会覆盖该函数并在新版本的PostNcDestroy中调用delete this来删除对象,从而具有了自动清除的功能。此类窗口对象通常是用new操作符创建在堆中的,但程序员不必操心用delete操作符去删除它们,因为一旦调用DestroyWindow删除窗口,对应的窗口对象也会紧接着被删除。
不具有自动清除功能的窗口类如下所示。这些窗口对象通常是以变量的形式创建的,无需自动清除功能。
所有标准的Windows控件类。
1. 从CWnd类直接派生出来的子窗口对象(如用户定制的控件)。
2. 切分窗口类CSplitterWnd。
3. 缺省的控制条类(包括工具条、状态条和对话条)。
4. 模态对话框类。
具有自动清除功能的窗口类如下所示,这些窗口对象通常是在堆中创建的。
1. 主框架窗口类(直接或间接从CFrameWnd类派生)。
2. 视图类(直接或间接从CView类派生)。
读者在设计自己的派生窗口类时,可根据窗口对象的创建方法来决定是否将窗口类设计成可以自动清除的。
例如,对于一个非模态对话框来说,其对象是创建在堆中的,因此应该具有自动清除功能。
综上所述,对于MFC窗口类及其派生类来说,在程序中一般不必显式删除窗口对象。也就是说,既不必调用DestroyWindow来删除窗口对象封装的窗口,也不必显式地用delete操作符来删除窗口对象本身。只要保证非自动清除的窗口对象是以变量的形式创建的,自动清除的窗口对象是在堆中创建的,MFC的运行机制就可以保证窗口对象的彻底删除。
如果需要手工删除窗口对象,则应该先调用相应的函数(如CWnd::DestroyWindow)删除窗口,然后再删除窗口对象.
对于以变量形式创建的窗口对象,窗口对象的删除是框架自动完成的.
对于在堆中动态创建了的非自动清除的窗口对象,必须在窗口被删除后,显式地调用delete来删除对象(一般在拥有者或父窗口的析构函数中进行).
对于具有自动清除功能的窗口对象,只需调用CWnd::DestroyWindow即可删除窗口和窗口对象。
注意,对于在堆中创建的窗口对象,不要在窗口还未关闭的情况下就用delete操作符来删除窗口对象.