How to instantiate the IWebViewControl in MFC or C++? - c++

Recently Microsoft has introduced IWebViewControl for WPF and winforms.
IWebViewControl uses the Edge rendering engine.
It is very easy to instantiate the IWebViewControl using the class constructor but I am not sure how to instantiate the IWebViewControl in MFC or C++.
https://learn.microsoft.com/en-us/microsoft-edge/hosting/webview

Here is IWebViewControl example in C++:
https://github.com/rjmurillo/webview-samples/tree/master/WinMD/Win32/WebViewSamples.Win32
Excerpt from the code:
void CheckFailure(_In_ HRESULT hr)
{
if (FAILED(hr))
{
WCHAR message[512] = L"";
StringCchPrintf(message, ARRAYSIZE(message), L"Error: 0x%x", hr);
MessageBoxW(nullptr, message, nullptr, MB_OK);
ExitProcess(-1);
}
}
template <typename TInterface>
Microsoft::WRL::ComPtr<TInterface> GetActivationFactoryFailFast(_In_z_ PCWSTR factoryClassName)
{
ComPtr<TInterface> factoryInstance;
CheckFailure(RoGetActivationFactory(
HStringReference(factoryClassName).Get(),
IID_PPV_ARGS(&factoryInstance)));
return factoryInstance;
}
template <typename TInterface>
Microsoft::WRL::ComPtr<TInterface> ActivateInstanceFailFast(_In_z_ PCWSTR className)
{
ComPtr<TInterface> classInstanceAsInspectable;
ComPtr<TInterface> classInstance;
CheckFailure(RoActivateInstance(
HStringReference(className).Get(),
&classInstanceAsInspectable));
CheckFailure(classInstanceAsInspectable.As(&classInstance));
return classInstance;
}
ComPtr<IUriRuntimeClass> CreateWinRtUri(_In_z_ PCWSTR uri, _In_ bool allowInvalidUri = false)
{
auto uriRuntimeClassFactory = GetActivationFactoryFailFast<IUriRuntimeClassFactory>(RuntimeClass_Windows_Foundation_Uri);
ComPtr<IUriRuntimeClass> uriRuntimeClass;
if (!allowInvalidUri)
{
CheckFailure(uriRuntimeClassFactory->CreateUri(HStringReference(uri).Get(), &uriRuntimeClass));
}
else
{
uriRuntimeClassFactory->CreateUri(HStringReference(uri).Get(), &uriRuntimeClass);
}
return uriRuntimeClass;
}
m_processOptions = ActivateInstanceFailFast<IWebViewControlProcessOptions>(RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcessOptions);
ComPtr<IWebViewControlProcessFactory> webViewControlProcessFactory = GetActivationFactoryFailFast<IWebViewControlProcessFactory>(RuntimeClass_Windows_Web_UI_Interop_WebViewControlProcess);
CheckFailure(webViewControlProcessFactory->CreateWithOptions(m_processOptions.Get(), &m_process));
ComPtr<IAsyncOperation<WebViewControl*>> createWebViewAsyncOperation;
CheckFailure(m_process->CreateWebViewControlAsync(reinterpret_cast<INT64>(m_hostWindow), HwndWindowRectToBoundsRect(m_hostWindow), &createWebViewAsyncOperation));
HRESULT hr = createWebViewAsyncOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<WebViewControl*>>([this, createWebViewAsyncOperation](IAsyncOperation<WebViewControl*>*, AsyncStatus status) -> HRESULT
{
CheckFailure(createWebViewAsyncOperation->GetResults(&m_webViewControl));
NavigateToUri(L"https://www.bing.com/");
return S_OK;
}).Get());
CheckFailure(hr);

I don't know if this is an answer as such and will remove it on request. MBut Microsoft now have a WebView2 in beta and it has a Win32 variant:
https://learn.microsoft.com/en-us/microsoft-edge/webview2/gettingstarted/win32
Since MFC is a Win32/Win64 app is it not possible to use this SDK?

Related

Programmatically pre-select using IFileDialog in C++

