Click here to Skip to main content
13,551,173 members
Click here to Skip to main content
Add your own
alternative version

Stats

31.3K views
497 downloads
64 bookmarked
Posted 10 Jun 2016
Licenced CPOL

Embedding a Chromium browser in an MFC application

, 10 Jun 2016
Rate this:
Please Sign up or sign in to vote.
Chromium Embedded Framework (CEF) v3 to embed a Chromium browser in an MFC application

Chromium Embedded Framework (or CEF) is an open source project that enables developers to include a Chromium browser in 3rd party applications. The base CEF provides APIs for C and C++ applications, but external projects (not implemented by CEF) support other languages such as C#, Java, Delphi or Python. This article shows how to embed a Chromium browser in an MFC single document interface application.

Prerequisites

CEF is available both as a binary distribution and as source code. Building from sources can be done locally or with an automated build system but requires building Chromium too which is a bit more complicated.

The binary distributions of CEF 3 are available at https://cefbuilds.com/ and include the following:

  • Debug and Release builds of CEF shared library (libcef) and its dependencies
  • C++ wrapper static library (libcef_dll_wrapper) necessary for C++ applications using CEF
  • Two sample applications (cefsimple and cefclient)
  • Resources for applications using CEF
  • Debug and Release symbols for CEF (as separate downloads)

For the purpose of this article we will be using the binary distribution of CEF.

The prerequisites for this article therefore are:

  • Latest CEF 64-bit build from the development branch (trunk)
  • Visual Studio 2013 (because CEF is compiled with this version)

For information on how to build CEF 64-bit see my article Building the 64-bit version of Chromium Embedded Framework on Windows.

For simplicity, the MFC application we will be creating in this article should be located in the main CEF folder with the same output location as the CEF sample applications.

Anatomy of the application

An MFC SDI application has several components:

  • A CWinApp derived class representing the instance of the application; this offers an entry point for initializing the application and one for clean up.
  • A CFrameWnd derived class representing the frame of a view; in a single document interface application there is a single view and therefore a single frame and this frame is called “main frame”.
  • A CDocument derived class representing the document.
  • A CView derived class representing the view that displays the data from the document.

A CEF-base application on the other hand has the following components:

  • An entry point to initialize CEF and run the CEF message loop.
  • A CefApp derived class to handle process-specific callbacks.
  • A CefClient derived class to handle browser-instance-specific callbacks (this can include callbacks for browser lifespan, context menus, dialogs, display notifications, drag events, focus events, keyboard events, etc.). A single instance of CefClient can be shared among any number of browsers.
  • One or more CefBrowser instances created with CefBrowserHost::CreateBrowser().

To embed a Chromium browser in an MFC application we need to:

  • Initialize CEF when the application starts; this should be done in CWinApp::InitInstance() overridden method
  • Uninitialize CEF when the application exists; this should be done in CWinApp::ExitInstance() overridden method
  • Implement CefClient to handle browser-instance-specific callbacks; an instance of the CefClient is stored in the CView derived class. When the view is created we must also create a CefBrowser as a child of the view and display it on top of the view and alway keep it sized at the same size as the view.

Static versus DLL run-time library

The libcef_dll_wrapper and the MFC application needs to be built with the same options for the runtime library, that is either multi-threaded static version (/MT or /MTd) or multi-threaded DLL (/MD or /MDd).

By default the libcef_dll_wrapper is set to be built with the static version. That means the MFC application needs the same setting as well as Use MFC in a Static Library in the General configuration settings. If you can't or don't want to build your MFC application with those options then you must change the runtime library settings for libcef_dll_wrapper from /MT to /MD.

The sample code that is available with this article is built with /MD.

The sample application

The demo application provided with this article is a simple browser. It has an address bar where you can type an URL (it also updates the current URL as you browse through pages) and a few buttons, one to navigate to the selected URL, one to navigate backwards and one to navigate forward. And of course the browser window where a web page is rendered.

At startup, the browser will display the content of the HTML file from disk, a sort of intro page.

