如何将 HTML 字符串加载到嵌入式 WebBrowser 控件?

How to load HTML string to embedded WebBrowser control?

我知道网上有很多关于这个的文章,我试了很多。虽然我可以让我的浏览器加载互联网上的网页,但我无法设法让它从内存中加载 HTML。

大多数时候,下面的两种方法没有任何可见的效果;其他时候,他们会抛出错误。虽然别人都说"neither pPSI nor pHtmlDoc2 point to a valid object",但我不知道这是不是真的。我已经用注释标记了导致错误的行。

如果您想重现问题,我的问题末尾是完整的工作代码。

方法一

void WebBrowser::setHTML(const wchar_t *htmlSource) {
    HRESULT                   hr;

    IDispatch                 *pDispatch = 0;
    IHTMLDocument2            *pHtmlDoc2 = 0;
    IPersistStreamInit        *pPSI = 0;
    IStream                   *pStream = 0;
    HGLOBAL                   hHTMLContent;

    Navigate(L"about:blank");
    hr = webBrowser2->get_Document(&pDispatch);
    if (SUCCEEDED(hr) && pDispatch) hr = pDispatch->QueryInterface(IID_IHTMLDocument2, (void **)&pHtmlDoc2);
    //BSTR p = 0;
    //if (SUCCEEDED(hr)) hr = pHtmlDoc2->get_readyState(&p);

    if (SUCCEEDED(hr) && pHtmlDoc2) hr = pHtmlDoc2->QueryInterface(IID_IPersistStreamInit, (void **)&pPSI);


    // allocate global memory to copy the HTML content to
    hHTMLContent = ::GlobalAlloc(GMEM_MOVEABLE, (::wcslen(htmlSource) + 1) * sizeof(TCHAR));
    if (hHTMLContent)
    {
        wchar_t * p_content(static_cast<wchar_t *>(GlobalLock(hHTMLContent)));
        ::wcscpy(p_content, htmlSource);  // Debbug: p_content contains HTML string
        GlobalUnlock(hHTMLContent);

        // create a stream object based on the HTML content
        if (SUCCEEDED(hr) && pPSI) hr = ::CreateStreamOnHGlobal(hHTMLContent, TRUE, &pStream);
        {
            hr = pPSI->InitNew();         //////////// <- Sometime throw error.
            if (SUCCEEDED(hr)) hr = pPSI->Load(pStream); 
        }
    }
    if (pStream) pStream->Release();
    if (pPSI) pPSI->Release();
    if (pHtmlDoc2) pHtmlDoc2->Release();
    if (pDispatch) pDispatch->Release();

}

错误:

Exception thrown at 0x51539FB1 (mshtml.dll) in Test.exe: 0xC0000005: Access violation reading location 0x00000030

方法二

这个是根据 CodeProject 上的 this article 修改的。在那个源代码中,它工作得很好,但是当我尝试适应我的代码时它不起作用:

void WebBrowser::setHTML2(const wchar_t *htmlSource) {
    HRESULT                   hr;

    IDispatch                 *pDispatch = 0;
    IHTMLDocument2            *pHtmlDoc2 = 0;

    this->Navigate(L"about:blank");
    hr = this->webBrowser2->get_Document(&pDispatch);
    if (SUCCEEDED(hr) && pDispatch) hr = pDispatch->QueryInterface(IID_IHTMLDocument2, (void **)&pHtmlDoc2);
    if (SUCCEEDED(hr) && pHtmlDoc2)
    {
        BSTR bstr = SysAllocString(htmlSource);
        SAFEARRAY *psaStrings = SafeArrayCreateVector(VT_VARIANT, 0, 1);
        if (psaStrings)
        {
            VARIANT *param;
            hr = SafeArrayAccessData(psaStrings, (LPVOID*)&param);
            if (SUCCEEDED(hr) && param)
            {
                param->vt = VT_BSTR;
                param->bstrVal = bstr;
                //if (SUCCEEDED(hr)) hr = SafeArrayUnaccessData(psaStrings);
                if (SUCCEEDED(hr))
                {
                    hr = pHtmlDoc2->write(psaStrings); //////////// <- Sometime throw error.
                }
            }

            SafeArrayDestroy(psaStrings);
        }
    }
    if (pHtmlDoc2) pHtmlDoc2->Release();
    if (pDispatch) pDispatch->Release();
}

错误:

Exception thrown at 0x51577F2E (mshtml.dll) in Test.exe: 0xC0000005: Access violation reading location 0x000002C4

可能的第三种方法,来自 Microsoft 的 Loading HTML content from a Stream,建议我在 DWebBrowserEvents2::DocumentComplete 事件上从流中加载,我已经尝试过但未能弄清楚如何实现。

完整源代码

下面是完整代码。我创建了一个新的 Win32 项目和 create/modify 以下文件:

Browser.h

#include <comdef.h>
#include <Exdisp.h>
#include <ExDispid.h>
#include <MsHTML.h>
#include <Mshtmhst.h>
#include <string>
#include <tchar.h>
#include <Windows.h>

using namespace std;

