使用自定义 IDocHostUIHandler 并关闭时崩溃 window
Crashes when using custom IDocHostUIHandler and closing window
我使用附加到 TWebBrowser 的自定义 IDocHostUIHandler
(在设计模式下)。有用。但是,当程序退出(或使用它的 window 关闭)时,它会崩溃。我想有些东西 deleted/destroyed 不正确。我假设自定义 IDocHostUIHandler 在被销毁后仍在使用,所以我应该先以某种方式销毁网络浏览器(或分离自定义 .
所以我的问题是 - 如何正确销毁自定义 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();
}
保存默认的 IDocHostUIHandler。不确定我是否需要那个,但是那里的指南讲述了一些关于避免内存泄漏以执行 IObjectWithSite::SetSite(NULL)
- 不确定这是否适用于此代码。
您没有正确地对 TDocUIHandler
对象执行引用计数。
对象的引用计数最初为 0。您的表单保持对该对象的活动引用(仅供参考,它应该在 class 数据成员中,而不是在全局变量中),但不会增加其引用计数因此。因此,当调用 SetUIHandler()
时,它会保存自己对该对象的引用并对其调用 AddRef()
,因此该对象的引用计数为 1,即使它实际上有 2 个活动引用。
当您的表单析构函数被调用时,它 Release()
是对象,将引用计数递减为 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!
}
我使用附加到 TWebBrowser 的自定义 IDocHostUIHandler
(在设计模式下)。有用。但是,当程序退出(或使用它的 window 关闭)时,它会崩溃。我想有些东西 deleted/destroyed 不正确。我假设自定义 IDocHostUIHandler 在被销毁后仍在使用,所以我应该先以某种方式销毁网络浏览器(或分离自定义 .
所以我的问题是 - 如何正确销毁自定义 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();
}
保存默认的 IDocHostUIHandler。不确定我是否需要那个,但是那里的指南讲述了一些关于避免内存泄漏以执行 IObjectWithSite::SetSite(NULL)
- 不确定这是否适用于此代码。
您没有正确地对 TDocUIHandler
对象执行引用计数。
对象的引用计数最初为 0。您的表单保持对该对象的活动引用(仅供参考,它应该在 class 数据成员中,而不是在全局变量中),但不会增加其引用计数因此。因此,当调用 SetUIHandler()
时,它会保存自己对该对象的引用并对其调用 AddRef()
,因此该对象的引用计数为 1,即使它实际上有 2 个活动引用。
当您的表单析构函数被调用时,它 Release()
是对象,将引用计数递减为 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!
}