一、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。
Paste_Image.png解压到任意目录,用VS2017打开cefclient2010.sln
,升级到VS2017格式。编译其中的libcef_dll_wrapper
项目,会出现以下错误。
产生原因为:SDK中的solution文件是用VS2010产生的,我的VS版本为VS2017,转换后,有些Project编译选项中,Treat Warnings As Errors(把警告看作错误来处理)选项开启了。按照下图,把此选项关闭,就可以正常编译了。
Paste_Image.png
编译成功后,在\out\Debug\lib
这个目录里会生成一个文件:libcef_dll_wrapper.lib
,这个文件在开发我们项目的时候会使用。
三、新建MFC项目
新建一个MFC SDI项目
设置为MFC标准,在静态库中使用MFC。
新建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<CefBrowser> browser) = 0;
// Called when the browser is closing.
virtual void OnBrowserClosing(CefRefPtr<CefBrowser> browser) = 0;
// Called when the browser has been closed.
virtual void OnBrowserClosed(CefRefPtr<CefBrowser> 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<CefDisplayHandler> GetDisplayHandler() override { return this; }
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }
// CefDisplayHandler methods:
virtual void OnAddressChange(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const CefString& url) override;
virtual void OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title) override;
virtual void OnFullscreenModeChange(CefRefPtr<CefBrowser> browser, bool fullscreen);
// CefLifeSpanHandler methods:
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) override;
virtual bool DoClose(CefRefPtr<CefBrowser> browser) override;
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) override;
// CefLoadHandler methods:
virtual void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
bool isLoading,
bool canGoBack,
bool canGoForward) override;
virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> 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 <sstream>
#include <string>
#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<CefBrowser> browser, CefRefPtr<CefFrame> 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<CefBrowser> browser, const CefString& title)
{
CEF_REQUIRE_UI_THREAD();
if (m_delegate != nullptr)
m_delegate->OnSetTitle(title);
}
void ClientHandler::OnFullscreenModeChange(CefRefPtr<CefBrowser> browser, bool fullscreen)
{
CEF_REQUIRE_UI_THREAD();
if (m_delegate != nullptr)
m_delegate->OnSetFullscreen(fullscreen);
}
void ClientHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
if (m_delegate != nullptr)
m_delegate->OnBrowserCreated(browser);
}
bool ClientHandler::DoClose(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
if (m_delegate != nullptr)
m_delegate->OnBrowserClosing(browser);
return false;
}
void ClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
if (m_delegate != nullptr)
m_delegate->OnBrowserClosed(browser);
}
void ClientHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> 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 << "<html><body bgcolor=\"white\">"
"<h2>Failed to load URL " << std::string(failedUrl) <<
" with error " << std::string(errorText) << " (" << errorCode <<
").</h2></body></html>";
frame->LoadString(ss.str(), failedUrl);
}
void ClientHandler::OnLoadingStateChange(CefRefPtr<CefBrowser> 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.h
和MFCCEFDoc.h
- 添加继承
public ClientHandler::Delegate
,并添加其虚函数
- 添加头文件
virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser) override;
// Called when the browser is closing.
virtual void OnBrowserClosing(CefRefPtr<CefBrowser> browser) override;
// Called when the browser has been closed.
virtual void OnBrowserClosed(CefRefPtr<CefBrowser> 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<CefBrowser> browser)
{
m_browser = browser;
}
void CCEFDemoView::OnBrowserClosing(CefRefPtr<CefBrowser> browser)
{
}
void CCEFDemoView::OnBrowserClosed(CefRefPtr<CefBrowser> 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<CMainFrame*>(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的
OnActivateView
和PreTranslateMessage
函数
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);
}
- 添加
OnInitialUpdate
和OnSize
函数 - 添加私有函数
InitStartUrl
和如下变量
CefRefPtr<ClientHandler> m_clientHandler;
CefRefPtr<CefBrowser> 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.h
和include/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<CefApp> 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
- 在
InitInstance
的CWinApp::InitInstance();
之前添加InitializeCef();
初始化CEF - 添加公有函数
void SetUrl(CString const & url)
- 添加消息映射
afx_msg void OnClose()
- 添加私有成员
CCEFDemoView* GetView()
- 添加头文件引用
复制资源文件
将cef_binary_3.2171.1901_windows32\Resources
目录下的文件,拷贝到程序的Debug目录
运行生成的程序,可得如下界面,说明成功。
Paste_Image.png
网友评论