从 IPersistMoniker 加载 HTML 以将基础 URL 添加到相关链接

Load HTML from IPersistMoniker to add base URL to relative links

我正在尝试使用 IPersistMoniker 从 URL 加载 HTML 以添加相对 URL 的基本路径,例如 <img src="foo.jpg">mypath/images/(或任何其他路径)。从我发现的过程是(基于this example):

  1. 实施IMoniker实例,特别是GetDisplayName(为相关链接提供URL)和BindToStorage(加载内容)
  2. QueryInterface IID_IPersistMoniker
  3. 的 TWebBrowser 文档
  4. CreateBindCtx(虽然不确定这是干什么用的)
  5. 使用 IPersistMonikerLoad 方法加载 HTML,从 (1) 传递 IMoniker 实例,从 (3) 传递 CreateBindCtx 实例

GetDisplayName 在我的实例中确实被调用,但是我应该将 IStream 传递给实际 HTML 的 BindToStorage 永远不会被调用,所以文档总是变成空白,没有加载。调用 Load 的 HRESULT 是 E_INVALIDARG。我错过了什么?

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

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

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

    你可以把baseUrlOleVariant改成WideString,然后用WideString::Copy()(用的是SysAllocStringLen(),兼容IMalloc) 到 return 分配给调用者的 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] 参数,但您没有 取消引用 将指向 return 的指针传递给调用者。

    您使用 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));
}