[WTL/ATL]_[初级]_[关于窗口子类析构时崩溃的原因]
Posted infoworld
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[WTL/ATL]_[初级]_[关于窗口子类析构时崩溃的原因]相关的知识,希望对你有一定的参考价值。
场景
-
在开发
WTL
程序时,会常用到非模式对话框用来作为进度条窗口,配合Tab
窗口可以做到进度条窗口只影响某个特定的父窗口,因此可能会创建多个对话框,并在进度结束后进行析构销毁。那么在自定义的对话框里析构时发生以下<atlwin.h>
的崩溃错误怎么回事?- 注意: 当前的
atl
版本是Microsoft Visual Studio 10.0\\VC\\atlmfc\\include
.
- 注意: 当前的
virtual ~CWindowImplRoot()
#ifdef _DEBUG
if(m_hWnd != NULL) // should be cleared in WindowProc
ATLTRACE(atlTraceWindowing, 0, _T("ERROR - Object deleted before window was destroyed\\n"));
ATLASSERT(FALSE);
#endif //_DEBUG
说明
- 看以上的代码,在父类
CWindowImplRoot
的析构函数里,不允许窗口句柄m_hWnd
有值,也就是必须要先销毁才可以继续析构。我们先看看CDialogImpl
的继承结构, 从CWindowImplRoot
开始的虚析构进行了m_hWnd
的值判断,前面的CDialogImpl
和CDialogImplBaseT
子类析构什么都不做。也就是之前说的,父类析构调用之前必须先销毁m_hWnd
, 为什么说销毁,因为对话框已经是顶级的窗口,不会有父窗口。
class ATL_NO_VTABLE CDialogImpl :
public CDialogImplBaseT< TBase >
virtual ~CDialogImplBaseT()
...
template <class TBase /* = CWindow */>
class ATL_NO_VTABLE CDialogImplBaseT :
public CWindowImplRoot< TBase >
virtual ~CWindowImplRoot()
- 销毁窗口使用DestroyWindow[1]函数,它会先递归销毁子窗口再销毁自身。所以对话框可以在析构里调用
DestroyWindow
函数,不必担心它有父窗口又销毁一次。可以在销毁前做一些操作, 比如先隐藏再销毁Timer
, 当然Windows
窗口句柄在销毁时会自动销毁属于它的Timer
,因此也可以不手动写.如下:
ProgressDialog::~ProgressDialog()
ShowWindow(SW_HIDE);
KillTimer(kLoadingTimer);
KillTimer(kProgressTimer);
DestroyWindow();
- 在调用
CDialogImpl::DestroyWindow
函数时, 它会调用Win32
的::DestroyWindow
函数。这个函数调用完之后,m_hWnd
的值为NULL
. 难道这个函数还能回填这个m_hWnd
值?这是不可能,因为传入这个函数的参数是m_hWnd
,而不是&m_hWnd
, 因此是在其他地方进行了设值。
BOOL DestroyWindow()
ATLASSERT(::IsWindow(m_hWnd));
#ifdef _DEBUG
ATLASSERT(!m_bModal); // must not be a modal dialog
#endif //_DEBUG
if (!::DestroyWindow(m_hWnd))
return FALSE;
return TRUE;
- 注意,这个
::DestroyWindow
函数调用后,会调用窗口处理函数CDialogImplBaseT< TBase >::DialogProc
,同步处理WM_DESTROY
消息,这里执行pThis->m_hWnd = NULL
, 也就是m_hWnd
设置为NULL
.这个DialogProc
会在窗口创建的时候通过StartDialogProc
传入.
HWND hWnd = ::CreateDialogParam(_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCE(static_cast<T*>(this)->IDD),
hWndParent, T::StartDialogProc, dwInitParam);
template <class TBase>
INT_PTR CALLBACK CDialogImplBaseT< TBase >::StartDialogProc(
_In_ HWND hWnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
CDialogImplBaseT< TBase >* pThis = (CDialogImplBaseT< TBase >*)_AtlWinModule.ExtractCreateWndData();
ATLASSERT(pThis != NULL);
if(!pThis)
return 0;
pThis->m_hWnd = hWnd;
// Initialize the thunk. This was allocated in CDialogImpl::DoModal or
// CDialogImpl::Create, so failure is unexpected here.
pThis->m_thunk.Init((WNDPROC)pThis->GetDialogProc(), pThis);
template <class TBase>
INT_PTR CALLBACK CDialogImplBaseT< TBase >::DialogProc(
if((pThis->m_dwState & WINSTATE_DESTROYED) && pThis->m_pCurrentMsg == NULL)
// clear out window handle
HWND hWndThis = pThis->m_hWnd;
pThis->m_hWnd = NULL;
pThis->m_dwState &= ~WINSTATE_DESTROYED;
- 对于作为子窗口的
CWindowImpl
子类,析构里不需要调用DestroyWindow
, 因为父窗口销毁时会把子窗口销毁。
图1:
参考
以上是关于[WTL/ATL]_[初级]_[关于窗口子类析构时崩溃的原因]的主要内容,如果未能解决你的问题,请参考以下文章
[WTL/ATL]_[初级]_[如何设置CEdit的文本框背景色和文字颜色]
[WTL/ATL]_[初级]_[如何设置CEdit的文本框背景色和文字颜色]
[WTL/ATL]_[初级]_[如何设置CEdit的文本框背景色和文字颜色]
[WTL/ATL]_[初级]_[微调控件CUpDownCtrl的使用]