I am perplexed on whether IFileDialog has the capability of programmatically selecting an item inside the dialog even with out the user selecting.
Ex.
I was hoping to achieve opening IFileDialog then selecting a default item/folder inside the dialog.
Ex.
By the way, in the picture above. I did manually click/select the folder.
But I was hoping to implement a defaultly selected item inside the IFileDialog.
The procedure suggested by zett42 works. You need to implement your own version of IFileDialogEvents. After hooking with IFileDialog::Advise you can query your way to IShellView and that lets you change the selection.
This example is a little silly because I'm forcing the directory as well to be sure I have a file I can select.
struct MyIFileDialogEvents : public IFileDialogEvents {
bool forcedDir, forcedSel;
MyIFileDialogEvents() : forcedDir(false), forcedSel(false) {}
...
};
STDMETHODIMP MyIFileDialogEvents::OnFolderChanging( IFileDialog *pfd, IShellItem*psiFolder)
{
if (forcedDir) return S_OK; else forcedDir = true;
IShellItem*psiwindir;
HRESULT hr = SHGetKnownFolderItem(FOLDERID_Windows, KF_FLAG_DEFAULT, NULL, IID_IShellItem, (void**) &psiwindir);
if (!hr)
{
hr = pfd->SetFolder(psiwindir); // MSDN says it is OK to change the folder in OnFolderChanging with SetFolder
psiwindir->Release();
}
if (FAILED(hr)) forcedSel = true;
return S_OK;
}
STDMETHODIMP MyIFileDialogEvents::OnFolderChange(IFileDialog *pfd)
{
if (forcedSel || !forcedDir) return S_OK; else forcedSel = true;
IShellItem*psiwindir, *psiexp;
HRESULT hr = SHGetKnownFolderItem(FOLDERID_Windows, KF_FLAG_DEFAULT, NULL, IID_IShellItem, (void**) &psiwindir);
if (!hr)
{
hr = SHCreateItemFromRelativeName(psiwindir, L"Explorer.exe", NULL, IID_IShellItem, (void**) &psiexp);
psiwindir->Release();
if (!hr)
{
IServiceProvider*pSP;
IShellBrowser*pSB;
IShellView*pSV;
if (!pfd->QueryInterface(IID_IServiceProvider, (void**) &pSP))
{
if (!pSP->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void**)&pSB))
{
if (!pSB->QueryActiveShellView(&pSV))
{
PIDLIST_ABSOLUTE pidl;
if (!SHGetIDListFromObject(psiexp, &pidl))
{
pSV->SelectItem(ILFindLastID(pidl), SVSI_SELECT|SVSI_ENSUREVISIBLE|SVSI_FOCUSED|SVSI_DESELECTOTHERS);
CoTaskMemFree(pidl);
}
pSV->Release();
}
pSB->Release();
}
pSP->Release();
}
psiexp->Release();
}
}
return S_OK;
}

Drag and drop from my app's ListView to external apps (such as Windows Explorer)