class WebBrowser :
    public IOleClientSite,
    public IOleInPlaceSite,
    public IStorage
{

public:

    WebBrowser(HWND hWndParent);

    bool CreateBrowser();

    void SetText(const wchar_t* t);

    void setHTML(const wchar_t *htmlSource);
    void setHTML2(const wchar_t *htmlSource);

    RECT PixelToHiMetric(const RECT& _rc);

    virtual void SetRect(const RECT& _rc);

    // ----- Control methods -----

    void GoBack();

    void GoForward();

    void Refresh();

    void Navigate(wstring szUrl);

    // ----- IUnknown -----

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
        void**ppvObject) override;

    virtual ULONG STDMETHODCALLTYPE AddRef(void);

    virtual ULONG STDMETHODCALLTYPE Release(void);

    // ---------- IOleWindow ----------

    virtual HRESULT STDMETHODCALLTYPE GetWindow(
        __RPC__deref_out_opt HWND *phwnd) override;

    virtual HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(
        BOOL fEnterMode) override;

    // ---------- IOleInPlaceSite ----------

    virtual HRESULT STDMETHODCALLTYPE CanInPlaceActivate(void) override;

    virtual HRESULT STDMETHODCALLTYPE OnInPlaceActivate(void) override;

    virtual HRESULT STDMETHODCALLTYPE OnUIActivate(void) override;

    virtual HRESULT STDMETHODCALLTYPE GetWindowContext(
        __RPC__deref_out_opt IOleInPlaceFrame **ppFrame,
        __RPC__deref_out_opt IOleInPlaceUIWindow **ppDoc,
        __RPC__out LPRECT lprcPosRect,
        __RPC__out LPRECT lprcClipRect,
        __RPC__inout LPOLEINPLACEFRAMEINFO lpFrameInfo) override;

    virtual HRESULT STDMETHODCALLTYPE Scroll(
        SIZE scrollExtant) override;

    virtual HRESULT STDMETHODCALLTYPE OnUIDeactivate(
        BOOL fUndoable) override;

    virtual HWND GetControlWindow();

    virtual HRESULT STDMETHODCALLTYPE OnInPlaceDeactivate(void) override;

    virtual HRESULT STDMETHODCALLTYPE DiscardUndoState(void) override;

    virtual HRESULT STDMETHODCALLTYPE DeactivateAndUndo(void) override;

    virtual HRESULT STDMETHODCALLTYPE OnPosRectChange(
        __RPC__in LPCRECT lprcPosRect) override;

    // ---------- IOleClientSite ----------

    virtual HRESULT STDMETHODCALLTYPE SaveObject(void) override;

    virtual HRESULT STDMETHODCALLTYPE GetMoniker(
        DWORD dwAssign,
        DWORD dwWhichMoniker,
        __RPC__deref_out_opt IMoniker **ppmk) override;

    virtual HRESULT STDMETHODCALLTYPE GetContainer(
        __RPC__deref_out_opt IOleContainer **ppContainer) override;

    virtual HRESULT STDMETHODCALLTYPE ShowObject(void) override;
    virtual HRESULT STDMETHODCALLTYPE OnShowWindow(
        BOOL fShow) override;

    virtual HRESULT STDMETHODCALLTYPE RequestNewObjectLayout(void) override;

    // ----- IStorage -----

    virtual HRESULT STDMETHODCALLTYPE CreateStream(
        __RPC__in_string const OLECHAR *pwcsName,
        DWORD grfMode,
        DWORD reserved1,
        DWORD reserved2,
        __RPC__deref_out_opt IStream **ppstm) override;

    virtual HRESULT STDMETHODCALLTYPE OpenStream(
        const OLECHAR *pwcsName,
        void *reserved1,
        DWORD grfMode,
        DWORD reserved2,
        IStream **ppstm) override;

    virtual HRESULT STDMETHODCALLTYPE CreateStorage(
        __RPC__in_string const OLECHAR *pwcsName,
        DWORD grfMode,
        DWORD reserved1,
        DWORD reserved2,
        __RPC__deref_out_opt IStorage **ppstg) override;

    virtual HRESULT STDMETHODCALLTYPE OpenStorage(
        __RPC__in_opt_string const OLECHAR *pwcsName,
        __RPC__in_opt IStorage *pstgPriority,
        DWORD grfMode,
        __RPC__deref_opt_in_opt SNB snbExclude,
        DWORD reserved,
        __RPC__deref_out_opt IStorage **ppstg) override;

    virtual HRESULT STDMETHODCALLTYPE CopyTo(
        DWORD ciidExclude,
        const IID *rgiidExclude,
        __RPC__in_opt  SNB snbExclude,
        IStorage *pstgDest) override;

    virtual HRESULT STDMETHODCALLTYPE MoveElementTo(
        __RPC__in_string const OLECHAR *pwcsName,
        __RPC__in_opt IStorage *pstgDest,
        __RPC__in_string const OLECHAR *pwcsNewName,
        DWORD grfFlags) override;

    virtual HRESULT STDMETHODCALLTYPE Commit(
        DWORD grfCommitFlags) override;

    virtual HRESULT STDMETHODCALLTYPE Revert(void) override;

    virtual HRESULT STDMETHODCALLTYPE EnumElements(
        DWORD reserved1,
        void *reserved2,
        DWORD reserved3,
        IEnumSTATSTG **ppenum) override;

    virtual HRESULT STDMETHODCALLTYPE DestroyElement(
        __RPC__in_string const OLECHAR *pwcsName) override;

    virtual HRESULT STDMETHODCALLTYPE RenameElement(
        __RPC__in_string const OLECHAR *pwcsOldName,
        __RPC__in_string const OLECHAR *pwcsNewName) override;

    virtual HRESULT STDMETHODCALLTYPE SetElementTimes(
        __RPC__in_opt_string const OLECHAR *pwcsName,
        __RPC__in_opt const FILETIME *pctime,
        __RPC__in_opt const FILETIME *patime,
        __RPC__in_opt const FILETIME *pmtime) override;

    virtual HRESULT STDMETHODCALLTYPE SetClass(
        __RPC__in REFCLSID clsid) override;
    virtual HRESULT STDMETHODCALLTYPE SetStateBits(
        DWORD grfStateBits,
        DWORD grfMask) override;

    virtual HRESULT STDMETHODCALLTYPE Stat(
        __RPC__out STATSTG *pstatstg,
        DWORD grfStatFlag) override;

protected:

    IOleObject* oleObject;
    IOleInPlaceObject* oleInPlaceObject;

    IWebBrowser2* webBrowser2;

    LONG iComRefCount;

    RECT rObject;

    HWND hWndParent;
    HWND hWndControl;

};

Browser.cpp

#include "stdafx.h"
#include "Browser.h"



namespace tkString
{
    wchar_t* Format(const wchar_t* format, ...)
    {
        va_list args;
        va_start(args, format);

        wchar_t *w = NULL;
        int len = _vsnwprintf(NULL, 0, format, args) + 1;
        if (len > 0)
        {
            w = new wchar_t[len];
            w[0] = 0;
            _vsnwprintf_s(w, len, len, format, args);
        }
        va_end(args);

        return w;
    }
}


void WebBrowser::SetText(const wchar_t* t)
{
    const wchar_t* html = L"<html><head><meta http-equiv='x-ua-compatible' content='IE=edge'></head><body><code>%ls</code></body></html>";

    if (t)
    {
        wchar_t *w = tkString::Format(html, t);
        setHTML(w);
        //setHTML2(w);
        delete[] w;
    }
    else this->Navigate(L"https://google.com.vn");
}

void WebBrowser::setHTML(const wchar_t *htmlSource) {
    HRESULT                   hr;

    IDispatch                 *pDispatch = 0;
    IHTMLDocument2            *pHtmlDoc2 = 0;
    IPersistStreamInit        *pPSI = 0;
    IStream                   *pStream = 0;
    HGLOBAL                   hHTMLContent;

    Navigate(L"about:blank");
    hr = webBrowser2->get_Document(&pDispatch);
    if (SUCCEEDED(hr) && pDispatch) hr = pDispatch->QueryInterface(IID_IHTMLDocument2, (void **)&pHtmlDoc2);
    //BSTR p = 0;
    //if (SUCCEEDED(hr)) hr = pHtmlDoc2->get_readyState(&p);

    if (SUCCEEDED(hr) && pHtmlDoc2) hr = pHtmlDoc2->QueryInterface(IID_IPersistStreamInit, (void **)&pPSI);


    // allocate global memory to copy the HTML content to
    hHTMLContent = ::GlobalAlloc(GMEM_MOVEABLE, (::wcslen(htmlSource) + 1) * sizeof(TCHAR));
    if (hHTMLContent)
    {
        wchar_t * p_content(static_cast<wchar_t *>(GlobalLock(hHTMLContent)));
        ::wcscpy(p_content, htmlSource);
        GlobalUnlock(hHTMLContent);

        // create a stream object based on the HTML content
        if (SUCCEEDED(hr) && pPSI) hr = ::CreateStreamOnHGlobal(hHTMLContent, TRUE, &pStream);

        if (SUCCEEDED(hr) && pStream) hr = pPSI->InitNew();
        if (SUCCEEDED(hr)) hr = pPSI->Load(pStream);
    }
    if (pStream) pStream->Release();
    if (pPSI) pPSI->Release();
    if (pHtmlDoc2) pHtmlDoc2->Release();
    if (pDispatch) pDispatch->Release();

}