The user can then type an URL in the address bar and navigate to that page using the Go button on the toolbar.

Throughout the rest of the article we will see step by step how to implement this application. Some details will be left out though to the reader to find in the source code.

Creating the MFC application

To get started with it we must create the MFC application first. This should be a single document interface (aka SDI) application. I called my application cefmfcdemo. The wizard generates the classes mentioned above and for simplicity I changed the names of the classes to the following:

  • CefView for the view class
  • CefDoc for the document class
  • CefMfcDemoApp for the application class

As mentioned before the application should be created in the same folder with the CEF library. The only reason for that is of course simplicity of the demo because we can easily setup the project with similar dependencies and output like the other sample applications that come with CEF.

The settings that you have to do:

  • Create a 64-bit project target copying settings from the 32-bit one
  • Make sure $(SolutionDir)$(Configuration)\ is the output for all platforms and configurations (that means the Debug and Release folders in the main CEF folder)

  • Change VC++ directories and add ..\ to the include directories and $(SolutionDir)$(Configuration)\ to the Library directories

  • Add libcef.lib and libcef_dll_wrapper.lib to the Additional Dependencies of the Linker

  • Copy the content of the Resources folder to Debug and Release before you run the application since this contains resources needed by the CEF framework

The ClientHandler class

This class provides handler implementations for browser specific callbacks such as lifespan, context menu, dialogs, drag events, keyboard events, and others. The implementation for this demo application is a simplified version of what is available in the cefsimple and especially cefclient sample applications from CEF. Given the architecture of a SDI application, there will be a single browser instance which simplifies the implementation of the client handler. In practice a CefClient instance can be shared between many browsers.

The ClientHandler header looks like this:

#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) override;

  // 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;
};

The ClientHandler is derived from several classes:

  • CefClient: the interface for handler implementations.
  • CefDisplayHandler: the interface for handling events related to browser display state; methods on this class are called on the UI thread.
  • CefLifeSpanHandler: the interface for handling events related to browser lifespan; methods on this class are called on the UI thread unless otherwise specified.
  • CefLoadHandler: the interface for handling events related to browser load status; the methods on this class are called on the browser process UI thread or render process main thread.

Since the ClientHandler class implements the three mentioned handler interfaces it overrides the following virtual methods (with a trivial implementation).

virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() override { return this; }
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }

The inner Delegate class represents an interface to receive notifications of various events, such as browser creation and destruction, change of URL, etc. This will be implemented by the view where the browser is created.

To create the browser the ClientHandler provides a method called CreateBrowser(). This actually simply calls CefBrowserHost::CreateBrowser with appropriate parameters.

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

The implementation of the handler interface methods are in turn calling methods from the Delegate interface, giving a chance to the view (in this case) to do something as a result to the occurrence of a particular event.

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);
}

The instance of a delegate is set in the constructor and reset in DetachDelegate().

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

void ClientHandler::DetachDelegate()
{
   m_delegate = nullptr;
}

The CefView class

This is the implementation of CView, but also of ClientHandler::Delegate interface.

The general idea behind the implementation is: when the view is created also create a browser and display it on top of the window; whenever the size of the view window changes also change the size of the browser window to cover the entire client area.

To complicate the thing a bit the browser will first display a start-up page, loaded from disk, that contains some general information. The user can then navigate to any URL by typing it in the application's address bar.

In the OnInitialUpdate() method we do the following:

  • Create the URL of the start-up page that will be loaded from disk
  • Create an instance of CefWindowInfo that represents the window information for the browser that will be created and set the view as the parent for the browser and the client area of the view as the window rectangle for the browser.
  • Create an instance of CefBrowserSettings that represents settings for the browser initialization and set the web_security to STATE_DISABLED (this means disabling the web security restrictions, i.e. same-origin policy).
  • Create an instance of ClientHandler specifying the view instance as the Delegate and create the browser specifying the window information, browser settings and initial URL.
void CefView::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 CefView::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;
}

