使用自定义 IDocHostUIHandler 和关闭窗口时崩溃

Posted

技术标签:

【中文标题】使用自定义 IDocHostUIHandler 和关闭窗口时崩溃【英文标题】:Crashes when using custom IDocHostUIHandler and closing window 【发布时间】:2020-11-10 01:24:01 【问题描述】:

我使用附加到 TWebBrowser 的自定义 IDocHostUIHandler(在设计模式下)。有用。但是,当程序退出(或使用它的窗口关闭)时,它会崩溃。我想某些东西没有被正确删除/销毁。我假设自定义 IDocHostUIHandler 在销毁后仍在使用中,因此我应该先以某种方式销毁 Web 浏览器(或分离自定义 .

所以我的问题是 - 我如何正确销毁自定义 IDocHostUIHandler 以便在现有程序不会崩溃或分离自定义 IDocHostUIHandler 时?

如果我在构造函数中删除diCustDoc->SetUIHandler(piDocUIHandler),它不会崩溃,所以问题肯定是由附加自定义IDocHostUIHandler引起的。

在上述Release()(在析构函数中)崩溃之前设置diCustDoc->SetUIHandler(NULL);

我的自定义IDocHostUIHandler(替换弹出菜单并自定义GetHostInfo)。

    class TDocUIHandler : public ::IDocHostUIHandler
        
        private:
            ULONG        RefCount;
            TPopupMenu* pPopupMenu;

        public:
            //TDocUIHandler()                     : RefCount(0)                         
            TDocUIHandler(TPopupMenu* fPopupMenu) : RefCount(0), pPopupMenu(fPopupMenu) 

            // IUnknown method
            HRESULT __stdcall QueryInterface(REFIID riid, void **ppv)
                
                if (!ppv) return E_POINTER;

                if        (IsEqualIID(riid, IID_IUnknown))          *ppv = static_cast<IUnknown*>(this);
                else if (IsEqualIID(riid, ::IID_IDocHostUIHandler)) *ppv = static_cast< ::IDocHostUIHandler*>(this);
                else                                                *ppv = NULL;

                if (*ppv)
                    
                    AddRef();                                                // Used the first time so increase the RefCount
                    return S_OK;
                    

                return E_NOINTERFACE;
                

            ULONG __stdcall AddRef()
                
                return (ULONG) InterlockedIncrement((long*)&RefCount);
                

            ULONG __stdcall Release()
                
                ULONG res = (ULONG) InterlockedDecrement((long*)&RefCount);
                if (res == 0) delete this;

                return res;
                

            // Returning S_OK tells the web browser that it need not display its
            // own context menu, presumably because the application hosting it has
            // displayed its own menu to replace it.
            // Since our host does not display any, no context menu is shown.

            STDMETHOD(ShowContextMenu)(         /* [in] */ DWORD dwID,
                                                /* [in] */ POINT __RPC_FAR *ppt,
                                                /* [in] */ IUnknown __RPC_FAR *pcmdtReserved,
                                                /* [in] */ IDispatch __RPC_FAR *pdispReserved)
                
                // https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa753264(v%3Dvs.85)
                switch (dwID)
                    
                    default:                    break;
                    case CONTEXT_MENU_DEFAULT:
                    case CONTEXT_MENU_IMAGE:    pPopupMenu->Popup(ppt->x, ppt->y);
                                                break;
                    

                return S_OK;
                

            STDMETHOD(GetHostInfo)(                /* [out][in] */ DOCHOSTUIINFO __RPC_FAR *pInfo)
                
                if (pInfo == NULL) return E_POINTER;

                pInfo->cbSize = sizeof(DOCHOSTUIINFO);
                pInfo->pchHostCss       = NULL;
                pInfo->pchHostNS        = NULL;
                pInfo->dwDoubleClick    = ::DOCHOSTUIDBLCLK_DEFAULT;  // default action

                pInfo->dwFlags          = 0
                                        | ::DOCHOSTUIFLAG_DIV_BLOCKDEFAULT
                                        | ::DOCHOSTUIFLAG_ENABLE_FORMS_AUTOCOMPLETE
                                        | ::DOCHOSTUIFLAG_THEME
                                        | ::DOCHOSTUIFLAG_DPI_AWARE
                                        ;

                return S_OK;
                

            STDMETHOD(ShowUI)(                  /* [in] */ DWORD dwID,
                                                /* [in] */ IOleInPlaceActiveObject __RPC_FAR *pActiveObject,
                                                /* [in] */ IOleCommandTarget __RPC_FAR *pCommandTarget,
                                                /* [in] */ IOleInPlaceFrame __RPC_FAR *pFrame,
                                                /* [in] */ IOleInPlaceUIWindow __RPC_FAR *pDoc)
                
                return E_NOTIMPL;
                

            STDMETHOD(HideUI)(void)
                
                return E_NOTIMPL;
                

            STDMETHOD(UpdateUI)(void)
                
                return E_NOTIMPL;
                

            STDMETHOD(EnableModeless)(          /* [in] */ BOOL fEnable)
                
                return E_NOTIMPL;
                

            STDMETHOD(OnDocWindowActivate)(        /* [in] */ BOOL fActivate)
                
                return E_NOTIMPL;
                

            STDMETHOD(OnFrameWindowActivate)(   /* [in] */ BOOL fActivate)
                
                return E_NOTIMPL;
                

            STDMETHOD(ResizeBorder)(            /* [in] */ LPCRECT prcBorder,
                                                /* [in] */ IOleInPlaceUIWindow __RPC_FAR *pUIWindow,
                                                /* [in] */ BOOL fRameWindow)
                
                return E_NOTIMPL;
                

            STDMETHOD(TranslateAccelerator)(    /* [in] */ LPMSG lpMsg,
                                                /* [in] */ const GUID __RPC_FAR *pguidCmdGroup,
                                                /* [in] */ DWORD nCmdID)
                
                return E_NOTIMPL;
                

            STDMETHOD(GetOptionKeyPath)(        /* [out] */ LPOLESTR __RPC_FAR *pchKey,
                                                /* [in] */ DWORD dw)
                
                return E_NOTIMPL;
                

            STDMETHOD(GetDropTarget)(           /* [in] */ IDropTarget __RPC_FAR *pDropTarget,
                                                /* [out] */ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget)
                
                return E_NOTIMPL;
                

            STDMETHOD(GetExternal)(                /* [out] */ IDispatch __RPC_FAR *__RPC_FAR *ppDispatch)
                
                return E_NOTIMPL;
                

            STDMETHOD(TranslateUrl)(            /* [in] */ DWORD dwTranslate,
                                                /* [in] */ OLECHAR __RPC_FAR *pchURLIn,
                                                /* [out] */ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut)
                
                return E_NOTIMPL;
                

            STDMETHOD(FilterDataObject)(        /* [in] */ IDataObject __RPC_FAR *pDO,
                                                /* [out] */ IDataObject __RPC_FAR *__RPC_FAR *ppDORet)
                
                return E_NOTIMPL;
                
        ;

附加上述处理程序的代码(在构造函数中):

TDocUIHandler* piDocUIHandler;

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)