void WebBrowser::setHTML2(const wchar_t *htmlSource) {
    HRESULT                   hr;

    IDispatch                 *pDispatch = 0;
    IHTMLDocument2            *pHtmlDoc2 = 0;

    this->Navigate(L"about:blank");
    hr = this->webBrowser2->get_Document(&pDispatch);
    if (SUCCEEDED(hr) && pDispatch) hr = pDispatch->QueryInterface(IID_IHTMLDocument2, (void **)&pHtmlDoc2);
    if (SUCCEEDED(hr) && pHtmlDoc2)
    {
        BSTR bstr = SysAllocString(htmlSource);
        SAFEARRAY *psaStrings = SafeArrayCreateVector(VT_VARIANT, 0, 1);
        if (psaStrings)
        {
            VARIANT *param;
            hr = SafeArrayAccessData(psaStrings, (LPVOID*)&param);
            if (SUCCEEDED(hr) && param)
            {
                param->vt = VT_BSTR;
                param->bstrVal = bstr;
                //if (SUCCEEDED(hr)) hr = SafeArrayUnaccessData(psaStrings);
                if (SUCCEEDED(hr)) hr = pHtmlDoc2->write(psaStrings);
            }

            SafeArrayDestroy(psaStrings);
        }
    }
    if (pHtmlDoc2) pHtmlDoc2->Release();
    if (pDispatch) pDispatch->Release();
}


WebBrowser::WebBrowser(HWND _hWndParent)
{
    iComRefCount = 0;
    ::SetRect(&rObject, 0, 0, 600, 400);
    hWndParent = _hWndParent;

    if (CreateBrowser() == FALSE)
    {
        return;
    }
    webBrowser2->put_Visible(TRUE);
    ShowWindow(GetControlWindow(), SW_SHOW);

    this->Navigate(_T("about:blank"));
}

bool WebBrowser::CreateBrowser()
{
    HRESULT hr;
    hr = ::OleCreate(CLSID_WebBrowser,
        IID_IOleObject, OLERENDER_DRAW, 0, this, this,
        (void**)&oleObject);

    if (FAILED(hr))
    {
        MessageBox(NULL, _T("Cannot create oleObject CLSID_WebBrowser"),
            _T("Error"),
            MB_ICONERROR);
        return FALSE;
    }

    hr = oleObject->SetClientSite(this);
    hr = OleSetContainedObject(oleObject, TRUE);

    RECT posRect;
    ::SetRect(&rObject, 0, 0, 600, 400);
    hr = oleObject->DoVerb(OLEIVERB_INPLACEACTIVATE,
        NULL, this, -1, hWndParent, &posRect);
    if (FAILED(hr))
    {
        MessageBox(NULL, _T("oleObject->DoVerb() failed"),
            _T("Error"),
            MB_ICONERROR);
        return FALSE;
    }

    hr = oleObject->QueryInterface(&webBrowser2);
    if (FAILED(hr))
    {
        MessageBox(NULL, _T("oleObject->QueryInterface(&webBrowser2) failed"),
            _T("Error"),
            MB_ICONERROR);
        return FALSE;
    }

    return TRUE;
}

RECT WebBrowser::PixelToHiMetric(const RECT& _rc)
{
    static bool s_initialized = false;
    static int s_pixelsPerInchX, s_pixelsPerInchY;
    if (!s_initialized)
    {
        HDC hdc = ::GetDC(0);
        s_pixelsPerInchX = ::GetDeviceCaps(hdc, LOGPIXELSX);
        s_pixelsPerInchY = ::GetDeviceCaps(hdc, LOGPIXELSY);
        ::ReleaseDC(0, hdc);
        s_initialized = true;
    }

    RECT rc;
    rc.left = MulDiv(2540, _rc.left, s_pixelsPerInchX);
    rc.top = MulDiv(2540, _rc.top, s_pixelsPerInchY);
    rc.right = MulDiv(2540, _rc.right, s_pixelsPerInchX);
    rc.bottom = MulDiv(2540, _rc.bottom, s_pixelsPerInchY);
    return rc;
}

void WebBrowser::SetRect(const RECT& _rc)
{
    rObject = _rc;

    {
        RECT hiMetricRect = PixelToHiMetric(rObject);
        SIZEL sz;
        sz.cx = hiMetricRect.right - hiMetricRect.left;
        sz.cy = hiMetricRect.bottom - hiMetricRect.top;
        oleObject->SetExtent(DVASPECT_CONTENT, &sz);
    }

    if (oleInPlaceObject != 0)
    {
        oleInPlaceObject->SetObjectRects(&rObject, &rObject);
    }
}

// ----- Control methods -----

void WebBrowser::GoBack()
{
    this->webBrowser2->GoBack();
}

void WebBrowser::GoForward()
{
    this->webBrowser2->GoForward();
}

void WebBrowser::Refresh()
{
    this->webBrowser2->Refresh();
}

void WebBrowser::Navigate(wstring szUrl)
{
    bstr_t url(szUrl.c_str());
    variant_t flags(0x02u); //navNoHistory
    this->webBrowser2->Navigate(url, &flags, 0, 0, 0);
}

// ----- IUnknown -----

