从 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):
- 实施
IMoniker
实例,特别是GetDisplayName
(为相关链接提供URL)和BindToStorage
(加载内容)
QueryInterface
IID_IPersistMoniker
的 TWebBrowser 文档
CreateBindCtx
(虽然不确定这是干什么用的)
- 使用
IPersistMoniker
的 Load
方法加载 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
参数,以便您可以访问它指向的指针并为其赋值。
你可以把baseUrl
从OleVariant
改成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;
}
但是,我实际上建议将 memStream
从 TMemoryStream
更改为 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);
}
- 虽然这是可选的,但我强烈建议您将
pMnk
和 pbc
变量包装在 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));
}
我正在尝试使用 IPersistMoniker
从 URL 加载 HTML 以添加相对 URL 的基本路径,例如 <img src="foo.jpg">
从 mypath/images/
(或任何其他路径)。从我发现的过程是(基于this example):
- 实施
IMoniker
实例,特别是GetDisplayName
(为相关链接提供URL)和BindToStorage
(加载内容) QueryInterface
IID_IPersistMoniker
的 TWebBrowser 文档
CreateBindCtx
(虽然不确定这是干什么用的)- 使用
IPersistMoniker
的Load
方法加载 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 字符串返回给调用者,因为你没有 dereferencingppszDisplayName
参数,以便您可以访问它指向的指针并为其赋值。你可以把
baseUrl
从OleVariant
改成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;
}
但是,我实际上建议将 memStream
从 TMemoryStream
更改为 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);
}
- 虽然这是可选的,但我强烈建议您将
pMnk
和pbc
变量包装在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));
}