I have a a ListView containing a list of files:
hList = CreateWindowEx(0, WC_LISTVIEW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT, 0, 0, 500, 400, hWnd, (HMENU)ID_LISTVIEW, hInst, NULL);
Let's say it contains a row c:\temp\hello.txt.
How to enable drag and drop of this file from my application's ListView to external applications (such as Windows Explorer) as "Copy"?
The GUI part of the question might be obvious (or not?) via:
case WM_NOTIFY:
{
...
case LVN_BEGINDRAG:
But here this question is about the actual sending of the file to external applications, such as Windows Explorer. How to do this?
Implement IDropSource, IDropSourceNotify (optional) and IDataObject and call DoDragDrop:
If you are developing an application that can act as a data source for an OLE drag-and-drop operation, you must call DoDragDrop when you detect that the user has started an OLE drag-and-drop operation.
The DoDragDrop function enters a loop in which it calls various methods in the IDropSource and IDropTarget interfaces. (For a successful drag-and-drop operation, the application acting as the data source must also implement IDropSource, while the target application must implement IDropTarget.)
SHCreateDataObject can provide a IDataObject instance for you but you often end up having to code your own because the shell provided implementation is not perfect.
IDragSourceHelper can help you to get a fancy drag image.
See also:
Dragging a shell object, part 1: Getting the IDataObject
What a drag: Dragging a virtual file (HGLOBAL edition)
What a drag: Dragging a virtual file (IStream edition)
What a drag: Dragging a virtual file (IStorage edition)
Here is some code that implements all that is required to perform such a ListView file drag&drop. First some includes:
#define CINTERFACE
#define COBJMACROS
#include "ShObjIdl.h"
#include "ShlObj.h"
#include "oleidl.h"
Then this in the WinMain function, to initialize OLE operations.
OleInitialize(NULL);
InitCommonControls();
Then, the IDropSource part:
typedef struct __DSV_TDropSource {
IDropSource This;
IDropSourceVtbl Func;
ULONG RefCnt;
} __DSV_TDropSource;
HRESULT WINAPI __DSV_QueryInterface(IDropSource *This, REFIID riid, void **ppvObject)
{
IUnknown *punk = NULL;
if (riid == IID_IUnknown)
{
punk = (IUnknown*)This;
}
else if (riid == IID_IDropSource)
{
punk = (IUnknown*)This;
}
*ppvObject = punk;
if (punk)
{
IUnknown_AddRef(punk);
return S_OK;
}
else {
return E_NOINTERFACE;
}
}
ULONG WINAPI __DSV_AddRef(IDropSource *This)
{
__DSV_TDropSource *pThis = (__DSV_TDropSource*)This;
return pThis->RefCnt++;
}
ULONG WINAPI __DSV_Release(IDropSource *This)
{
__DSV_TDropSource *pThis = (__DSV_TDropSource*)This;
LONG iRes = (LONG)pThis->RefCnt - 1;
if (iRes < 1) { iRes = 0; }
pThis->RefCnt = iRes;
if (iRes == 0) { free(pThis); }
return iRes;
}
HRESULT WINAPI __DSV_QueryContinueDrag(IDropSource *This, BOOL fEscapePressed, DWORD grfKeyState)
{
if (fEscapePressed) { return DRAGDROP_S_CANCEL; }
if (!(grfKeyState & (MK_LBUTTON | MK_RBUTTON))) { return DRAGDROP_S_DROP; }
return S_OK;
}
HRESULT WINAPI __DSV_GiveFeedback(IDropSource *This, DWORD dwEffect)
{
return DRAGDROP_S_USEDEFAULTCURSORS;
}
IDropSource* CreateDropSource()
{
__DSV_TDropSource *pResu = (__DSV_TDropSource*)malloc(sizeof(__DSV_TDropSource));
if (!pResu) { return 0; }
pResu->This.lpVtbl = &(pResu->Func);
pResu->Func.QueryInterface = __DSV_QueryInterface;
pResu->Func.AddRef = __DSV_AddRef;
pResu->Func.Release = __DSV_Release;
pResu->Func.QueryContinueDrag = __DSV_QueryContinueDrag;
pResu->Func.GiveFeedback = __DSV_GiveFeedback;
pResu->RefCnt = 1;
return (IDropSource*)pResu;
}
void** GetFileUiObject(TCHAR *ptFile, REFIID riid)
{
void** pInterfaceResu = 0;
IShellFolder *pFolder;
PIDLIST_RELATIVE pFile;
PIDLIST_ABSOLUTE pITEMDLIST_File;
HRESULT iResu;
pITEMDLIST_File = ILCreateFromPath(ptFile);
if (!pITEMDLIST_File)
return 0;
iResu = SHBindToParent(pITEMDLIST_File, IID_IShellFolder, (void**)&pFolder, (PCUITEMID_CHILD*)&pFile);
if (iResu != S_OK)
return 0;
const ITEMIDLIST* pArray[1] = { pFile };
iResu = IShellFolder_GetUIObjectOf(pFolder, NULL, 1, pArray, riid, NULL, (void**)&pInterfaceResu);
if (iResu != S_OK)
return 0;
IShellFolder_Release(pFolder);
return pInterfaceResu;
}
Lastly, this should be performed in the message loop:
case WM_NOTIFY:
pdi = (NMLVDISPINFO*) lParam;
nmlv = (NMLISTVIEW*) lParam;
switch (pdi->hdr.code)
{
case LVN_BEGINDRAG:
wstring fName = L"C:\\test.txt";
IDataObject *pObj;
IDropSource *pSrc;
pObj = (IDataObject*)GetFileUiObject(LPWSTR(fName.c_str()), IID_IDataObject);
if (!pObj)
break;
pSrc = CreateDropSource();
if (!pSrc)
{
IDataObject_Release(pObj);
break;
}
DWORD dwEffect;
DoDragDrop(pObj, pSrc, DROPEFFECT_COPY | DROPEFFECT_LINK, &dwEffect);
IDropSource_Release(pSrc);
IDataObject_Release(pObj);
break;

Windows Explorer: self implemented Overlay Icons do not work

I'm trying to simply display some overlay icons in the Windows Explorer.
Since I'm relatively new to programming for Windows (I just have some experience in Mac programming), I used this article to start.
So this is my code:
Icon.h
#pragma once
#include "resource.h"
#include "ExplorerSync_i.h"
using namespace ATL;
class ATL_NO_VTABLE CIcon :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CIcon, &CLSID_Icon>,
public IShellIconOverlayIdentifier,
public IDispatchImpl<IIcon, &IID_IIcon, &LIBID_ExplorerSyncLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CIcon();
STDMETHOD(GetOverlayInfo)(LPWSTR pwszIconFile, int cchMax, int *pIndex, DWORD* pdwFlags);
STDMETHOD(GetPriority)(int* pPriority);
STDMETHOD(IsMemberOf)(LPCWSTR pwszPath, DWORD dwAttrib);
DECLARE_REGISTRY_RESOURCEID(IDR_ICON)
BEGIN_COM_MAP(CIcon)
COM_INTERFACE_ENTRY(IIcon)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IShellIconOverlayIdentifier)
END_COM_MAP()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
protected:
};
Icon.cpp
#include "stdafx.h"
#include "Icon.h"
#using <system.dll>
using namespace System;
using namespace System::Diagnostics;
STDMETHODIMP CIcon::GetOverlayInfo(LPWSTR pwszIconFile, int cchMax, int * pIndex, DWORD * pdwFlags)
{
GetModuleFileNameW(_AtlBaseModule.GetModuleInstance(), pwszIconFile, cchMax);
*pIndex = 0;
*pdwFlags = ISIOI_ICONFILE | ISIOI_ICONINDEX;
return S_OK;
}
STDMETHODIMP CIcon::GetPriority(int * pPriority)
{
*pPriority = 0;
return S_OK;
}
STDMETHODIMP CIcon::IsMemberOf(LPCWSTR pwszPath, DWORD dwAttrib)
{
wchar_t *s = _wcsdup(pwszPath);
HRESULT r = S_FALSE;
_wcslwr_s(s, 1);
r = S_OK;
free(s);
return r;
}
CIcon::CIcon() {
}
ExplorerSync.cpp
#include "stdafx.h"
#include "resource.h"
#include "ExplorerSync_i.h"
#include "dllmain.h"
#include "Icon.h"
#using <system.dll>
using namespace System;
using namespace System::Diagnostics;
using namespace ATL;
STDAPI DllCanUnloadNow(void)
{
return _AtlModule.DllCanUnloadNow();
}
_Check_return_
STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID* ppv)
{
return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}
STDAPI DllRegisterServer(void)
{
HRESULT hr = _AtlModule.DllRegisterServer();
// not possible to instantiate, because it is abstract according to error message
//CIcon *cIcon = new (std::nothrow) CIcon();
return hr;
}
STDAPI DllUnregisterServer(void)
{
HRESULT hr = _AtlModule.DllUnregisterServer();
return hr;
}
STDAPI DllInstall(BOOL bInstall, _In_opt_ LPCWSTR pszCmdLine)
{
HRESULT hr = E_FAIL;
static const wchar_t szUserSwitch[] = L"user";
if (pszCmdLine != NULL)
{
if (_wcsnicmp(pszCmdLine, szUserSwitch, _countof(szUserSwitch)) == 0)
{
ATL::AtlSetPerUserRegistration(true);
}
}
if (bInstall)
{
hr = DllRegisterServer();
if (FAILED(hr))
{
DllUnregisterServer();
}
}
else
{
hr = DllUnregisterServer();
}
return hr;
}
So I used primarily the code from the example mentioned above and because it did not work, I changed the code a bit according to my thoughts.
The problem is not the compiling. This works and registration via regsvr32 as well without any errors. But with some event log entries I found out, that DllRegisterServer is the only method which is called. All the other functions (especially the interface implementations for the icons) are not called at all. Although it's not done in the example, I tried to instantiate the Icon class in the DllRegisterServer method in the hope it helps, but I cannot do that (error message: Icon is abstract and cannot be instantiated).
So can anybody tell me, what's going wrong, please?
Thank you so much in advance!