HRESULT STDMETHODCALLTYPE WebBrowser::QueryInterface(REFIID riid,
    void**ppvObject)
{
    if (riid == __uuidof(IUnknown))
    {
        (*ppvObject) = static_cast<IOleClientSite*>(this);
    }
    else if (riid == __uuidof(IOleInPlaceSite))
    {
        (*ppvObject) = static_cast<IOleInPlaceSite*>(this);
    }
    else
    {
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}

ULONG STDMETHODCALLTYPE WebBrowser::AddRef(void)
{
    iComRefCount++;
    return iComRefCount;
}

ULONG STDMETHODCALLTYPE WebBrowser::Release(void)
{
    iComRefCount--;
    return iComRefCount;
}

// ---------- IOleWindow ----------

HRESULT STDMETHODCALLTYPE WebBrowser::GetWindow(
    __RPC__deref_out_opt HWND *phwnd)
{
    (*phwnd) = hWndParent;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::ContextSensitiveHelp(
    BOOL fEnterMode)
{
    return E_NOTIMPL;
}

// ---------- IOleInPlaceSite ----------

HRESULT STDMETHODCALLTYPE WebBrowser::CanInPlaceActivate(void)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::OnInPlaceActivate(void)
{
    OleLockRunning(oleObject, TRUE, FALSE);
    oleObject->QueryInterface(&oleInPlaceObject);
    oleInPlaceObject->SetObjectRects(&rObject, &rObject);

    return S_OK;

}

HRESULT STDMETHODCALLTYPE WebBrowser::OnUIActivate(void)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::GetWindowContext(
    __RPC__deref_out_opt IOleInPlaceFrame **ppFrame,
    __RPC__deref_out_opt IOleInPlaceUIWindow **ppDoc,
    __RPC__out LPRECT lprcPosRect,
    __RPC__out LPRECT lprcClipRect,
    __RPC__inout LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
    HWND hwnd = hWndParent;

    (*ppFrame) = NULL;
    (*ppDoc) = NULL;
    (*lprcPosRect).left = rObject.left;
    (*lprcPosRect).top = rObject.top;
    (*lprcPosRect).right = rObject.right;
    (*lprcPosRect).bottom = rObject.bottom;
    *lprcClipRect = *lprcPosRect;

    lpFrameInfo->fMDIApp = false;
    lpFrameInfo->hwndFrame = hwnd;
    lpFrameInfo->haccel = NULL;
    lpFrameInfo->cAccelEntries = 0;

    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::Scroll(
    SIZE scrollExtant)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::OnUIDeactivate(
    BOOL fUndoable)
{
    return S_OK;
}

HWND WebBrowser::GetControlWindow()
{
    if (hWndControl != 0)
        return hWndControl;

    if (oleInPlaceObject == 0)
        return 0;

    oleInPlaceObject->GetWindow(&hWndControl);
    return hWndControl;
}

HRESULT STDMETHODCALLTYPE WebBrowser::OnInPlaceDeactivate(void)
{
    hWndControl = 0;
    oleInPlaceObject = 0;

    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::DiscardUndoState(void)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::DeactivateAndUndo(void)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::OnPosRectChange(
    __RPC__in LPCRECT lprcPosRect)
{
    return E_NOTIMPL;
}

// ---------- IOleClientSite ----------

HRESULT STDMETHODCALLTYPE WebBrowser::SaveObject(void)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::GetMoniker(
    DWORD dwAssign,
    DWORD dwWhichMoniker,
    __RPC__deref_out_opt IMoniker **ppmk)
{
    if ((dwAssign == OLEGETMONIKER_ONLYIFTHERE) &&
        (dwWhichMoniker == OLEWHICHMK_CONTAINER))
        return E_FAIL;

    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::GetContainer(
    __RPC__deref_out_opt IOleContainer **ppContainer)
{
    return E_NOINTERFACE;
}

HRESULT STDMETHODCALLTYPE WebBrowser::ShowObject(void)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::OnShowWindow(
    BOOL fShow)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::RequestNewObjectLayout(void)
{
    return E_NOTIMPL;
}

// ----- IStorage -----

HRESULT STDMETHODCALLTYPE WebBrowser::CreateStream(
    __RPC__in_string const OLECHAR *pwcsName,
    DWORD grfMode,
    DWORD reserved1,
    DWORD reserved2,
    __RPC__deref_out_opt IStream **ppstm)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::OpenStream(
    const OLECHAR *pwcsName,
    void *reserved1,
    DWORD grfMode,
    DWORD reserved2,
    IStream **ppstm)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::CreateStorage(
    __RPC__in_string const OLECHAR *pwcsName,
    DWORD grfMode,
    DWORD reserved1,
    DWORD reserved2,
    __RPC__deref_out_opt IStorage **ppstg)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::OpenStorage(
    __RPC__in_opt_string const OLECHAR *pwcsName,
    __RPC__in_opt IStorage *pstgPriority,
    DWORD grfMode,
    __RPC__deref_opt_in_opt SNB snbExclude,
    DWORD reserved,
    __RPC__deref_out_opt IStorage **ppstg)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::CopyTo(
    DWORD ciidExclude,
    const IID *rgiidExclude,
    __RPC__in_opt  SNB snbExclude,
    IStorage *pstgDest)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::MoveElementTo(
    __RPC__in_string const OLECHAR *pwcsName,
    __RPC__in_opt IStorage *pstgDest,
    __RPC__in_string const OLECHAR *pwcsNewName,
    DWORD grfFlags)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::Commit(
    DWORD grfCommitFlags)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::Revert(void)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::EnumElements(
    DWORD reserved1,
    void *reserved2,
    DWORD reserved3,
    IEnumSTATSTG **ppenum)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::DestroyElement(
    __RPC__in_string const OLECHAR *pwcsName)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::RenameElement(
    __RPC__in_string const OLECHAR *pwcsOldName,
    __RPC__in_string const OLECHAR *pwcsNewName)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::SetElementTimes(
    __RPC__in_opt_string const OLECHAR *pwcsName,
    __RPC__in_opt const FILETIME *pctime,
    __RPC__in_opt const FILETIME *patime,
    __RPC__in_opt const FILETIME *pmtime)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::SetClass(
    __RPC__in REFCLSID clsid)
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::SetStateBits(
    DWORD grfStateBits,
    DWORD grfMask)
{
    return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE WebBrowser::Stat(
    __RPC__out STATSTG *pstatstg,
    DWORD grfStatFlag)
{
    return E_NOTIMPL;
}

Win32Project1.cpp

#include "stdafx.h"
#include "Win32Project1.h"
#include "Browser.h"

HWND mainHWND;
WebBrowser* myBrowser = NULL;

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    OleInitialize(NULL);

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WIN32PROJECT1, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }
    myBrowser = new WebBrowser(mainHWND);
    //myBrowser->Navigate(L"https://google.com.vn");
    myBrowser->SetText(L"Hello!");

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32PROJECT1));

    MSG msg;

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32PROJECT1));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WIN32PROJECT1);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   mainHWND = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!mainHWND)
   {
      return FALSE;
   }

   ShowWindow(mainHWND, nCmdShow);
   UpdateWindow(mainHWND);

   return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    // Removed
    return (INT_PTR)FALSE;
}

Visual Studio 创建的其他源文件保持不变。

问题

什么问题导致我的程序无法加载 HTML 字符串,我该如何解决?或者您是否有任何工作示例(没有 MFC 或 ATL)将 HTML 字符串加载到 WebBrowser?

注意:我知道我可以将HTML字符串写入硬盘并将文件路径传递给浏览器,但我不想要硬盘完全参与。

注2:为了赢得赏金,请提供一个完整的工作代码示例,尽可能多地使用我的代码。浏览器应成功加载 HTML 字符串。即使它正在加载在线网页,它也应该停止处理这个新命令。

   Navigate(L"about:blank");

发布的源代码没有很好地演示问题。它没有显示错误,但也没有 "Hello"。只有 省略 Navigate() 调用才接近失败 nullptr 错误。代码还没有完成,它缺少捕获 DocumentComplete 事件所需的管道。只有在 触发此事件后,WebBrowser::setHtml() 中的代码才能工作。

让 WebBrowser class 实现 IDispatch 和 IUnknown 接口:

class WebBrowser :
    public IDispatch,
    public IUnknown,
    public IOleClientSite,
    public IOleInPlaceSite,
    public IStorage
{
private:
    HRESULT OnCompleted(DISPPARAMS* args);
    wchar_t* htmlSource;
    IConnectionPoint* callback;
    DWORD eventCookie;
    // etc...
};

请注意 htmlSource 变量现在如何成为 class 成员,字符串需要存储到 DocumentComplete 事件触发。

我们需要实现 IDispatch。这很容易做到,我将代码内联:

// ---------- IDispatch ----------
HRESULT GetTypeInfoCount(UINT *pctinfo) {  return E_FAIL; }
HRESULT GetTypeInfo(UINT, LCID, ITypeInfo **) { return E_FAIL; }
HRESULT GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { return E_FAIL; }

HRESULT Invoke(DISPID dispIdMember, REFIID, LCID, WORD,
    DISPPARAMS *pDispParams, VARIANT *pVarResult,
    EXCEPINFO*, UINT*) {
    if (dispIdMember == DISPID_DOCUMENTCOMPLETE) return OnCompleted(pDispParams);
    else return S_OK;
}

