一个在VC6.0环境下开发的MDI应用程序,用到了ATL,COM。每个View用到一个CWindowImpl类型的窗口,请问程序退出,或者View关闭时,应该如何销毁CWindowImpl的窗口呢?我尝试了好几种方法,都碰到不同的问题
第一种实现是处理WM_DESTROY消息,在OnDestroy中调用PostQuitMessage(0),但是该调用造成整个程序的退出,并且出现一大堆内存泄漏。第二种实现是在OnDestroy中调用DestroyWindow(),此时能够正常退出View,主程序退出时,主界面窗口能够正常关闭,但是程序并没有退出,仍然驻留在内存中,我用调试功能的break中断后发现,程序在CWinThread::PumpMessage()函数中死循环。请问大家正确的处理方法应该是怎样的呢?
第一种实现是处理WM_DESTROY消息,在OnDestroy中调用PostQuitMessage(0),但是该调用造成整个程序的退出,并且出现一大堆内存泄漏。第二种实现是在OnDestroy中调用DestroyWindow(),此时能够正常退出View,主程序退出时,主界面窗口能够正常关闭,但是程序并没有退出,仍然驻留在内存中,我用调试功能的break中断后发现,程序在CWinThread::PumpMessage()函数中死循环。请问大家正确的处理方法应该是怎样的呢?
virtual void OnFinalMessage( HWND hWnd );ParametershWnd[in] A handle to the window being destroyed.ResCalled after receiving the last message (typically WM_NCDESTROY). The default implementation of OnFinalMessage does nothing, but you can override this function to handle cleanup before destroying a window. If you want to automatically delete your object upon the window destruction, you can call delete this; in this function.
LRESULT CIdsUIClientSite::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
m_hWnd = NULL;
delete this;
return 1;
}同样会造成我前文中提到的主程序驻留内存中的现象。
谢谢回复
LRESULT CIdsUIClientSite::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
m_hWnd = NULL;
delete this;
return 1;
}同样会造成我前文中提到的主程序驻留内存中的现象。
谢谢回复
试试
要销毁窗口,释放资源,在收到WM_DESTROY消息之后,在OnDestroy函数中做如下调用: DestroyWindow();
this->m_hWnd = NULL; // DestroyWindow()函数没有将m_hWnd设定为NULL,这将导致出现
// ERROR - Object deleted before window was destroyed还有
如果需要销毁窗口,则进行收尾处理,调用虚拟函数:OnFinalMessage
生成pdb符号文件,然后用windbg运行程序,然后关闭程序
正常关闭,则在windbg里是断点的状态,否则,command的状态为忙,此时,可以暂停程序,
然后看哪些模块、线程、句柄等还没有销毁
我的代码结构是这样的:
MDI的框架,每个View会使用一个继承自CWinodowImpl的窗口类,该类定义如下:
class ATL_NO_VTABLE CIdsUIClientSite :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CIdsUIClientSite, &CLSID_IdsUIClientSite>,
public IDispatchImpl<IIdsUIClientSite, &IID_IIdsUIClientSite, &LIBID_IdsViewer>,
public CWindowImpl<CIdsUIClientSite>,
public IOleClientSite,
public IOleInPlaceSite,
public IDocHostUIHandler该类实现了一个创建窗口的接口,其中一个成员函数实现如下,用于创建窗口:
STDMETHODIMP CIdsUIClientSite::CreateHostWnd(HWND hParentWnd)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState()) ASSERT(hParentWnd!=NULL);
m_hParentWnd = hParentWnd; // hParentWnd是MDI的View的HWND RECT rc;
::GetClientRect(m_hParentWnd, &rc); this->Create(hParentWnd,rc);
this->ShowWindow(SW_NORMAL); return S_OK;
}通过上述接口成员函数设定了该CWindowImpl窗口的父窗口是MDI的View。在CIdsUIClientSite的OnCreate消息处理函数中,我使用了WebBrowser组件,代码如下:
HRESULT hr = CoCreateInstance(CLSID_WebBrowser,
NULL,
CLSCTX_INPROC,
IID_IWebBrowser,
(void**)&m_spWebBrowser);
m_spInPlaceObject = m_spWebBrowser;
spOleObject = m_spWebBrowser; hr = spOleObject->SetClientSite(this); // 问题在这里
if (FAILED(hr))
{
MessageBox(_T("SetClientSite failed"));
return FALSE;
}
现在实验的结果是这样,
如果将上述代码中spOleObject->SetClientSite(this); 去掉,程序就能正常退出,如果没有去掉,程序将驻留内存。
后来我跟踪发现:
在CFrameWnd::OnClose()函数中有如下代码段:
// don't exit if there are outstanding component objects
if (!AfxOleCanExitApp()) // 这里应该条件为FALSE,Frame才能正常退出
{
// take user out of control of the app
AfxOleSetUserCtrl(FALSE); // don't destroy the main window and close down just yet
// (there are outstanding component (OLE) objects)
return;
}在AfxOleCanExitApp()判断结果为FALSE,所以造成了程序驻留内存。跟踪进入AfxOleCanExitApp()发现BOOL AFXAPI AfxOleCanExitApp()
{
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
return pModuleState->m_nObjectCount == 0;
}pModuleState->m_nObjectCount为1,此时应该看出就是WebBrowser组件没有销毁,原因就是spOleObject->SetClientSite(this); 那句代码。问题是,现在我不知道怎么做了。
在CIdsUIClientSite的成员函数中调用spOleObject->SetClientSite(this); 或者spOleObject->DoVerb等函数都会增加CIdsUIClientSite的引用计数。问题是程序退出时,似乎WebBrowser组件并没有减少CIdsUIClientSite类的引用计数,最后造成退出时该值大于0,最后造成pModuleState-> m_nObjectCount不为0。我采用的笨办法,是CIdsUIClientSite类自己维护一个引用计数m_lLockCount,每调用一次SetClientSite之类的函数,该m_lLockCount增加,最后在OnFinalMessage实现如下代码: while(m_lLockCount--)
{
this->Release();
}这种办法能够确保CIdsUIClientSite的引用计数在最后时刻为0。
虽然问题解决了,现在还有写比较模糊的是,为什么WebBrowser组件在销毁时不减少其已经使用的COM组件(这里就是CIdsUIClientSite类)的引用计数。另外,jameshooo你说不要在MFC中使用ATL来创建窗口,我现在的目的是要在View里Host一个WebBrowser组件,并且需要对其进行一定程度的定制,而根据WebBrowser组件的定制规则,本质就是实现一些接口。
要实现这些接口,不可避免的要使用到ATL,而要实现IOleClientSite等接口,必须要实现IOleWindow,所以造成了我使用CWindowImpl来实现窗口。如果不采用ATL,如何实现我上述的要求呢?
你的目的是想用webbrowser control来做view。解决方案1:
//使用ATL库做的activeX控件通用的host(container)
#include <atlhost.h>//用CAxWindow取代CWindow
class CEditorView :
public CWindowImpl<CEditorView,CAxWindow>//在CEditorView的create方法中的window text属性直接输入http地址即可创建浏览器。
pView->Create(..., rcDefault, L"about:blank",... )//使用CAxWindow的QueryControl直接得到浏览器接口
HRESULT hRet=QueryControl(__uuidof(IWebBrowser2),(void**)&spUnk);
其1,你使用ATL的方法不对,你的类并不是最终的COM类,而是最终COM类CComObject<T>的基类。参考CAxWindowHost的实现,以及他的使用方法。
其2,导致处于错的原因相信是webbrowser调用了两个不配对的Addref和Release。
hr = spOleObject->SetClientSite(this),这句话传this是语义模糊的。特别是在这种MFC和ATL混用
的场合很可能MFC的某个基类实现了IUnknown而ATL相关的类也有一份实现。
至少应该应该这样:
hr = spOleObject->SetClientSite(static_cast<IOleClientSite*> (this));
{
delete this;
}对于new的窗口来说不会造成内存泄露你看一下atlwin.h中的一段代码LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// ...
// 省略上面n行
if((pThis->m_dwState & WINSTATE_DESTROYED) && pThis->m_pCurrentMsg == NULL)
{
// clear out window handle
HWND hWnd = pThis->m_hWnd;
pThis->m_hWnd = NULL;
pThis->m_dwState &= ~WINSTATE_DESTROYED;
// clean up after window is destroyed
pThis->OnFinalMessage(hWnd);
}
return lRes;
}
《深入解析ATL》对于窗口编程来说,基本没什么用。
Lz找一下WTL的文章看看或许比较有用。
ATL/WTL完全可以取代MFC,在Ax方面远远优于MFC。
{
//销毁进程,否则系统无法退出
AfxOleUnlockApp();
}