从 IPersistMoniker 加载 HTML 以将基本 URL 添加到相关链接
Posted
技术标签:
【中文标题】从 IPersistMoniker 加载 HTML 以将基本 URL 添加到相关链接【英文标题】:Load HTML from IPersistMoniker to add base URL to relative links 【发布时间】:2021-10-16 06:40:28 【问题描述】:我正在尝试使用 IPersistMoniker
从 URL 加载 html 以添加相对 URL 基本路径,例如 <img src="foo.jpg">
从 mypath/images/
(或任何其他路径)加载。根据我发现的过程是(基于this example):
-
实现
IMoniker
实例,特别是GetDisplayName
(提供相对链接的URL)和BindToStorage
(加载内容)
QueryInterface
的 TWebBrowser 文档为 IID_IPersistMoniker
CreateBindCtx
(不知道这是干什么用的)
使用IPersistMoniker
的Load
方法加载HTML,传递来自(1) 的IMoniker
实例和来自(3) 的CreateBindCtx
实例
GetDisplayName
在我的实例中确实被调用,但是我应该将IStream
传递给实际 HTML 的 BindToStorage
从未被调用,因此文档总是显示为空白,未加载。 HRESULT 是E_INVALIDARG
,用于调用Load
。我错过了什么?
IMoniker 实现(省略了一些内容):
// Simple IMoniker implementation
class TMoniker : public IMoniker
private: OleVariant baseUrl;
TMemoryStream* memStream;
LONG m_cRef;
public: TMoniker(const UnicodeString& fBaseUrl, const UnicodeString& fContent)
m_cRef = 1; // Set to 1 so that the AddRef() doesn't need to be called when initialized the first time
this->baseUrl = fBaseUrl;
memStream = new TMemoryStream;
memStream->LoadFromFile(fContent.SubString(8,fContent.Length()));
memStream->Position = 0;
//--------------------------------------------------------------
// IUnknown
//--------------------------------------------------------------
STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
//--------------------------------------------------------------
// IMoniker
//--------------------------------------------------------------
STDMETHODIMP GetDisplayName(IBindCtx *pbc, IMoniker *pmkToLeft, LPOLESTR *ppszDisplayName)
Application->MessageBox(L"GetDisplayName", L"Info", MB_OK); // Check if method is called
// UPDATE - should be *ppszDisplayName = this->baseUrl;
ppszDisplayName = this->baseUrl;
return S_OK;
STDMETHODIMP BindToStorage(IBindCtx *pbc, IMoniker *pmkToLeft, REFIID riid, void **ppvObj)
Application->MessageBox(L"BindToStorage", L"Info", MB_OK); // Check if method is called
ppvObj = NULL;
if (IsEqualIID(riid, IID_IStream))
Application->MessageBox(L"IMoniker::BindToStorage", L"Info", MB_OK);
// DelphiInterface<IStream> sa(*(new TStreamAdapter(memStream.get(), soReference)));
// ppvObj = (IStream)sa;
return S_OK;
STDMETHODIMP BindToObject(IBindCtx *pbc, IMoniker *pmkToLeft, REFIID riidResult, void **ppvResult) return E_NOTIMPL;
STDMETHODIMP Reduce(IBindCtx *pbc, DWORD dwReduceHowFar, IMoniker **ppmkToLeft, IMoniker **ppmkReduced) return E_NOTIMPL;
STDMETHODIMP ComposeWith(IMoniker *pmkRight, BOOL fOnlyIfNotGeneric, IMoniker **ppmkComposite) return E_NOTIMPL;
STDMETHODIMP Enum(BOOL fForward, IEnumMoniker **ppenumMoniker) return E_NOTIMPL;
STDMETHODIMP IsEqual(IMoniker *pmkOtherMoniker) return E_NOTIMPL;
STDMETHODIMP Hash(DWORD *pdwHash) return E_NOTIMPL;
STDMETHODIMP IsRunning(IBindCtx *pbc, IMoniker *pmkToLeft, IMoniker *pmkNewlyRunning) return E_NOTIMPL;
STDMETHODIMP GetTimeOfLastChange(IBindCtx *pbc, IMoniker *pmkToLeft, FILETIME *pFileTime) return E_NOTIMPL;
STDMETHODIMP Inverse(IMoniker **ppmk) return E_NOTIMPL;
STDMETHODIMP CommonPrefixWith(IMoniker *pmkOther, IMoniker **ppmkPrefix) return E_NOTIMPL;
STDMETHODIMP RelativePathTo(IMoniker *pmkOther, IMoniker **ppmkRelPath) return E_NOTIMPL;
STDMETHODIMP ParseDisplayName(IBindCtx *pbc, IMoniker *pmkToLeft, LPOLESTR pszDisplayName, ULONG *pchEaten, IMoniker **ppmkOut) return E_NOTIMPL;
STDMETHODIMP IsSystemMoniker(DWORD *pdwMksys) return E_NOTIMPL;
//--------------------------------------------------------------
// IPersistStream
//--------------------------------------------------------------
STDMETHODIMP IsDirty() return E_NOTIMPL;
STDMETHODIMP Load(IStream *pStm) return E_NOTIMPL;
STDMETHODIMP Save(IStream *pStm, BOOL fClearDirty) return E_NOTIMPL;
STDMETHODIMP GetSizeMax(ULARGE_INTEGER *pcbSize) return E_NOTIMPL;
//--------------------------------------------------------------
// IPersist
//--------------------------------------------------------------
STDMETHODIMP GetClassID(CLSID *pClassID) return E_NOTIMPL;
;
//------------------------------------------------------------------------------
// IUnknown::QueryInterface
//------------------------------------------------------------------------------
STDMETHODIMP TMoniker::QueryInterface(REFIID riid, void** ppv)
if (!ppv) return E_POINTER;
if (IID_IUnknown == riid) *ppv = (IUnknown *) this;
else if (IID_IMoniker == riid) *ppv = (IMoniker *) this;
else if (IID_IPersistStream == riid) *ppv = (IPersistStream *)this;
else if (IID_IPersist == riid) *ppv = (IPersist *) this;
else
*ppv = NULL;
return E_NOINTERFACE;
// AddRef It
((IUnknown*)*ppv)->AddRef();
return S_OK;
//------------------------------------------------------------------------------
// IUnknown::AddRef
//------------------------------------------------------------------------------
STDMETHODIMP_(ULONG) TMoniker::AddRef()
return ::InterlockedIncrement(&m_cRef);
//------------------------------------------------------------------------------
// IUnknown::Release
//------------------------------------------------------------------------------
STDMETHODIMP_(ULONG) TMoniker::Release()
LONG cRef = ::InterlockedDecrement(&m_cRef);
if (0 == cRef) delete this;
return cRef;
加载内容:
TMoniker* pMnk = new TMoniker("about:blank", "file://c:\\temp\\file.html");
LPBC pbc=0;
DelphiInterface<IHTMLDocument2> diDoc2 = WB->Document;
if (diDoc2)
DelphiInterface<IPersistMoniker> diPM;
if (SUCCEEDED(diDoc2->QueryInterface(IID_IPersistMoniker, (void**)&diPM)))
if (SUCCEEDED(CreateBindCtx(0, &pbc)))
// !!! returns `E_INVALIDARG` here !!!
if (SUCCEEDED(diPM->Load(TRUE, pmk, pbc, STGM_READWRITE)))
if (pbc) pbc->Release();
pMnk->Release();
【问题讨论】:
多么愚蠢的错误,我使用ppszDisplayName = this->baseUrl;
而不是*ppszDisplayName = this->baseUrl;
!!
即使使用该 fix,您仍然会遇到错误,因为您没有返回 baseUrl
持有的 BSTR
的 copy .调用者将释放返回的字符串,因此您不希望它直接释放 baseUrl
的内部 BSTR
,这就是您现在要返回的内容。
【参考方案1】:
我发现您的代码存在一些问题:
GetDisplayName()
的ppszDisplayName
参数是[out]
参数。它接收调用者提供的OLESTR*
指针的地址,并且您应该将该指针设置为使用IMalloc::Alloc()
或等效项分配的OLE 字符串。但你没有那样做。实际上,您根本没有将 any 字符串返回给调用者,因为您没有取消引用 ppszDisplayName
参数,因此您可以访问它指向的指针给它赋值。
您可以将baseUrl
从OleVariant
更改为WideString
,然后使用WideString::Copy()
(使用SysAllocStringLen()
,与IMalloc
兼容)将分配的baseUrl
副本返回给来电者:
private: WideString baseUrl;
STDMETHODIMP GetDisplayName(IBindCtx *pbc, IMoniker *pmkToLeft, LPOLESTR *ppszDisplayName)
//Application->MessageBox(L"GetDisplayName", L"Info", MB_OK); // Check if method is called
if (!ppszDisplayName) return E_POINTER;
*ppszDisplayName = baseUrl.Copy();
return S_OK;
BindToStorage()
的 ppvObj
参数同样也是 [out]
参数,但您没有取消引用传递的指针以将某些内容返回给调用者。
您使用TStreamAdapter
走在正确的轨道上,不过,您只需完成它:
STDMETHODIMP BindToStorage(IBindCtx *pbc, IMoniker *pmkToLeft, REFIID riid, void **ppvObj)
//Application->MessageBox(L"BindToStorage", L"Info", MB_OK); // Check if method is called
if (!ppvObj) return E_POINTER;
*ppvObj = NULL;
if (!IsEqualIID(riid, IID_IStream)) return E_NOINTERFACE;
//Application->MessageBox(L"IMoniker::BindToStorage", L"Info", MB_OK);
DelphiInterface<IStream> sa(*(new TStreamAdapter(memStream.get(), soReference)));
*ppvObj = (IStream*)sa;
/* or simply:
*ppvObj = (IStream*) *(new TStreamAdapter(memStream.get(), soReference));
*/
sa->AddRef(); // <-- don't forget this, whether you use DelphiInterface or not!
return S_OK;
但是,我实际上建议将 memStream
从 TMemoryStream
更改为 IStream
,因此 BindToStorage()
给出的任何 IStream
都不可能超过它所指的 HTML 数据:
#include <System.StrUtils.hpp>
#include <memory>
private: DelphiInterface<IStream> diStrm;
TMoniker(const UnicodeString& fBaseUrl, const UnicodeString& fContent)
...
UnicodeString path = fContent;
if (StartsText(L"file://", fContent))
path.Delete(1, 7);
std::auto_ptr<TMemoryStream> memStream(new TMemoryStream); // or std::unique_ptr in C++11 and later...
memStream->LoadFromFile(fContent);
memStream->Position = 0;
diStrm = *(new TStreamAdapter(memStream.get(), soOwned));
memStream.release();
...
STDMETHODIMP BindToStorage(IBindCtx *pbc, IMoniker *pmkToLeft, REFIID riid, void **ppvObj)
return diStrm->QueryInterface(riid, ppvObj);
虽然这是可选的,但我强烈建议您将pMnk
和pbc
变量包装在DelphiInterface
或其他智能COM 指针中,让它为您处理调用Release()
。您还可以使用OleCheck()
来简化错误处理:
DelphiInterface<IHTMLDocument2> diDoc2 = WB->Document;
if (diDoc2)
DelphiInterface<IPersistMoniker> diPM;
OleCheck(diDoc2->QueryInterface(IID_IPersistMoniker, (void**)&diPM));
// or: OleCheck(diDoc2->QueryInterface(IID_PPV_ARGS(&diPM)));
DelphiInterface<IBindCtx> diBC;
OleCheck(CreateBindCtx(0, &diBC));
// set m_cRef to 0 in the TMoniker constructor, not 1...
DelphiInterface<IMoniker> diMnk(new TMoniker(L"about:blank", L"file://c:\\temp\\file.html"));
OleCheck(diPM->Load(TRUE, diMnk, diBC, STGM_READ));
【讨论】:
以上是关于从 IPersistMoniker 加载 HTML 以将基本 URL 添加到相关链接的主要内容,如果未能解决你的问题,请参考以下文章
Nuxtjs - 图像可以从 HTML 加载,但不能从 css/scss 文件加载