QueryInterface() 需要调整:

HRESULT STDMETHODCALLTYPE WebBrowser::QueryInterface(REFIID riid, void**ppvObject)
{
    if      (riid == __uuidof(IUnknown))        *ppvObject = static_cast<IUnknown*>(this);
    else if (riid == __uuidof(IDispatch))       *ppvObject = static_cast<IDispatch*>(this);
    else if (riid == __uuidof(IOleClientSite))  *ppvObject = static_cast<IOleClientSite*>(this);
    else if (riid == __uuidof(IOleInPlaceSite)) *ppvObject = static_cast<IOleInPlaceSite*>(this);
    else
        return E_NOINTERFACE;
    AddRef();
    return S_OK;
}

构造函数需要订阅事件接口:

WebBrowser::WebBrowser(HWND _hWndParent)
{
    //...
    // appended:
    htmlSource = nullptr;
    IConnectionPointContainer* container = nullptr;
    webBrowser2->QueryInterface(IID_IConnectionPointContainer, (void**)&container);
    container->FindConnectionPoint(__uuidof(DWebBrowserEvents2), &callback);
    IUnknown* punk = nullptr;
    this->QueryInterface(IID_IUnknown, (void**)&punk);
    callback->Advise(punk, &eventCookie);
    punk->Release();
    container->Release();
}

setText() 函数变得非常简单,我们根本不用担心setHtml()。我们将只设置 htmlSource 成员,以便我们知道在 DocumentComplete 事件触发时要做什么:

void WebBrowser::SetText(const wchar_t* t)
{
    const wchar_t* html = L"<html><head><meta http-equiv='x-ua-compatible' content='IE=edge'></head><body><code>%ls</code></body></html>";
    if (htmlSource) delete[] htmlSource;
    htmlSource = tkString::Format(html, t);
    this->Navigate(L"about::blank");
}

最后添加了在 DocumentComplete 事件触发时运行的函数。大部分代码从 setHtml:

HRESULT WebBrowser::OnCompleted(DISPPARAMS* args) {
    HRESULT                   hr;

    IDispatch                 *pDispatch = 0;
    IHTMLDocument2            *pHtmlDoc2 = 0;
    IPersistStreamInit        *pPSI = 0;
    IStream                   *pStream = 0;
    HGLOBAL                   hHTMLContent;


    if (!htmlSource) return S_OK;
    hr = webBrowser2->get_Document(&pDispatch);
    if (SUCCEEDED(hr) && pDispatch) hr = pDispatch->QueryInterface(IID_IHTMLDocument2, (void **)&pHtmlDoc2);
    if (SUCCEEDED(hr) && pHtmlDoc2) hr = pHtmlDoc2->QueryInterface(IID_IPersistStreamInit, (void **)&pPSI);


    // allocate global memory to copy the HTML content to
    hHTMLContent = ::GlobalAlloc(GMEM_MOVEABLE, (::wcslen(htmlSource) + 1) * sizeof(TCHAR));
    if (hHTMLContent)
    {
        wchar_t * p_content(static_cast<wchar_t *>(GlobalLock(hHTMLContent)));
        ::wcscpy(p_content, htmlSource);
        GlobalUnlock(hHTMLContent);

        // create a stream object based on the HTML content
        if (SUCCEEDED(hr) && pPSI) hr = ::CreateStreamOnHGlobal(hHTMLContent, TRUE, &pStream);

        if (SUCCEEDED(hr) && pStream) hr = pPSI->InitNew();
        if (SUCCEEDED(hr)) hr = pPSI->Load(pStream);
    }
    if (pStream) pStream->Release();
    if (pPSI) pPSI->Release();
    if (pHtmlDoc2) pHtmlDoc2->Release();
    if (pDispatch) pDispatch->Release();
    delete[] htmlSource;
    htmlSource = nullptr;
    return S_OK;
}

我会将适当的清理留作 // 待办事项。 运行 此代码现在生成所需的结果:

基于,下面是完整的工作代码,供任何想测试的人使用。
创建名为 "Win32Project1" 和 add/edit 的新 Win32 项目,文件如下:

WebBrowser.h

#pragma once

#include <comdef.h>
#include <Exdisp.h>
#include <ExDispid.h>
#include <MsHTML.h>
#include <Mshtmhst.h>
#include <string>
#include <tchar.h>
#include <Windows.h>