The implementation of the ClientHandler::Delegate interface is relatively simple and should not require much explanation. Note that in OnSetAddress we verify whether the new URL matches the start-up URL and in that case we set no text in the application's address bar.

void CefView::OnBrowserCreated(CefRefPtr<CefBrowser> browser)
{
   m_browser = browser;
}

void CefView::OnBrowserClosing(CefRefPtr<CefBrowser> browser)
{
}

void CefView::OnBrowserClosed(CefRefPtr<CefBrowser> browser)
{
   if(m_browser != nullptr && 
      m_browser->GetIdentifier() == browser->GetIdentifier())
   {
      m_browser = nullptr;

      m_clientHandler->DetachDelegate();
   }
}

void CefView::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 CefView::OnSetTitle(std::string const & title)
{
   ::SetWindowText(m_hWnd, CefString(title).ToWString().c_str());
}

void CefView::OnSetFullscreen(bool const fullscreen)
{
   if(m_browser != nullptr)
   {
      if(fullscreen)
      {
         CefWindowsHelpers::Maximize(m_browser);
      }
      else 
      {
         CefWindowsHelpers::Restore(m_browser);
      }
   }
}

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

An important thing that we have to do in the view is resizing the browser window every time the view changes size. Since the browser window is completely overlapping the view's client area their size must be the same all the time. So in the view we must handle the WM_SIZE message and resize the browser.

void CefView::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);
      }
   }   
}

Another feature that the application supports is refreshing the current page when pressing F5. Since the browser overlaps the client area of the view, handling WM_KEYDOWN message to do something when F5 is pressed we have to actively looking at the mesages received by the parent before they are dispatched further. This is done by overriding PreTranslateMessage() in the view.

BOOL CefView::PreTranslateMessage(MSG* pMsg)
{
   if(pMsg->message == WM_KEYDOWN)
   {
      if(pMsg->wParam == VK_F5)
      {
         m_browser->Reload();
      }
   }

   return CView::PreTranslateMessage(pMsg);  
}

Setting up the application

The only important thing left is initializing and uninitializing the application. This has to be done in InitInstance() and ExitInstance() of CefMfcdDemoApp (the CWinApp derived class). In InitInstance() we will call InitializeCef() and in ExitInstance() we will call UninitializeCef().

What these do:

  • The initialization function is calling CefInitialize to initialize the CEF browser process, passing several parameters: application arguments, application settings, and a CefApp object. In the application settings we set multi_threaded_message_loop to false which means we must call CefDoMessageLoopWork() from our application message look.
  • The uninitialize function is simply calling CefShutdown() to shut down the CEF browser process before the application exits.
void CefMfcdDemoApp::InitializeCef()
{
   CefMainArgs mainargs(m_hInstance);

   CefSettings settings;
   settings.multi_threaded_message_loop = false;
   
   CefInitialize(mainargs, settings, m_app, nullptr);
}

void CefMfcdDemoApp::UninitializeCef()
{
   CefShutdown();
}

BOOL CefMfcdDemoApp::InitInstance()
{
   // various initialization

   InitializeCef();

   CWinApp::InitInstance();

   // more initialization

   return TRUE;
}

int CefMfcdDemoApp::ExitInstance()
{
	AfxOleTerm(FALSE);

  UninitializeCef();

	return CWinApp::ExitInstance();
}

Since we specified that the browser process should not run a message loop in a separate thread (by setting multi_threaded_message_loop to false) we need to call CefDoMessageLoopWork() from the main thread's message loop. We do that by overriding CWinApp::PumpMessage() as show bellow.

BOOL CefMfcdDemoApp::PumpMessage()
{
   auto result = CWinApp::PumpMessage();

   CefDoMessageLoopWork();
   
   return result;
}

Putting it all together

Having all this in place the application can be built and run. The result should look like in the screenshot presented ealier. Notice that the sample application contains additional functionality than what has been described so far. However, those details are not related to the embeding of a Chromium browser an in MFC application. You can check the attached sources for those details.

