0

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

  1. 实施IMoniker实例,特别是GetDisplayName(提供相对链接的 URL)和BindToStorage(加载内容)
  2. QueryInterfaceTWebBrowser 文档的IID_IPersistMoniker
  3. CreateBindCtx(虽然不知道这是为了什么)
  4. 使用Load的方法IPersistMoniker来加载 HTML,传递IMoniker来自 (1)的CreateBindCtx实例和来自 (3)的实例

GetDisplayName在我的实例中确实被调用了,但是BindToStorage我应该将其传递IStream给实际 HTML 的位置从未被调用,因此文档总是显示为空白,未加载。HRESULTE_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();
4

1 回答 1

3

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

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

    您可以baseUrl从更改OleVariantWideString,然后使用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;
}
  • ppvObj参数BindToStorage()同样也是一个[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;
}

但是,我实际上建议将memStreamfrom更改为TMemoryStreamto,因此任何给定的 byIStream都不可能超过它所指的 HTML 数据:IStreamBindToStorage()

#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);
}
  • 虽然这是可选的,但我强烈建议您将pMnkandpbc变量包装在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));
}
于 2021-08-12T23:41:50.657 回答