Thursday, August 7, 2014

How to use Webkit CEF in an MFC Project

It took sometime before i was able to figure this out myself.

The CEF download contains a sample on how it’s done in a Win32 Project, in Visual Studio. Unfortunately, that sample contains quite a lot of things that makes it difficult to grasp especially for those who have completely no prior knowledge of using webkit (which was my case).

Okay some might ask why use CEF in an MFC app in the first place. Well, i needed it for a project i was assigned at work. 


image

There are very little sources out there about using CEF in an MFC project. Actually, i've found just a single blog post regarding this – the rest were bits and pieces of info and forum replies. Well, i guess not many people are using CEF+MFC but it is the combination that was perfect to my current needs.

Note: MFC does not natively have HDPI support. On the other hand, i think, a Win32 Project has some bit of processing regarding this. I’m using Visual Studio 2010, btw.

Also, I’m making this post a bit of a spoon-fed tutorial. Just follow along the steps if you’ve never used webkit/CEF before. Or if you already have an idea on how to do it, just skip the steps here and download the sample project which you can probably use as a template or your reference project.


What you need:

1. Visual Studio
- i’m using 2010 but i guess you may use 2003 and up.
- be warned that as of this writing, based on the last time i tried it, VS2012 still has a couple of issues with MFC.

2. CEF 3 binaries
- download them here:
http://www.magpcss.net/cef_downloads/
- download whichever version you like but it’s always better to stick with the latest stable version

3. Patience and obedience (haha)


Well, let’s dive in:

A. Setting Up the Project --> This is seriously a beginner's level of instructions (i'm really spoon-feeding here)
1. Download the latest CEF3 binaries.

2. Create an MFC Project in Visual Studio (for my case VS2010)
    - select "Dialog based"
    - select "Use MFC in a static library"   <--- well, this is up to you
    - remove the about box if you don't want it

3. Once VS generates the application folder, create a "CEF3" folder in your project folder.

4. Copy the following folder from the CEF3 binaries to your "CEF3" folder:
    cefclient
    include
    lib
    libcef_dll
    - This would make it a lot simpler for us when it's time to use the header files from CEF. This would also organize our CEF-specific source files.

5. To simplify this tutorial/project, we create an "Output" folder where we generate the project executable, and where we put in the CEF libraries. So, create a new folder called "Output" inside the Project folder.

6. From the CEF3 binaries, copy all the dll files and the "locales" folder under "Release" (or use the debug if you want), to your "Output" folder.

7. Going back to VS, open up the Project Properties and select "All Configurations".

8. Change the following properties:
   (page name : property name : value)
   General : Output Directory : Output\
   Debugging : Command : $(TargetPath)
   Debugging : Working Directory : $(TargetDir)
   C/C++/General : Additional Include Directories : $(ProjectDir)CEF3\;
   Linker/General : Additional Library Directories : $(ProjectDir)CEF3\lib;
   - Don't forget to click apply after editing the properties. Actually, the last 2 items above are the 3 most important settings, where we specify the location of the original CEF files that we will be using in some of our classes. For the sake of simplicity just follow the settings. You can always modify them based on your setup.

9. Still in the Configuration Settings, change the Configuration to Debug and Release, following these settings:
    for Debug --> Linker/Input : Additional Dependencies : libcef.lib;libcef_dll_wrapper_d.lib;
    for Release --> Linker/Input : Additional Dependencies : libcef.lib;libcef_dll_wrapper.lib;
    - We'd be using the "_d.lib" version for debug mode.

10. Save the configuration settings and try to build.
Everything should be working fine here and an exe should be generated in the Output folder.


B. Using CEF in Dialog
1. Create a ClientHandler class that inherits from CefClient and CefLifeSpanHandler.
The sample class is available at the "ClientHandler.h" and "ClientHandler.cpp" files.
If you're being lazy, just go ahead and include that file in your project.

The important things about this class are:
- it must inherit from CefClient and CefLifeSpanHandler, which are required when we setup the webkit control in our dialog.
- it must implement GetLifeSpanHandler(), which tells the webkit that we have an instance of the LifeSpanHandler - for our case, it's the same class.
- it implements DoClose(), OnAfterCreated() and OnBeforeClose() functions.

2. Edit the MFC dialog and add a picture control (static control) and size it accordingly.

3. In your MFC dialog class, add a ClientHandler (pointer) member:
    ClientHandler* m_Handler;