piDocUIHandler = new TDocUIHandler(PopupMenu1);

WB->Navigate("about:blank");
while (WB->Busy)  Application->ProcessMessages(); Sleep(50); 

DelphiInterface<IhtmlDocument2> diDoc = WB->Document;
if (diDoc)
    
    diDoc->designMode = "on";

    DelphiInterface< ::ICustomDoc> diCustDoc;
    if (SUCCEEDED(WB->Document->QueryInterface( ::IID_ICustomDoc,(void**)&diCustDoc)) && diCustDoc)
        
        if (SUCCEEDED(diCustDoc->SetUIHandler(piDocUIHandler)))
        
        //MSGBOX("Success!");
        
    else
        
        // FAIL
        
    

在表单析构函数中:

__fastcall TForm1::~TForm1()
    
    piDocUIHandler->Release();
    

这里找到的代码: https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa753260(v%3Dvs.85)

保存默认的 IDocHostUIHandler。不确定我是否需要,但那里的指南告诉了一些关于避免内存泄漏以执行IObjectWithSite::SetSite(NULL) - 不确定这是否适用于此代码。

【问题讨论】:

【参考方案1】:

您没有正确地对 TDocUIHandler 对象执行引用计数。

对象的引用计数最初为 0。您的表单维护对该对象的活动引用(仅供参考,它应该在类数据成员中,而不是在全局变量中),但不会相应地增加其引用计数。因此,当调用SetUIHandler() 时,它会保存自己对对象的引用并在其上调用AddRef(),因此对象的引用计数为1,即使它实际上有2 个活动引用。

当您的 Form 析构函数被调用时,它 Release() 的对象,将 refcount 递减为 0,因此对象 delete 本身,即使 WebBrowser 仍然有它的活动引用。当 WebBrowser 正在清理时,它会尝试 Release() 其对对象的引用并崩溃,因为它不知道该对象已被销毁。

试试这个:

IDocHostUIHandler* piDocUIHandler;

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)

    piDocUIHandler = new TDocUIHandler(PopupMenu1);
    piDocUIHandler->AddRef(); // <-- ADD THIS!!

    ...


__fastcall TForm1::~TForm1()

    piDocUIHandler->Release();

或者:

DelphiInterface<IDocHostUIHandler> piDocUIHandler; // <-- use DelphiInterface instead!

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)

    piDocUIHandler = new TDocUIHandler(PopupMenu1); // <-- calls AddRef() for you!
    ...


__fastcall TForm1::~TForm1()

    piDocUIHandler.Release(); // <-- note '.' not '->' !

    or:

    piDocUIHandler = NULL; // <-- calls Release() for you!

【讨论】:

您的解释当然是有道理的,但是为什么如果我对实现的Release() 方法进行断点,它的返回值res 永远不会达到0?在另一个项目中,它也在 CodeGuard 中崩溃,但我认为这是由于与我从该项目中删除的 EmbeddedWB 冲突(如在一个不使用 EmbeddedWB 的空白新项目中,CodeGuard 对相同的代码保持沉默)。仍然在这两个项目中,res 永远不会为 ULONG res 返回值达到 0,最后一个在退出之前始终为 1。 @Coder12345 对于自动创建的TForm,例如MainForm,其析构函数直到之后 WinMain()退出时才被调用,此时TApplication对象被销毁。你有考虑吗?

以上是关于使用自定义 IDocHostUIHandler 和关闭窗口时崩溃的主要内容,如果未能解决你的问题,请参考以下文章

C++ COM 设计。组合与多重继承

对比使用IConfigurationSectionHandler和ConfigurationSection自定义节点和自定义处理程序

自定义容器和自定义转换

如何使用自定义表单和自定义流程

Gatsby:在自定义帖子类型上使用 GraphQL 查询和自定义分类

使用自定义列名称和自定义值保存 JSON