一步一步将CEF3嵌入到MFC视图中

一、CEF简介

Chromium Embedded Framework (CEF) 是一个开源项目,允许在第三方应用中集成谷歌浏览器。原生支持C和C++,通过非官方扩展支持其他语言,如C#, Java, Delphi 或 Python。本文较详细的记录了将CEF集成到MFC单文档程序的过程。

使用环境为:windows10 64位系统,visual studio 2017,按照32位编译。

二、准备工作

下载CEF,最新版本在http://cefbuilds.com/下载,但好像被墙了,我一直没法登陆,后来在下载链接下载到cef_binary_3.2171.1901_windows32

一步一步将CEF3嵌入到MFC视图中_第1张图片
Paste_Image.png

解压到任意目录,用VS2017打开cefclient2010.sln,升级到VS2017格式。编译其中的libcef_dll_wrapper项目,会出现以下错误。

一步一步将CEF3嵌入到MFC视图中_第2张图片
Paste_Image.png

产生原因为:SDK中的solution文件是用VS2010产生的,我的VS版本为VS2017,转换后,有些Project编译选项中,Treat Warnings As Errors(把警告看作错误来处理)选项开启了。按照下图,把此选项关闭,就可以正常编译了。


一步一步将CEF3嵌入到MFC视图中_第3张图片
Paste_Image.png

编译成功后,在\out\Debug\lib 这个目录里会生成一个文件:libcef_dll_wrapper.lib,这个文件在开发我们项目的时候会使用。

三、新建MFC项目

新建一个MFC SDI项目

设置为MFC标准,在静态库中使用MFC。


一步一步将CEF3嵌入到MFC视图中_第4张图片
新建MFC项目

准备CEF文件

  • cef_binary_3.2171.1901_windows32中的include目录拷贝到CEFDemo文件夹中。
  • cef_binary_3.2171.1901_windows32目录下Debug中的dll文件拷贝到CEFDemo的Debug目录中。
  • libcef.lib和上面生成的libcef_dll_wrapper.lib拷贝到项目文件夹下的lib目录(新建)中。
  • 配置项目属性
  • C/C++ ->常规附加包含目录$(ProjectDir)lib\;$(ProjectDir)include\;
  • 链接器->输入添加附加依赖项$(ProjectDir)lib\libcef.lib;$(ProjectDir)lib\libcef_dll_wrapper.lib;
  • Debug目录下新建一个html文件夹,并添加一个index.html用来在程序界面中呈现。

添加必要的类

添加ClientHandler类,具体如下:

#pragma once

#include "include/base/cef_lock.h"
#include "include/cef_client.h"

class ClientHandler : public CefClient,
    public CefDisplayHandler,
    public CefLifeSpanHandler,
    public CefLoadHandler
{
public:
    // Implement this interface to receive notification of ClientHandler
    // events. The methods of this class will be called on the main thread.
    class Delegate
    {
    public:
        // Called when the browser is created.
        virtual void OnBrowserCreated(CefRefPtr browser) = 0;

        // Called when the browser is closing.
        virtual void OnBrowserClosing(CefRefPtr browser) = 0;

        // Called when the browser has been closed.
        virtual void OnBrowserClosed(CefRefPtr browser) = 0;

        // Set the window URL address.
        virtual void OnSetAddress(std::string const & url) = 0;

        // Set the window title.
        virtual void OnSetTitle(std::string const & title) = 0;

        // Set fullscreen mode.
        virtual void OnSetFullscreen(bool const fullscreen) = 0;

        // Set the loading state.
        virtual void OnSetLoadingState(bool const isLoading,
            bool const canGoBack,
            bool const canGoForward) = 0;

    protected:
        virtual ~Delegate() {}
    };

public:
    ClientHandler(Delegate* delegate);
    ~ClientHandler();

    void CreateBrowser(CefWindowInfo const & info, CefBrowserSettings const & settings, CefString const & url);

    // CefClient methods:
    virtual CefRefPtr GetDisplayHandler() override { return this; }
    virtual CefRefPtr GetLifeSpanHandler() override { return this; }
    virtual CefRefPtr GetLoadHandler() override { return this; }

    // CefDisplayHandler methods:
    virtual void OnAddressChange(CefRefPtr browser, CefRefPtr frame, const CefString& url) override;
    virtual void OnTitleChange(CefRefPtr browser, const CefString& title) override;
    virtual void OnFullscreenModeChange(CefRefPtr browser, bool fullscreen);

    // CefLifeSpanHandler methods:
    virtual void OnAfterCreated(CefRefPtr browser) override;
    virtual bool DoClose(CefRefPtr browser) override;
    virtual void OnBeforeClose(CefRefPtr browser) override;

    // CefLoadHandler methods:
    virtual void OnLoadingStateChange(CefRefPtr browser,
        bool isLoading,
        bool canGoBack,
        bool canGoForward) override;

    virtual void OnLoadError(CefRefPtr browser,
        CefRefPtr frame,
        ErrorCode errorCode,
        const CefString& errorText,
        const CefString& failedUrl) override;

    // This object may outlive the Delegate object so it's necessary for the
    // Delegate to detach itself before destruction.
    void DetachDelegate();

private:

    // Include the default reference counting implementation.
    IMPLEMENT_REFCOUNTING(ClientHandler);
    // Include the default locking implementation.
    IMPLEMENT_LOCKING(ClientHandler);

private:
    Delegate* m_delegate;
};

