Search code examples
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:

    • the ppszDisplayName parameter of GetDisplayName() is an [out] parameter. It receives the address of a caller-provided OLESTR* pointer, and you are expected to set that pointer to an OLE string that is allocated with IMalloc::Alloc() or equivalent. But you are not doing that. In fact, you are not returning any string back to the caller at all, because you are not dereferencing the ppszDisplayName parameter so you can access the pointer it is pointing at to assign a value to it.

      You can change baseUrl from OleVariant to WideString, and then use WideString::Copy() (which uses SysAllocStringLen(), which is compatible with IMalloc) to return an allocated copy of baseUrl to the caller:

    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;
    }
    
    • the ppvObj parameter of BindToStorage() is likewise also an [out] parameter, but you are not dereferencing the passed pointer to return something back to the caller.

      You were on the right track using TStreamAdapter, though, you just need to finish it:

    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);
    }
    
    • though this is optional, I highly suggest you wrap the pMnk and pbc variables in DelphiInterface or other smart COM pointer, let it handle calling Release() for you. You can also use OleCheck() to simplify your error handling:
    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));
    }