- just for posterity, we will always keep track of this object

4. In your OnInitDialog(), get the rectangle of the Picture control:
    RECT rect;
    GetDlgItem(IDC_STATIC)->GetClientRect(&rect);
- we will use this rectangle when creating the webkit browser inside the dialog

5. In your OnInitDialog, create all the necessary objects to initialize CEF.
This would include: args, app settings, browser settings and application objects:
    CefMainArgs mainArgs(theApp.m_hInstance);
    CefSettings appSettings;
    CefBrowserSettings browserSettings;
    CefRefPtr<CefApp> cefApplication;
    CefWindowInfo info;
    appSettings.multi_threaded_message_loop = true;
    info.SetAsChild(GetDlgItem(IDC_STATIC)->GetSafeHwnd(), rect);
    CefRefPtr<CefClient> client(new ClientHandler());
    m_Handler = (ClientHandler*) client.get();
- we also assign the ClientHandler object to our class member.
- the "multi_threaded_message_loop" makes use of the default MFC message-loop implementation. otherwise, we would need to implement a custom handling for CEF.

6. Initialize CEF using the objects we just created
    bool bSucceeded = CefInitialize(mainArgs, appSettings, cefApplication);

7. Create the browser in our dialog:
    if (bSucceeded)
    {
        CefBrowserHost::CreateBrowser(info, m_Handler, _T("http://www.google.com/"), browserSettings);
    }

8. Add the DestroyWindow() function for the dialog.
This is the normal DestroyWindow() function that listens to the window destroy event.

9. In the DestroyWindow() function, shutdown CEF:
    CefShutdown();

10. Build the project and run the application.
   Viola! You now have an MFC dialog app with a webkit CEF browser in it.



You can use the sample project i provided as a reference or use it as a base project. The files are here: sites.google.com/site/mickeymicks/mickeymickstech/Cef3.Samples.Mfc.Dialog.SrcOnly.zip 

You might also want to adjust the size of the browser, by listening to the dialog's resize event and then using Win APIs to adjust the browser through its handle
    m_Handler->GetBrowser()->GetHost()->GetWindowHandle();

To do further customization and additional processing, checkout and implement the other "..Handler" classes. This is the same way as what we did with "CefLifeSpanHandler".
And, don't forget to add the "Get..Handler()" function for every inheritance you add, otherwise it won't ever get called.

This is only meant to help those who would like to use CEF in an MFC app. Please refer to the CEF3 samples for other functionalities. The client sample there uses a Win32 project and for me contains A LOT of functionalities that makes it difficult to refer to, particularly for first time developers of CEF. 

Well, happy programming!

10 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Great!!!!!!! It helps me. Thanks a bunch!

    ReplyDelete
  3. Thanks for sharing!

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. hi good work..but the app crashes when it is closed and again the main window opens..why is that?

    ReplyDelete
  6. Thanks for sharing!
    But when I run project, have error: "the program can't start because libcef.dll is missing from your computer"
    How to resolve? please help me. thanks you!

    ReplyDelete
    Replies
    1. You need copy the libcef.dll and other other dlls in the Release folder of the compiled Cef visual studio solution to the exe location. You also need to xcopy the Resources folder of the Cef binary to the exe location as well.

      Delete
  7. When I try to use the handle to resize the browser then I get flickering. Any suggestions?

    class ChromeBrowser : public CStatic
    {
    .....
    }

    void ChromeBrowser::OnSize(UINT nType, int cx, int cy)
    {
    CStatic::OnSize(nType, cx, cy);

    if (getHandler())
    {
    if (getHandler()->GetBrowser())
    {
    CefWindowHandle hwnd = getHandler()->GetBrowser()->GetHost()->GetWindowHandle();
    if (hwnd)
    {
    CRect rect;
    GetClientRect(rect);

    //RECT rect;
    //GetDlgItem(IDC_STATIC_CHROME)->GetClientRect(&rect);

    HDWP hdwp = BeginDeferWindowPos(1);
    hdwp = DeferWindowPos(hdwp, hwnd, NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
    EndDeferWindowPos(hdwp);
    }
    }
    }
    }

    ReplyDelete
  8. Your content should be reflective of what you customers are looking for. Knowing the keywords and phrases your customers are searching for help you create content that is specially tailored towards them. From blog posts all the way to your websites navigation bar, SEO helps to inform your content strategy https://growth-loops.com .

    ReplyDelete