#include "stdafx.h"
#include "ClientHandler.h"


#include 
#include 

#include "include/base/cef_bind.h"
#include "include/cef_app.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"

ClientHandler::ClientHandler(Delegate* delegate)
    : m_delegate(delegate)
{
}

ClientHandler::~ClientHandler()
{
}

void ClientHandler::CreateBrowser(CefWindowInfo const & info, CefBrowserSettings const & settings, CefString const & url)
{
    CefBrowserHost::CreateBrowser(info, this, url, settings, nullptr);
}

void ClientHandler::OnAddressChange(CefRefPtr browser, CefRefPtr frame, const CefString& url)
{
    CEF_REQUIRE_UI_THREAD();

    // Only update the address for the main (top-level) frame.
    if (frame->IsMain())
    {
        if (m_delegate != nullptr)
            m_delegate->OnSetAddress(url);
    }
}

void ClientHandler::OnTitleChange(CefRefPtr browser, const CefString& title)
{
    CEF_REQUIRE_UI_THREAD();

    if (m_delegate != nullptr)
        m_delegate->OnSetTitle(title);
}

void ClientHandler::OnFullscreenModeChange(CefRefPtr browser, bool fullscreen)
{
    CEF_REQUIRE_UI_THREAD();

    if (m_delegate != nullptr)
        m_delegate->OnSetFullscreen(fullscreen);
}

void ClientHandler::OnAfterCreated(CefRefPtr browser)
{
    CEF_REQUIRE_UI_THREAD();

    if (m_delegate != nullptr)
        m_delegate->OnBrowserCreated(browser);
}

bool ClientHandler::DoClose(CefRefPtr browser)
{
    CEF_REQUIRE_UI_THREAD();

    if (m_delegate != nullptr)
        m_delegate->OnBrowserClosing(browser);

    return false;
}

void ClientHandler::OnBeforeClose(CefRefPtr browser)
{
    CEF_REQUIRE_UI_THREAD();

    if (m_delegate != nullptr)
        m_delegate->OnBrowserClosed(browser);
}

void ClientHandler::OnLoadError(CefRefPtr browser,
    CefRefPtr frame,
    ErrorCode errorCode,
    const CefString& errorText,
    const CefString& failedUrl)
{
    CEF_REQUIRE_UI_THREAD();

    // Don't display an error for downloaded files.
    if (errorCode == ERR_ABORTED)
        return;

    // Display a load error message.
    std::stringstream ss;
    ss << ""
        "

Failed to load URL " << std::string(failedUrl) << " with error " << std::string(errorText) << " (" << errorCode << ").

"; frame->LoadString(ss.str(), failedUrl); } void ClientHandler::OnLoadingStateChange(CefRefPtr browser, bool isLoading, bool canGoBack, bool canGoForward) { CEF_REQUIRE_UI_THREAD(); if (m_delegate != nullptr) m_delegate->OnSetLoadingState(isLoading, canGoBack, canGoForward); } void ClientHandler::DetachDelegate() { m_delegate = nullptr; }

改造MFC类

  • CCEFDemoDoc 不需改动
  • CMFCCEFView
    • 添加头文件CClientHandler.hMFCCEFDoc.h
    • 添加继承public ClientHandler::Delegate,并添加其虚函数
    virtual void OnBrowserCreated(CefRefPtr browser) override;

    // Called when the browser is closing.
    virtual void OnBrowserClosing(CefRefPtr browser) override;

    // Called when the browser has been closed.
    virtual void OnBrowserClosed(CefRefPtr browser) override;

    // Set the window URL address.
    virtual void OnSetAddress(std::string const & url) override;

    // Set the window title.
    virtual void OnSetTitle(std::string const & title) override;

    // Set fullscreen mode.
    virtual void OnSetFullscreen(bool const fullscreen) override;

    // Set the loading state.
    virtual void OnSetLoadingState(bool const isLoading,
        bool const canGoBack,
        bool const canGoForward) override;
void CCEFDemoView::OnBrowserCreated(CefRefPtr browser)
{
    m_browser = browser;
}

void CCEFDemoView::OnBrowserClosing(CefRefPtr browser)
{
}

void CCEFDemoView::OnBrowserClosed(CefRefPtr browser)
{
    if (m_browser != nullptr &&
        m_browser->GetIdentifier() == browser->GetIdentifier())
    {
        m_browser = nullptr;
        m_clientHandler->DetachDelegate();
    }
}

void CCEFDemoView::OnSetAddress(std::string const & url)
{
    auto main = static_cast(m_wndMain);
    if (main != nullptr)
    {
        auto newurl = CString{ url.c_str() };
        if (newurl.Find(m_startUrl) >= 0)
            newurl = "";

        //main->SetUrl(newurl);
    }
}

void CCEFDemoView::OnSetTitle(std::string const & title)
{
    ::SetWindowText(m_hWnd, CefString(title).ToWString().c_str());
}

void CCEFDemoView::OnSetFullscreen(bool const fullscreen)
{
}

void CCEFDemoView::OnSetLoadingState(bool const isLoading,
    bool const canGoBack,
    bool const canGoForward)
{
}

void CCEFDemoView::OnInitialUpdate()
{
    CView::OnInitialUpdate();

    InitStartUrl();

    auto rect = RECT{ 0 };
    GetClientRect(&rect);

    CefWindowInfo info;
    info.SetAsChild(GetSafeHwnd(), rect);

    CefBrowserSettings browserSettings;
    browserSettings.web_security = STATE_DISABLED;

    m_clientHandler = new ClientHandler(this);
    m_clientHandler->CreateBrowser(info, browserSettings, CefString(m_startUrl));
}

void CCEFDemoView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);

    if (m_clientHandler != nullptr)
    {
        if (m_browser != nullptr)
        {
            auto hwnd = m_browser->GetHost()->GetWindowHandle();
            auto rect = RECT{ 0 };
            GetClientRect(&rect);

            ::SetWindowPos(hwnd, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
        }
    }
}


