从 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(不知道这是干什么用的) 使用IPersistMonikerLoad 方法加载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-&gt;baseUrl; 而不是*ppszDisplayName = this-&gt;baseUrl; !! 即使使用该 fix,您仍然会遇到错误,因为您没有返回 baseUrl 持有的 BSTRcopy .调用者将释放返回的字符串,因此您不希望它直接释放 baseUrl 的内部 BSTR,这就是您现在要返回的内容。 【参考方案1】:

我发现您的代码存在一些问题:

GetDisplayName()ppszDisplayName 参数是[out] 参数。它接收调用者提供的OLESTR* 指针的地址,并且您应该将该指针设置为使用IMalloc::Alloc() 或等效项分配的OLE 字符串。但你没有那样做。实际上,您根本没有将 any 字符串返回给调用者,因为您没有取消引用 ppszDisplayName 参数,因此您可以访问它指向的指针给它赋值。

您可以将baseUrlOleVariant 更改为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;

但是,我实际上建议将 memStreamTMemoryStream 更改为 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);

虽然这是可选的,但我强烈建议您将pMnkpbc 变量包装在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 文件加载

从 HTML 将图像加载到 UITextview/WKWebView

Webview 从资产目录加载 html

从 HTML 字符串 wkwebview 加载脚本

UIWebView 从本地文件系统加载 HTML 文件

Angular JS - 单击按钮从指令加载模板 html