c++webbrowser-controlc++buildermshtmltwebbrowser

Crashes when using custom IDocHostUIHandler and closing window


I use custom IDocHostUIHandler attached to a TWebBrowser (in design mode). It works. However, when the program exits (or the window using it closes) it crashes. I suppose something is not deleted/destroyed properly. I assume that the custom IDocHostUIHandler is still in use after it is destroyed so I should somehow destroy web browser first (or detach the custom .

So my question is - how do I properly destroy custom IDocHostUIHandler so that when existing the program doesn't crash or detach the custom IDocHostUIHandler?

If I remove the diCustDoc->SetUIHandler(piDocUIHandler) in constructor it works without any crashes so the problem is definitely caused by attaching custom IDocHostUIHandler.

Setting the diCustDoc->SetUIHandler(NULL); before the above Release() (in destructor) crashes too.

My custom IDocHostUIHandler (replaces popup menu and customizes the 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://learn.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;
                }
        };

Code to attach the above handler (in constructor):

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
        }
    }
}

In form destructor:

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

The code found here: https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa753260(v%3Dvs.85)

Saves the default IDocHostUIHandler. Not sure if I need that, but the guide there tells something about avoiding memory leaks to perform IObjectWithSite::SetSite(NULL) - not sure if that applies to this code.


Solution

  • You are not performing reference counting on your TDocUIHandler object correctly.

    The object's refcount is initially 0. Your Form maintains an active reference to the object (FYI, it should be in a class data member, not in a global variable), but does not increment its refcount accordingly. Thus, when SetUIHandler() is called, it saves its own reference to the object and calls AddRef() on it, so the object's refcount is 1, even though it actually has 2 active references.

    When your Form destructor is called, it Release()'s the object, decrementing the refcount to 0, so the object delete's itself, even though the WebBrowser still has an active reference to it. When the WebBrowser is cleaning up, it tries to Release() its reference to the object and crashes since it doesn't know the object was already destroyed.

    Try this:

    IDocHostUIHandler* piDocUIHandler;
    
    __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
    {
        piDocUIHandler = new TDocUIHandler(PopupMenu1);
        piDocUIHandler->AddRef(); // <-- ADD THIS!!
    
        ...
    }
    
    __fastcall TForm1::~TForm1()
    {
        piDocUIHandler->Release();
    }
    

    Alternatively:

    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!
    }