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):
IMoniker
instance, in particular GetDisplayName
(gives the URL for relative links) and BindToStorage
(loads the content)QueryInterface
of the TWebBrowser Document for IID_IPersistMoniker
CreateBindCtx
(not sure what this is for though)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();
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);
}
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));
}