一个在VC6.0环境下开发的MDI应用程序,用到了ATL,COM。每个View用到一个CWindowImpl类型的窗口,请问程序退出,或者View关闭时,应该如何销毁CWindowImpl的窗口呢?我尝试了好几种方法,都碰到不同的问题
第一种实现是处理WM_DESTROY消息,在OnDestroy中调用PostQuitMessage(0),但是该调用造成整个程序的退出,并且出现一大堆内存泄漏。第二种实现是在OnDestroy中调用DestroyWindow(),此时能够正常退出View,主程序退出时,主界面窗口能够正常关闭,但是程序并没有退出,仍然驻留在内存中,我用调试功能的break中断后发现,程序在CWinThread::PumpMessage()函数中死循环。请问大家正确的处理方法应该是怎样的呢?

解决方案 »

  1.   

    CWindowImpl::OnFinalMessage
    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.
      

  2.   

    回复ouyh12345,这个函数我重载过,实现如下:
    LRESULT CIdsUIClientSite::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
            m_hWnd = NULL;
    delete this;
    return 1;
    }同样会造成我前文中提到的主程序驻留内存中的现象。
    谢谢回复
      

  3.   

    回复ouyh12345,这个函数我重载过,实现如下:
    LRESULT CIdsUIClientSite::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
            m_hWnd = NULL;
    delete this;
    return 1;
    }同样会造成我前文中提到的主程序驻留内存中的现象。
    谢谢回复
      

  4.   

    google了一下,
    试试
    要销毁窗口,释放资源,在收到WM_DESTROY消息之后,在OnDestroy函数中做如下调用:    DestroyWindow();
         this->m_hWnd = NULL;     // DestroyWindow()函数没有将m_hWnd设定为NULL,这将导致出现
        // ERROR - Object deleted before window was destroyed还有
    如果需要销毁窗口,则进行收尾处理,调用虚拟函数:OnFinalMessage
      

  5.   

    如果还有程序并没有退出,仍然驻留在内存中的现象,则用windbg调试一下
    生成pdb符号文件,然后用windbg运行程序,然后关闭程序
    正常关闭,则在windbg里是断点的状态,否则,command的状态为忙,此时,可以暂停程序,
    然后看哪些模块、线程、句柄等还没有销毁
      

  6.   

    回复ouyh12345,你google到的那个调用DestroyWindow()的方法,是我写的,采用那种方法仍然会造成程序驻留内存的结果。
      

  7.   

    这个view是你的顶级窗口吗?确保在顶级窗口的OnFinalMessage重载函数里执行 PostQuitMessage(0); 否则消息循环不会退出
      

  8.   

    回jameshooo,昨天晚上我尝试了一下,发现问题不在如何销毁CWindowImpl。
    我的代码结构是这样的:
    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); 那句代码。问题是,现在我不知道怎么做了。
      

  9.   

    不要在MFC中使用ATL来创建窗口和控件,MFC中的窗口、控件、模块、线程等都有专门针对MFC才使用的状态属性,除非你确切知道怎么在ATL中添加这些MFC状态支持,否则还是建议使用MFC来创建OCX
      

  10.   

    回jameshooo,刚刚试验了一上午,基本找到问题的根源,就是因为前面帖子中提到的CIdsUIClientSite类没有销毁,导致pModuleState-> m_nObjectCount不为0,最后导致if (!AfxOleCanExitApp()) 条件为真,造成程序驻留内存了。CIdsUIClientSite类没有销毁的原因,是因为其引用计数一直大于0。它使用了WebBrowser组件,
    在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,如何实现我上述的要求呢?
      

  11.   

    用MFC使用ActiveX其实更简单,你把控件往窗口模板上拖就行了,MFC自动实现了容器,不需要你自己来实现容器需要的这些接口,只要在InitInstance里面调用一句AfxEnableControlContainer()就行了
      

  12.   

    ATL窗口不太好搞,建议看一《深入解析ATL》第9章中相关部分。
      

  13.   

    这不是窗口销毁的问题,应该是犯了几个COM和使用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);
      

  14.   

    解决方案2:
    其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));
      

  15.   

    在继承CWindowImpl<>模板类的窗口里面添加如下代码:virtual void OnFinalMessage(HWND /*hWnd*/)
    {
    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;
    }
      

  16.   

    ATL/WTL的框架设计的相当不错,消息循环封装的都设计的比较好。缺少文档,需要深入看源代码。
    《深入解析ATL》对于窗口编程来说,基本没什么用。
    Lz找一下WTL的文章看看或许比较有用。
      

  17.   

    不要在ATL中使用MFC来创建窗口和控件,如果你不是很精通的话
    ATL/WTL完全可以取代MFC,在Ax方面远远优于MFC。
      

  18.   

    CEmbed_WordDoc::~CEmbed_WordDoc()
    {
    //销毁进程,否则系统无法退出
    AfxOleUnlockApp();
    }