[WTL/ATL]_[初级]_[关于窗口子类析构时崩溃的原因]

Posted infoworld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[WTL/ATL]_[初级]_[关于窗口子类析构时崩溃的原因]相关的知识,希望对你有一定的参考价值。

场景

  1. 在开发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
    

说明

  1. 看以上的代码,在父类CWindowImplRoot的析构函数里,不允许窗口句柄m_hWnd有值,也就是必须要先销毁才可以继续析构。我们先看看CDialogImpl的继承结构, 从CWindowImplRoot开始的虚析构进行了m_hWnd的值判断,前面的CDialogImplCDialogImplBaseT子类析构什么都不做。也就是之前说的,父类析构调用之前必须先销毁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()
  1. 销毁窗口使用DestroyWindow[1]函数,它会先递归销毁子窗口再销毁自身。所以对话框可以在析构里调用DestroyWindow函数,不必担心它有父窗口又销毁一次。可以在销毁前做一些操作, 比如先隐藏再销毁Timer, 当然Windows窗口句柄在销毁时会自动销毁属于它的Timer,因此也可以不手动写.如下:
ProgressDialog::~ProgressDialog()

	ShowWindow(SW_HIDE);
	KillTimer(kLoadingTimer);
	KillTimer(kProgressTimer);
	DestroyWindow();

  1. 在调用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;
	
  1. 注意,这个::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;
  1. 对于作为子窗口的CWindowImpl子类,析构里不需要调用DestroyWindow, 因为父窗口销毁时会把子窗口销毁。

图1:

参考

  1. DestroyWindow

以上是关于[WTL/ATL]_[初级]_[关于窗口子类析构时崩溃的原因]的主要内容,如果未能解决你的问题,请参考以下文章

[WTL/ATL]_[初级]_[如何设置CEdit的文本框背景色和文字颜色]

[WTL/ATL]_[初级]_[如何设置CEdit的文本框背景色和文字颜色]

[WTL/ATL]_[初级]_[如何设置CEdit的文本框背景色和文字颜色]

[WTL/ATL]_[初级]_[微调控件CUpDownCtrl的使用]

[WTL/ATL]_[初级]_[使用虚拟列表视图来解决新增大量数据卡顿问题]

[WTL/ATL]_[初级]_[使用虚拟列表视图来解决新增大量数据卡顿问题]