How to load & call a VBScript function from within C++?

We have customers asking for VBScript functions to be called when particular actions occur within our product. I've been trying to research the Windows Scripting technologies but I'm having difficulty finding exactly what I need. Hopefully some of you can help.
Our product is a native C++ Windows product. The customer would specify a VBScript file, which we would load, and whenever a particular event occurs, we'd call a particular function in the VBScript and let it do its thing. We may provide objects within the script's namespace for it to access information about our product as well.
I found some information on MSDN about the IActiveScript interface, and some related things, but cannot find any examples of instantiating a COM object that implements this interface for VBScript.
I know that PowerShell would probably be a better option for this these days, but our customers are stuck in a lot of legacy systems and VBScript is what they know.
Any help you can provide (links or otherwise) would be appreciated!
I've put together a "Hello World" IActiveScript C++ ATL console application that:
Define CSimpleScriptSite class
Implement IActiveScriptSite interface (mandatory)
Implement IActiveScriptSiteWindow interface (optional)
Minimum implementation with most functions implemented with a dummy stub
Has no error handling. Consult MSDN IActiveScriptError.
Use CoCreateInstance a new IActiveSite object
Create instances of both VBScript and JScript
Link the IActiveSite to IActiveScriptSite using IActiveSite::SetScriptSite
Call QueryInterface to get an IActiveScriptParse interface
Use IActiveScriptParse to execute VBScript or JScript code
The sample:
Evaluates an expression in JScript
Evaluates an expression in VBScript
Runs a command in VBScript
Code:
#include "stdafx.h"
#include <atlbase.h>
#include <activscp.h>
class CSimpleScriptSite :
public IActiveScriptSite,
public IActiveScriptSiteWindow
{
public:
CSimpleScriptSite() : m_cRefCount(1), m_hWnd(NULL) { }
// IUnknown
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);
// IActiveScriptSite
STDMETHOD(GetLCID)(LCID *plcid){ *plcid = 0; return S_OK; }
STDMETHOD(GetItemInfo)(LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown **ppiunkItem, ITypeInfo **ppti) { return TYPE_E_ELEMENTNOTFOUND; }
STDMETHOD(GetDocVersionString)(BSTR *pbstrVersion) { *pbstrVersion = SysAllocString(L"1.0"); return S_OK; }
STDMETHOD(OnScriptTerminate)(const VARIANT *pvarResult, const EXCEPINFO *pexcepinfo) { return S_OK; }
STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState) { return S_OK; }
STDMETHOD(OnScriptError)(IActiveScriptError *pIActiveScriptError) { return S_OK; }
STDMETHOD(OnEnterScript)(void) { return S_OK; }
STDMETHOD(OnLeaveScript)(void) { return S_OK; }
// IActiveScriptSiteWindow
STDMETHOD(GetWindow)(HWND *phWnd) { *phWnd = m_hWnd; return S_OK; }
STDMETHOD(EnableModeless)(BOOL fEnable) { return S_OK; }
// Miscellaneous
HRESULT SetWindow(HWND hWnd) { m_hWnd = hWnd; return S_OK; }
public:
LONG m_cRefCount;
HWND m_hWnd;
};
STDMETHODIMP_(ULONG) CSimpleScriptSite::AddRef()
{
return InterlockedIncrement(&m_cRefCount);
}
STDMETHODIMP_(ULONG) CSimpleScriptSite::Release()
{
if (!InterlockedDecrement(&m_cRefCount))
{
delete this;
return 0;
}
return m_cRefCount;
}
STDMETHODIMP CSimpleScriptSite::QueryInterface(REFIID riid, void **ppvObject)
{
if (riid == IID_IUnknown || riid == IID_IActiveScriptSiteWindow)
{
*ppvObject = (IActiveScriptSiteWindow *) this;
AddRef();
return NOERROR;
}
if (riid == IID_IActiveScriptSite)
{
*ppvObject = (IActiveScriptSite *) this;
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr = S_OK;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Initialize
CSimpleScriptSite* pScriptSite = new CSimpleScriptSite();
CComPtr<IActiveScript> spJScript;
CComPtr<IActiveScriptParse> spJScriptParse;
hr = spJScript.CoCreateInstance(OLESTR("JScript"));
hr = spJScript->SetScriptSite(pScriptSite);
hr = spJScript->QueryInterface(&spJScriptParse);
hr = spJScriptParse->InitNew();
CComPtr<IActiveScript> spVBScript;
CComPtr<IActiveScriptParse> spVBScriptParse;
hr = spVBScript.CoCreateInstance(OLESTR("VBScript"));
hr = spVBScript->SetScriptSite(pScriptSite);
hr = spVBScript->QueryInterface(&spVBScriptParse);
hr = spVBScriptParse->InitNew();
// Run some scripts
CComVariant result;
EXCEPINFO ei = { };
hr = spJScriptParse->ParseScriptText(OLESTR("(new Date()).getTime()"), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
hr = spVBScriptParse->ParseScriptText(OLESTR("Now"), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
hr = spVBScriptParse->ParseScriptText(OLESTR("MsgBox \"Hello World! The current time is: \" & Now"), NULL, NULL, NULL, 0, 0, 0, &result, &ei);
// Cleanup
spVBScriptParse = NULL;
spVBScript = NULL;
spJScriptParse = NULL;
spJScript = NULL;
pScriptSite->Release();
pScriptSite = NULL;
::CoUninitialize();
return 0;
}
A version of the above code can be found here:
https://github.com/stephenquan/RunScriptDemo
IActiveScript and related interfaces work very well. I use them in my product exactly the same way you have described. Some of out customers write their own VBScript and JScript scripts to analyze and update application data before it gets posted to a database.
You use CoCreateInstance() to instantiate IActiveScript, like you would any other COM object. You would then call its QueryInterface() method to obtain an IActiveScriptParse interface for loading snippets of scripting code, and then you update the IActiveScript's state to execute the code.
You can add custom objects to the script by implementing IDispatch-derived classes and then passing them to the engine using IActiveScript::AddNamedItem() and an IActiveScriptSite::GetItemInfo() callback.
There are examples of IActiveScript usage available on MSDN.

IHTMLDocument2->documentElement->outerHTML is too slow recreating HTML from DOM, is there a faster way?

I've got an IE BHO plugin that sends out via a COM call the HTML of a page that was loaded in the window.
// Note all error handling removed for readability :)
STDMETHODIMP CPlugin::get_HTML(long lMaxSize, BSTR *pbstrHTML)
{
CComPtr<IDispatch> pDispatch;
MSHTML::IHTMLDocument2Ptr pDocument2 = NULL;
MSHTML::IHTMLDocument3Ptr pDocument3 = NULL;
hr = m_spWebBrowser->get_Document(&pDispatch);
hr = pDispatch->QueryInterface(IID_IHTMLDocument3, (void**)&pDocument3);
MSHTML::IHTMLElementPtr pRoot = pDocument3->documentElement;
wstring strHTML = pRoot->outerHTML;
CComBSTR bstrHTML = strOutput.c_str();
bstrHTML.CopyTo(pbstrHTML);
}
However when it encounters a very large page (e.g. "http://sitemap.zillow.com/uncompressed/ForSale_Hood_MedPri_1.xml"), it takes 3 minutes to create the HTML from the DOM.
Is there a way to access the raw HTML/XML?
When you do a 'view page source' in IE, it pops up almost immediately, so internally IE must be using some API that can do what I want.
Thanks,
Shane.
It seems that in old versions of MSHTML, outerHTML had a O(n^2) performance. However, in newer versions (IE8) this problem is gone. If you have a choice, use IE8 or later.
Otherwise, using IPersistStream::Save is an option. But CreateStreamOnHGlobal won't help you since its implementation is also O(n^2). You'll have to use a custom IStream for that.
Included is an IStream implementation which was made for this purpose and supports quick writes:
#include <atlbase.h>
#include <atlcom.h>
#include <vector>
// an implementation of a write-only IStream.
// needed because the CreateStreamOnHGlobal implementation doesn't handle
// resizes well (N writes seem to take O(N^2) time)
class MyStream :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<MyStream>,
public IStreamImpl
{
public:
std::vector<char> buf;
BEGIN_COM_MAP(MyStream)
COM_INTERFACE_ENTRY(IStream)
END_COM_MAP()
STDMETHOD(Write) (const void * pv, ULONG cb, ULONG *pcbWritten);
};
/*
Usage:
CComPtr<IStream> stream;
hr = MyStream::CreateInstance(&stream);
// streamObj will be valid as long as IStream smart pointer lives
MyStream *streamObj = (MyStream*)stream.p;
*/
STDMETHODIMP MyStream::Write(const void * pv, ULONG cb, ULONG *pcbWritten)
{
buf.insert(buf.end(), (char*)pv, (char*)pv+cb);
return S_OK;
}
Yes, you can QI for IPersistStream and save to a memory stream created by CreateStreamOnHGlobal
Note the document must finished downloading (ready state needs to be complete).
Thanks Amnon, the following code is mostly working for me.
// an implementation of a write-only IStream.
// needed because the CreateStreamOnHGlobal implementation doesn't handle
// resizes well (N writes seem to take O(N^2) time)
class MyStream :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<MyStream>,
public IStream
{
public:
std::vector<char> buf;
BEGIN_COM_MAP(MyStream)
COM_INTERFACE_ENTRY(IStream)
END_COM_MAP()
STDMETHOD(Write) (const void * pv, ULONG cb, ULONG *pcbWritten);
// Implement IStream abstract functions
STDMETHOD(Read) (void *pv, ULONG cb, ULONG *pcbRead) { return S_OK; };
STDMETHOD(Seek) (LARGE_INTEGER dlibMove,DWORD dwOrigin,ULARGE_INTEGER *plibNewPosition) { return S_OK; };
STDMETHOD(SetSize) (ULARGE_INTEGER libNewSize) { return S_OK; };
STDMETHOD(CopyTo) (IStream *pstm,ULARGE_INTEGER cb,ULARGE_INTEGER *pcbRead,ULARGE_INTEGER *pcbWritten) { return S_OK; };
STDMETHOD(Commit) (DWORD grfCommitFlags) { return S_OK; };
STDMETHOD(Revert) () { return S_OK; };
STDMETHOD(LockRegion) (ULARGE_INTEGER libOffset,ULARGE_INTEGER cb,DWORD dwLockType) { return S_OK; };
STDMETHOD(UnlockRegion) (ULARGE_INTEGER libOffset,ULARGE_INTEGER cb,DWORD dwLockType) { return S_OK; };
STDMETHOD(Stat) (__RPC__out STATSTG *pstatstg,DWORD grfStatFlag) { return S_OK; };
STDMETHOD(Clone) (__RPC__deref_out_opt IStream **ppstm) { return S_OK; };
};
STDMETHODIMP MyStream::Write(const void * pv, ULONG cb, ULONG *pcbWritten)
{
buf.insert(buf.end(), (char*)pv, (char*)pv+cb);
return S_OK;
}
// Retrieves the HTML of the current page
STDMETHODIMP CPlugin::get_HTML(long lMaxSize, BSTR *pbstrHTML)
{
HRESULT hr = S_OK;
try
{
CComPtr<IDispatch> pDispatch;
MSHTML::IHTMLDocumentPtr pDocument = NULL;
CComPtr<IStream> mystream;
hr = MyStream::CreateInstance(&mystream);
// streamObj will be valid as long as IStream smart pointer lives
MyStream *streamObj = (MyStream*)mystream.p;
hr = m_spWebBrowser->get_Document(&pDispatch);
hr = pDispatch->QueryInterface(IID_IHTMLDocument, (void**)&pDocument);
IPersistStreamInitPtr persistStream = pDocument;
hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
hr = persistStream->Save(mystream, FALSE);
}
catch(...)
{
TRACE_FN("Got exception somewhere");
}
return hr;
}
Now the only problem left is how to figure why some it returns me single-byte chars most times, and double-byte chars at other times. Any ideas?
Thanks for the help.