c++buildertwebbrowsermoniker

Load HTML from IPersistMoniker to add base URL to relative links


I am attempting to load HTML from URL using IPersistMoniker to add relative URLs base path, for example <img src="foo.jpg"> to load from mypath/images/ (or any other path). From what I found the process is (based on this example):

  1. implement IMoniker instance, in particular GetDisplayName (gives the URL for relative links) and BindToStorage (loads the content)
  2. QueryInterface of the TWebBrowser Document for IID_IPersistMoniker
  3. CreateBindCtx (not sure what this is for though)
  4. use Load method of the IPersistMoniker to load HTML, passing the IMoniker instance from (1) and CreateBindCtx instance from (3)

GetDisplayName in my instance does get called, but the BindToStorage where I am supposed to pass the IStream to the actual HTML never gets called so the document always turns out blank, not loaded. The HRESULT is E_INVALIDARG for the call to Load. What have I missed?

IMoniker implementation (some things omitted):

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

Load the content:

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();

Solution

  • I see a few issues with your code:

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

    However, I would actually suggest changing memStream from TMemoryStream to IStream so it is not possible for any IStream given out by BindToStorage() to outlive the HTML data it is referring to:

    #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);
    }
    
    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));
    }