void CCEFDemoView::InitStartUrl()
{
    TCHAR path_buffer[_MAX_PATH] = { 0 };
    TCHAR drive[_MAX_DRIVE] = { 0 };
    TCHAR dir[_MAX_DIR] = { 0 };
    TCHAR fname[_MAX_FNAME] = { 0 };
    TCHAR ext[_MAX_EXT] = { 0 };

    ::GetModuleFileName(NULL, path_buffer, sizeof(path_buffer));
    auto err = _tsplitpath_s(path_buffer, drive, _MAX_DRIVE, dir, _MAX_DIR, fname, _MAX_FNAME, ext, _MAX_EXT);
    if (err != 0) {}

    auto s = CString{ dir };
    s += _T("html");
    err = _tmakepath_s(path_buffer, _MAX_PATH, drive, (LPCTSTR)s, _T("index"), _T("html"));
    if (err != 0) {}

    m_startUrl = CString{ path_buffer };
    m_startUrl.Replace(_T('\\'), _T('/'));
    m_startUrl = CString{ _T("file:///") } +m_startUrl;
}
  • 重写CView的OnActivateViewPreTranslateMessage函数
void CCEFDemoView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
{
    m_wndMain = AfxGetMainWnd();

    return CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
}
//截获键盘F5消息,刷新页面
BOOL CCEFDemoView::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->message == WM_KEYDOWN)
    {
        if (pMsg->wParam == VK_F5)
        {
            m_browser->Reload();
        }
    }
    return CView::PreTranslateMessage(pMsg);
}
  • 添加OnInitialUpdateOnSize函数
  • 添加私有函数InitStartUrl和如下变量
    CefRefPtr m_clientHandler;
    CefRefPtr m_browser;
    CWnd* m_wndMain = nullptr;
    CString m_startUrl;
  • CCEFDemoView.cpp中添加消息映射ON_WM_SIZE()
  • 析构函数如下:
    if (m_clientHandler != nullptr)
        m_clientHandler->DetachDelegate();

修改CCEFDemoApp

  • 修改头文件
    • 添加引用include/cef_base.hinclude/cef_app.h
    • 重写函数ExitInstance()PumpMessage()
int CCEFDemoApp::ExitInstance()
{
    AfxOleTerm(FALSE);
    UninitializeCef();
    return CWinApp::ExitInstance();
}

BOOL CCEFDemoApp::PumpMessage()
{
    auto result = CWinApp::PumpMessage();
    CefDoMessageLoopWork();
    return result;
}
  • 添加私有函数和变量:InitializeCef()UninitializeCef(),和CefRefPtr m_app
void CCEFDemoApp::InitializeCef()
{
    CefMainArgs mainargs(m_hInstance);
    CefSettings settings;
    settings.multi_threaded_message_loop = false;
    CefInitialize(mainargs, settings, m_app, nullptr);
}
void CCEFDemoApp::UninitializeCef()
{
    CefShutdown();
}
  • 修改源文件
    • 添加头文件引用CEFDemoView.h
    • InitInstanceCWinApp::InitInstance();之前添加InitializeCef();初始化CEF
    • 添加公有函数void SetUrl(CString const & url)
    • 添加消息映射afx_msg void OnClose()
    • 添加私有成员CCEFDemoView* GetView()

复制资源文件

cef_binary_3.2171.1901_windows32\Resources目录下的文件,拷贝到程序的Debug目录

一步一步将CEF3嵌入到MFC视图中_第5张图片
Paste_Image.png

运行生成的程序,可得如下界面,说明成功。

一步一步将CEF3嵌入到MFC视图中_第6张图片
Paste_Image.png

你可能感兴趣的:(一步一步将CEF3嵌入到MFC视图中)