class WebBrowser :
    public IDispatch,
    public IOleClientSite,
    public IOleInPlaceSite,
    public IStorage
{
public:

    WebBrowser(HWND hWndParent);

    void SetText(const wchar_t* t);
    void setHTML(const wchar_t *htmlText, bool shouldWrapinBODYtag = true);

    // ----- Create Browser -----
    HRESULT RegisterGIWebBrowser();
    HRESULT GetGIWebBrowser(IWebBrowser2** pwb);
    bool CreateBrowser();

    // ----- For Resize Window -----
    RECT PixelToHiMetric(const RECT& _rc);
    virtual void SetRect(const RECT& _rc);
    virtual HWND GetControlWindow();

    // ----- Control methods -----
    void GoBack();
    void GoForward();
    void Refresh();
    void Navigate(const wchar_t *szUrl);

    // ----- IUnknown -----
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void**ppvObject) override;
    virtual ULONG STDMETHODCALLTYPE AddRef(void);
    virtual ULONG STDMETHODCALLTYPE Release(void);

    // ---------- IOleWindow ----------
    virtual HRESULT STDMETHODCALLTYPE GetWindow(__RPC__deref_out_opt HWND *phwnd) override;
    virtual HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode) override;

    // ---------- IOleInPlaceSite ----------
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::CanInPlaceActivate(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::OnUIActivate(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::OnInPlaceActivate(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::GetWindowContext(__RPC__deref_out_opt IOleInPlaceFrame **ppFrame, __RPC__deref_out_opt IOleInPlaceUIWindow **ppDoc, __RPC__out LPRECT lprcPosRect, __RPC__out LPRECT lprcClipRect, __RPC__inout LPOLEINPLACEFRAMEINFO lpFrameInfo) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::Scroll(SIZE scrollExtant) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::OnUIDeactivate(BOOL fUndoable) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::DiscardUndoState(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::DeactivateAndUndo(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::OnPosRectChange(__RPC__in LPCRECT lprcPosRect) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::OnInPlaceDeactivate(void) override;

    // ---------- IOleClientSite ----------
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::SaveObject(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::GetContainer(__RPC__deref_out_opt IOleContainer **ppContainer) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::ShowObject(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::OnShowWindow(BOOL fShow) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::RequestNewObjectLayout(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, __RPC__deref_out_opt IMoniker **ppmk) override;

    // ----- IStorage -----
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::CreateStream(__RPC__in_string const OLECHAR *pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, __RPC__deref_out_opt IStream **ppstm) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::OpenStream(const OLECHAR *pwcsName, void *reserved1, DWORD grfMode, DWORD reserved2, IStream **ppstm) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::CreateStorage(__RPC__in_string const OLECHAR *pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, __RPC__deref_out_opt IStorage **ppstg) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::OpenStorage(__RPC__in_opt_string const OLECHAR *pwcsName, __RPC__in_opt IStorage *pstgPriority, DWORD grfMode, __RPC__deref_opt_in_opt SNB snbExclude, DWORD reserved, __RPC__deref_out_opt IStorage **ppstg) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::CopyTo(DWORD ciidExclude, const IID *rgiidExclude, __RPC__in_opt  SNB snbExclude, IStorage *pstgDest) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::MoveElementTo(__RPC__in_string const OLECHAR *pwcsName, __RPC__in_opt IStorage *pstgDest, __RPC__in_string const OLECHAR *pwcsNewName, DWORD grfFlags) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::Commit(DWORD grfCommitFlags) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::Revert(void) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::EnumElements(DWORD reserved1, void *reserved2, DWORD reserved3, IEnumSTATSTG **ppenum) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::DestroyElement(__RPC__in_string const OLECHAR *pwcsName) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::RenameElement(__RPC__in_string const OLECHAR *pwcsOldName, __RPC__in_string const OLECHAR *pwcsNewName) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::SetElementTimes(__RPC__in_opt_string const OLECHAR *pwcsName, __RPC__in_opt const FILETIME *pctime, __RPC__in_opt const FILETIME *patime, __RPC__in_opt const FILETIME *pmtime) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::SetClass(__RPC__in REFCLSID clsid) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::SetStateBits(DWORD grfStateBits, DWORD grfMask) override;
    virtual HRESULT STDMETHODCALLTYPE WebBrowser::Stat(__RPC__out STATSTG *pstatstg, DWORD grfStatFlag) override;

    // ---------- IDispatch ----------
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(__RPC__out UINT *pctinfo) override;
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT, LCID, __RPC__deref_out_opt ITypeInfo **) override;
    virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(__RPC__in REFIID riid, __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames, __RPC__in_range(0, 16384) UINT cNames, LCID lcid, __RPC__out_ecount_full(cNames) DISPID *rgDispId) override;
    virtual HRESULT STDMETHODCALLTYPE Invoke(_In_  DISPID dispIdMember, _In_  REFIID, _In_  LCID, _In_  WORD, _In_  DISPPARAMS *pDispParams, _Out_opt_ VARIANT *pVarResult, _Out_opt_ EXCEPINFO*, _Out_opt_ UINT*) override;


private:
    IOleObject* oleObject;
    IOleInPlaceObject* oleInPlaceObject;
    IWebBrowser2* webBrowser2;
    LONG iComRefCount;
    RECT rObject;
    HWND hWndParent;
    HWND hWndControl;

    HRESULT OnCompleted(DISPPARAMS* args);
    wchar_t* htmlSource;
    IConnectionPoint* callback;
    DWORD eventCookie;
    DWORD dwGIWebBrowserCookie;

    bool isFullyCreated;
};

WebBrowser.cpp

#include "stdafx.h"
#include "WebBrowser.h"

namespace tkString
{
    wchar_t* Format(const wchar_t* format, ...)
    {
        va_list args;
        va_start(args, format);

        wchar_t* w = NULL;
        int len = _vscwprintf(format, args) + 1;
        if (len > 1)
        {
            w = new wchar_t[len];
            w[0] = 0;
            _vsnwprintf_s(w, len, len, format, args);
        }
        va_end(args);

        return w;
    }
}

void WebBrowser::SetText(const wchar_t* t)
{
    if (isFullyCreated)
    {
        // TODO: escape text
        const wchar_t* html = L"<html><head><meta http-equiv='x-ua-compatible' content='IE=edge'></head><body><code>%ls</code></body></html>";
        if (htmlSource) delete[] htmlSource;
        htmlSource = tkString::Format(html, t);
        this->Navigate(L"about:blank");
    }
    else {}
}

void WebBrowser::setHTML(const wchar_t* htmlText, bool shouldWrapinBODYtag)
{
    if (isFullyCreated)
    {
        if (htmlSource) delete[] htmlSource;
        htmlSource = NULL;
        if (shouldWrapinBODYtag)
        {
            const wchar_t* html = L"<html><head><meta http-equiv='x-ua-compatible' content='IE=edge'></head><body>%ls</body></html>";
            htmlSource = tkString::Format(html, htmlText);
        }
        else
        {
            if (htmlText)
            {
                size_t len = wcslen(htmlText) + 1;
                htmlSource = new wchar_t[len];
                wmemcpy(htmlSource, htmlText, len);
            }
        }
        this->Navigate(L"about:blank");
    }
    else {}
}

HRESULT WebBrowser::OnCompleted(DISPPARAMS* args)
{
    HRESULT                   hr;

    IDispatch                 *pDispatch = 0;
    IHTMLDocument2            *pHtmlDoc2 = 0;
    IPersistStreamInit        *pPSI = 0;
    IStream                   *pStream = 0;
    HGLOBAL                   hHTMLContent;


    if (!htmlSource) return S_OK;
    hr = webBrowser2->get_Document(&pDispatch);
    if (SUCCEEDED(hr) && pDispatch) hr = pDispatch->QueryInterface(IID_IHTMLDocument2, (void **)&pHtmlDoc2);
    if (SUCCEEDED(hr) && pHtmlDoc2) hr = pHtmlDoc2->QueryInterface(IID_IPersistStreamInit, (void **)&pPSI);


    // allocate global memory to copy the HTML content to
    size_t len = wcslen(htmlSource) + 1;
    hHTMLContent = ::GlobalAlloc(GMEM_MOVEABLE, len * sizeof(wchar_t));
    if (hHTMLContent)
    {
        wchar_t * p_content(static_cast<wchar_t *>(GlobalLock(hHTMLContent)));
        wmemcpy_s(p_content, len, htmlSource, len);
        GlobalUnlock(hHTMLContent);

        // create a stream object based on the HTML content
        if (SUCCEEDED(hr) && pPSI) hr = ::CreateStreamOnHGlobal(hHTMLContent, TRUE, &pStream);

        if (SUCCEEDED(hr) && pStream) hr = pPSI->InitNew();
        if (SUCCEEDED(hr)) hr = pPSI->Load(pStream);
    }
    if (pStream) pStream->Release();
    if (pPSI) pPSI->Release();
    if (pHtmlDoc2) pHtmlDoc2->Release();
    if (pDispatch) pDispatch->Release();

    delete[] htmlSource;
    htmlSource = nullptr;
    return S_OK;
}

// ----- Control methods -----
void WebBrowser::Navigate(const wchar_t *szUrl)
{
    HRESULT hr = E_FAIL;
    IWebBrowser2 *pwb = NULL;
    hr = GetGIWebBrowser(&pwb);
    if (SUCCEEDED(hr) && pwb)
    {
        bstr_t url(szUrl);
        variant_t flags(0x02u); //navNoHistory
        pwb->Navigate(url, &flags, 0, 0, 0);
    }
    else { /* Handle error */ }
}

void WebBrowser::GoBack() { this->webBrowser2->GoBack(); }
void WebBrowser::GoForward() { this->webBrowser2->GoForward(); }
void WebBrowser::Refresh() { this->webBrowser2->Refresh(); }

WebBrowser::WebBrowser(HWND _hWndParent) :
isFullyCreated(false)
{
    HRESULT hr = E_FAIL;
    htmlSource = nullptr;
    IConnectionPointContainer* container = nullptr;
    IUnknown* punk = nullptr;

    iComRefCount = 0;
    ::SetRect(&rObject, 0, 0, 300, 300);
    hWndParent = _hWndParent;

    if (CreateBrowser())
    {
        this->Navigate(L"about:blank");
        hr = RegisterGIWebBrowser();
        if (SUCCEEDED(hr))
        {
            webBrowser2->put_Visible(TRUE);
            ShowWindow(GetControlWindow(), SW_SHOW);

            hr = webBrowser2->QueryInterface(IID_IConnectionPointContainer, (void**)&container);
            if (SUCCEEDED(hr) && container) hr = container->FindConnectionPoint(__uuidof(DWebBrowserEvents2), &callback);
            if (SUCCEEDED(hr) && container) hr = this->QueryInterface(IID_IUnknown, (void**)&punk);
            if (SUCCEEDED(hr) && container) hr = callback->Advise(punk, &eventCookie);
            if (SUCCEEDED(hr) && eventCookie) isFullyCreated = true;

            if (punk) punk->Release();
            if (container) container->Release();
        }
    }
}

bool WebBrowser::CreateBrowser()
{
    HRESULT hr;
    hr = ::OleCreate(CLSID_WebBrowser, IID_IOleObject, OLERENDER_DRAW, 0, this, this, (void**)&oleObject);
    if (SUCCEEDED(hr) && oleObject)
    {
        if (SUCCEEDED(hr)) hr = oleObject->SetClientSite(this);
        if (SUCCEEDED(hr)) hr = OleSetContainedObject(oleObject, TRUE);

        if (SUCCEEDED(hr))
        {
            RECT posRect;
            ::SetRect(&posRect, -300, -300, 300, 300);
            hr = oleObject->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, -1, hWndParent, &posRect);
        }
        hr = oleObject->QueryInterface(&webBrowser2);
    }
    if (FAILED(hr) || !webBrowser2)
    {
        MessageBox(NULL, L"Create Browser failed!", L"Error", MB_ICONERROR);
        return false;
    }
    return true;
}

RECT WebBrowser::PixelToHiMetric(const RECT& _rc)
{
    static bool s_initialized = false;
    static int s_pixelsPerInchX, s_pixelsPerInchY;
    if (!s_initialized)
    {
        HDC hdc = ::GetDC(0);
        s_pixelsPerInchX = ::GetDeviceCaps(hdc, LOGPIXELSX);
        s_pixelsPerInchY = ::GetDeviceCaps(hdc, LOGPIXELSY);
        ::ReleaseDC(0, hdc);
        s_initialized = true;
    }

    RECT rc;
    rc.left = MulDiv(2540, _rc.left, s_pixelsPerInchX);
    rc.top = MulDiv(2540, _rc.top, s_pixelsPerInchY);
    rc.right = MulDiv(2540, _rc.right, s_pixelsPerInchX);
    rc.bottom = MulDiv(2540, _rc.bottom, s_pixelsPerInchY);
    return rc;
}

void WebBrowser::SetRect(const RECT& _rc)
{
    rObject = _rc;

    {
        RECT hiMetricRect = PixelToHiMetric(rObject);
        SIZEL sz;
        sz.cx = hiMetricRect.right - hiMetricRect.left;
        sz.cy = hiMetricRect.bottom - hiMetricRect.top;
        oleObject->SetExtent(DVASPECT_CONTENT, &sz);
    }

    if (oleInPlaceObject != 0)
    {
        oleInPlaceObject->SetObjectRects(&rObject, &rObject);
    }
}

HWND WebBrowser::GetControlWindow() { if (!hWndControl && oleInPlaceObject) oleInPlaceObject->GetWindow(&hWndControl); return hWndControl; }

// Register IWebBrowser2 instance for use in threads
HRESULT WebBrowser::RegisterGIWebBrowser()
{
    HRESULT hr = E_FAIL;
    IUnknown*   pIUnknown = NULL;
    IGlobalInterfaceTable* pIGlobalInterfaceTable = NULL;
    dwGIWebBrowserCookie = 0;

    hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, (void **)&pIGlobalInterfaceTable);
    if (SUCCEEDED(hr) && pIGlobalInterfaceTable) hr = webBrowser2->QueryInterface(IID_IUnknown, (void**)&pIUnknown);
    if (SUCCEEDED(hr) && pIUnknown) hr = pIGlobalInterfaceTable->RegisterInterfaceInGlobal(pIUnknown, __uuidof(IWebBrowser2), &dwGIWebBrowserCookie);

    if (pIUnknown) pIUnknown->Release();
    if (pIGlobalInterfaceTable) pIGlobalInterfaceTable->Release();

    if (SUCCEEDED(hr) && dwGIWebBrowserCookie) return hr;
    return E_FAIL;
}

// Get IWebBrowser2 instance from threads
HRESULT WebBrowser::GetGIWebBrowser(IWebBrowser2** pwb)
{
    HRESULT hr = E_FAIL;
    IGlobalInterfaceTable* pIGlobalInterfaceTable = NULL;
    *pwb = NULL;

    if (dwGIWebBrowserCookie) hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable, (void **)&pIGlobalInterfaceTable);
    if (SUCCEEDED(hr) && pIGlobalInterfaceTable) hr = pIGlobalInterfaceTable->GetInterfaceFromGlobal(dwGIWebBrowserCookie, __uuidof(IWebBrowser2), (void **)pwb);

    if (pIGlobalInterfaceTable) pIGlobalInterfaceTable->Release();
    return hr;
}

// ----- IUnknown -----
ULONG STDMETHODCALLTYPE WebBrowser::AddRef(void) { return ++iComRefCount; }
ULONG STDMETHODCALLTYPE WebBrowser::Release(void) { return --iComRefCount; }

HRESULT STDMETHODCALLTYPE WebBrowser::QueryInterface(REFIID riid, void**ppvObject)
{
    if (riid == __uuidof(IUnknown))        *ppvObject = static_cast<IDispatch*>(this);
    else if (riid == __uuidof(IDispatch))       *ppvObject = static_cast<IDispatch*>(this);
    else if (riid == __uuidof(IOleClientSite))  *ppvObject = static_cast<IOleClientSite*>(this);
    else if (riid == __uuidof(IOleInPlaceSite)) *ppvObject = static_cast<IOleInPlaceSite*>(this);
    else
        return E_NOINTERFACE;

    AddRef();
    return S_OK;
}

// ---------- IOleInPlaceSite ----------
HRESULT STDMETHODCALLTYPE WebBrowser::CanInPlaceActivate(void){ return S_OK; }
HRESULT STDMETHODCALLTYPE WebBrowser::OnUIActivate(void){ return S_OK; }

HRESULT STDMETHODCALLTYPE WebBrowser::OnInPlaceActivate(void)
{
    OleLockRunning(oleObject, TRUE, FALSE);
    oleObject->QueryInterface(&oleInPlaceObject);
    oleInPlaceObject->SetObjectRects(&rObject, &rObject);
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::GetWindowContext(__RPC__deref_out_opt IOleInPlaceFrame **ppFrame, __RPC__deref_out_opt IOleInPlaceUIWindow **ppDoc, __RPC__out LPRECT lprcPosRect, __RPC__out LPRECT lprcClipRect, __RPC__inout LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
    HWND hwnd = hWndParent;

    (*ppFrame) = NULL;
    (*ppDoc) = NULL;
    (*lprcPosRect).left = rObject.left;
    (*lprcPosRect).top = rObject.top;
    (*lprcPosRect).right = rObject.right;
    (*lprcPosRect).bottom = rObject.bottom;
    *lprcClipRect = *lprcPosRect;

    lpFrameInfo->fMDIApp = false;
    lpFrameInfo->hwndFrame = hwnd;
    lpFrameInfo->haccel = NULL;
    lpFrameInfo->cAccelEntries = 0;

    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebBrowser::Scroll(SIZE scrollExtant) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::OnUIDeactivate(BOOL fUndoable) { return S_OK; }
HRESULT STDMETHODCALLTYPE WebBrowser::DiscardUndoState(void) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::DeactivateAndUndo(void) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::OnPosRectChange(__RPC__in LPCRECT lprcPosRect) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::OnInPlaceDeactivate(void) { hWndControl = 0; oleInPlaceObject = 0; return S_OK; }

// ---------- IDispatch ----------
HRESULT WebBrowser::GetTypeInfoCount(__RPC__out UINT *pctinfo) { return E_FAIL; }
HRESULT WebBrowser::GetTypeInfo(UINT, LCID, __RPC__deref_out_opt ITypeInfo **) { return E_FAIL; }
HRESULT WebBrowser::GetIDsOfNames(__RPC__in REFIID riid, __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames, __RPC__in_range(0, 16384) UINT cNames, LCID lcid, __RPC__out_ecount_full(cNames) DISPID *rgDispId) { return E_FAIL; }

HRESULT STDMETHODCALLTYPE WebBrowser::Invoke(_In_  DISPID dispIdMember, _In_  REFIID, _In_  LCID, _In_  WORD, _In_  DISPPARAMS *pDispParams, _Out_opt_ VARIANT *pVarResult, _Out_opt_ EXCEPINFO*, _Out_opt_ UINT*)
{
    if (dispIdMember == DISPID_DOCUMENTCOMPLETE) return OnCompleted(pDispParams);
    else return S_OK;
}

// ---------- IOleWindow ----------
HRESULT STDMETHODCALLTYPE WebBrowser::GetWindow(__RPC__deref_out_opt HWND *phwnd) { (*phwnd) = hWndParent; return S_OK; }
HRESULT STDMETHODCALLTYPE WebBrowser::ContextSensitiveHelp(BOOL fEnterMode) { return E_NOTIMPL; }

// ---------- IOleClientSite ----------
HRESULT STDMETHODCALLTYPE WebBrowser::SaveObject(void) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::GetContainer(__RPC__deref_out_opt IOleContainer **ppContainer) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::ShowObject(void) { return S_OK; }
HRESULT STDMETHODCALLTYPE WebBrowser::OnShowWindow(BOOL fShow) { return S_OK; }
HRESULT STDMETHODCALLTYPE WebBrowser::RequestNewObjectLayout(void) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, __RPC__deref_out_opt IMoniker **ppmk) { if ((dwAssign == OLEGETMONIKER_ONLYIFTHERE) && (dwWhichMoniker == OLEWHICHMK_CONTAINER))return E_FAIL; return E_NOTIMPL; }

// ----- IStorage -----
HRESULT STDMETHODCALLTYPE WebBrowser::CreateStream(__RPC__in_string const OLECHAR *pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, __RPC__deref_out_opt IStream **ppstm) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::OpenStream(const OLECHAR *pwcsName, void *reserved1, DWORD grfMode, DWORD reserved2, IStream **ppstm) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::CreateStorage(__RPC__in_string const OLECHAR *pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, __RPC__deref_out_opt IStorage **ppstg) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::OpenStorage(__RPC__in_opt_string const OLECHAR *pwcsName, __RPC__in_opt IStorage *pstgPriority, DWORD grfMode, __RPC__deref_opt_in_opt SNB snbExclude, DWORD reserved, __RPC__deref_out_opt IStorage **ppstg) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::CopyTo(DWORD ciidExclude, const IID *rgiidExclude, __RPC__in_opt  SNB snbExclude, IStorage *pstgDest) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::MoveElementTo(__RPC__in_string const OLECHAR *pwcsName, __RPC__in_opt IStorage *pstgDest, __RPC__in_string const OLECHAR *pwcsNewName, DWORD grfFlags) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::Commit(DWORD grfCommitFlags) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::Revert(void) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::EnumElements(DWORD reserved1, void *reserved2, DWORD reserved3, IEnumSTATSTG **ppenum) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::DestroyElement(__RPC__in_string const OLECHAR *pwcsName) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::RenameElement(__RPC__in_string const OLECHAR *pwcsOldName, __RPC__in_string const OLECHAR *pwcsNewName) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::SetElementTimes(__RPC__in_opt_string const OLECHAR *pwcsName, __RPC__in_opt const FILETIME *pctime, __RPC__in_opt const FILETIME *patime, __RPC__in_opt const FILETIME *pmtime) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::SetClass(__RPC__in REFCLSID clsid) { return S_OK; }
HRESULT STDMETHODCALLTYPE WebBrowser::SetStateBits(DWORD grfStateBits, DWORD grfMask) { return E_NOTIMPL; }
HRESULT STDMETHODCALLTYPE WebBrowser::Stat(__RPC__out STATSTG *pstatstg, DWORD grfStatFlag) { return E_NOTIMPL; }

Win32Project1.cpp

#include "stdafx.h"
#include "Win32Project1.h"
#include "WebBrowser.h"

HWND hWnd = NULL;
HWND tkWebBrowserHWND = NULL;
WebBrowser* tkWebBrowser = NULL;
void callFromThread();

ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    OleInitialize(NULL);
    MyRegisterClass(hInstance);

    if (!InitInstance(hInstance, nCmdShow)) return FALSE;

    tkWebBrowserHWND = CreateWindow(L"Static", NULL, WS_CHILD | WS_VISIBLE, 221, 0, 300, 300, hWnd, NULL, hInstance, 0);
    tkWebBrowser = new WebBrowser(tkWebBrowserHWND);

    // Use one of following command
    //tkWebBrowser->SetText(L"Hello!");
    //tkWebBrowser->setHTML(L"<h1>Title</h1><div>Content</div>");
    //tkWebBrowser->setHTML(L"<html><body><h1>Title 2</h1><div>Content 2</div></body></html>", false);
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)callFromThread, 0, 0, 0);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

void callFromThread()
{
    OleInitialize(NULL);

    // Use one of following command
    //tkWebBrowser->SetText(L"Hello from thread!");
    //tkWebBrowser->setHTML(L"<h1>Title from thread</h1><div>Content from thread</div>");
    tkWebBrowser->setHTML(L"<html><body><h1>Title 2 from thread</h1><div>Content 2 from thread</div></body></html>", false);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;

    switch (message)
    {
    case WM_COMMAND:
        wmId = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        switch (wmId)
        {
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

HINSTANCE hInst;
const wchar_t* szTitle = L"Title";
const wchar_t* szWindowClass = L"Window Class";

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32PROJECT1));
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCE(IDC_WIN32PROJECT1);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassEx(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance;

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd) return FALSE;

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}