This article presented the minimum necessary steps for embedding a Chromium browser in an MFC application. For details about the framework including documentation on using it in native applications check the project's web page.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Marius Bancila
Architect Visma Software
Romania Romania
Marius Bancila is the author of Modern C++ Programming Cookbook. He has been a Microsoft MVP for VC++ and later Visual Studio and Development Technologies for 11 years. He works as a system architect for Visma, a Norwegian-based company. He is mainly focused on building desktop applications with VC++ and VC#. He keeps a blog at http://www.mariusbancila.ro/blog, focused on Windows programming. He is the co-founder of codexpert.ro, a community for Romanian C++ programmers.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionSample project? Pin
Albert van Peppen21-Mar-18 4:13
memberAlbert van Peppen21-Mar-18 4:13 
SuggestionMaking it work with the latest version of CEF Pin
jason_s_coleman8-Feb-18 12:33
memberjason_s_coleman8-Feb-18 12:33 
GeneralRe: Making it work with the latest version of CEF Pin
Member 137568302-Apr-18 3:32
memberMember 137568302-Apr-18 3:32 
GeneralRe: Making it work with the latest version of CEF Pin
jason_s_coleman3-Apr-18 10:49
memberjason_s_coleman3-Apr-18 10:49 
QuestionMessage Removed Pin
15-Nov-17 18:15
membercazorla1915-Nov-17 18:15 
Questionnew learner Pin
Member 1327762119-Sep-17 3:08
memberMember 1327762119-Sep-17 3:08 
QuestionNew version of CEF no longer works Pin
yinlosky8-Sep-17 7:34
memberyinlosky8-Sep-17 7:34 
AnswerRe: New version of CEF no longer works Pin
hwang asoo20-Sep-17 4:57
memberhwang asoo20-Sep-17 4:57 
QuestionSource code Pin
yinlosky1-Aug-17 5:16
memberyinlosky1-Aug-17 5:16 
AnswerRe: Source code Pin
Marius Bancila1-Aug-17 10:27
professionalMarius Bancila1-Aug-17 10:27 
GeneralRe: Source code Pin
yinlosky5-Sep-17 8:30
memberyinlosky5-Sep-17 8:30 
QuestionLoadURL crashes Pin
MARONGUI20-Apr-17 0:48
memberMARONGUI20-Apr-17 0:48 
GeneralMy vote of 5 Pin
KarstenK10-Jan-17 23:03
memberKarstenK10-Jan-17 23:03 
Bugstill crash at cefshutdown! Pin
cqyczw123-Jan-17 16:45
membercqyczw123-Jan-17 16:45 
GeneralRe: still crash at cefshutdown! Pin
awfan.cn5-Mar-17 17:56
memberawfan.cn5-Mar-17 17:56 
GeneralMy vote of 5 Pin
mayncoco9-Nov-16 15:02
membermayncoco9-Nov-16 15:02 
Questionmissing ceflib_dll_wrapper.lib ? Pin
rbrunton8-Sep-16 1:48
memberrbrunton8-Sep-16 1:48 
QuestionRe: missing ceflib_dll_wrapper.lib ? Pin
rp_suman1-Dec-16 20:11
memberrp_suman1-Dec-16 20:11 
AnswerRe: missing ceflib_dll_wrapper.lib ? Pin
LiquidPenguin15-Mar-17 3:35
memberLiquidPenguin15-Mar-17 3:35 
GeneralRe: missing ceflib_dll_wrapper.lib ? Pin
PIEBALDconsult15-Mar-17 3:36
protectorPIEBALDconsult15-Mar-17 3:36 
QuestionA very good article Pin
Douglas Hill26-Jun-16 15:42
memberDouglas Hill26-Jun-16 15:42 
QuestionGood article Pin
Flaviu211-Jun-16 20:01
memberFlaviu211-Jun-16 20:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02-2016 | 2.8.180515.1 | Last Updated 10 Jun 2016
Article Copyright 2016 by Marius Bancila
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid