How to put CEF's message loop into a worker thread?

Having problems with building or using CEF's C/C++ APIs? This forum is here to help. Please do not post bug reports or feature requests here.

How to put CEF's message loop into a worker thread?

Postby q0e6k » Fri Oct 02, 2020 3:43 am

I'm trying to put CEF's message loop into a worker thread, hoping that it can go simultaneously with the message loop of widget toolkit such as gtk_main or wxGUIEventLoop. I stole some codes from tests/cefclient/ and put them in my code below. The code below is as concise as possible so as not to distract myself from anything else irrelevant.

Code: Select all
// main.cpp

#include <cassert> // for function-like macro assert
#include <future> // for class template future
#include <gdk/gdkx.h> // for function-like macro GDK_WINDOW_XID
#include <gtk/gtk.h>
#include <include/cef_app.h> // for function CefExecuteProcess
#include <include/cef_client.h>
#include <include/internal/cef_linux.h> // for class CefMainArgs
#include <memory> // for class template unique_ptr
#include <thread> // for class thread
#if defined(_NDEBUG)
    #define ASSERT(expr) expr
#else
    #define ASSERT(expr) assert(expr)
#endif
GdkVisual *get_default_visual(void);

void my_cef_ui_thread(std::future<Window>&& futCtnWin)
{
    // Create the first browser window.
    CefWindowInfo winfo;
    winfo.SetAsChild(futCtnWin.get(), CefRect());
    CefBrowserSettings bsettings;
    CefBrowserHost::CreateBrowser(winfo, nullptr, "https://www.google.com/", bsettings, nullptr, nullptr);
    // Enter CEF message loop.
    GMainContext *ctx = ::g_main_context_new();
    ::g_main_context_push_thread_default(ctx);
    GMainLoop *loop = ::g_main_loop_new(ctx, false);
    ::g_main_loop_run(loop);
    ::g_main_loop_unref(loop);
    ::g_main_context_unref(ctx);
    // Clean up CEF.
    ::CefShutdown();
}
int main(int argc, char *argv[])
{
    // Create the worker thread or not, depending on the role of this process.
    CefMainArgs args(argc, argv);
    int iExitStatusCode = ::CefExecuteProcess(args, nullptr, nullptr);
    if (iExitStatusCode != -1) return iExitStatusCode;
    // Initialize CEF.
    CefSettings settings;
    settings.multi_threaded_message_loop = true;
    ASSERT(::CefInitialize(args, settings, nullptr, nullptr) == true);
    std::promise<Window> pmiCtnWin;
    auto&& thdCEF = std::unique_ptr<std::thread>(new std::thread(my_cef_ui_thread, pmiCtnWin.get_future()));;
    // Create the top-level window.
    ::gtk_init(NULL, NULL);
    GtkWidget *frm = ::gtk_window_new(GTK_WINDOW_TOPLEVEL);
    ::gtk_widget_set_visual(frm, get_default_visual());
    ::gtk_widget_realize(frm);
    pmiCtnWin.set_value(GDK_WINDOW_XID(::gtk_widget_get_window(frm)));
    ::gtk_widget_show(frm);
    // Enter GTK event loop.
//    ::gtk_main();
    // Clean up and exit normally.
    thdCEF->join();
    return 0;
}


Below is an auxiliary function get_default_visual which is needed for running CEF on GTK+ 3. It is used in the above translation unit to change the X Visual before creating the underlying X window of the container widget for CEF browser window. It is necessary because the X Visual provided by GTK+ 3 isn't compatible with CEF.

Code: Select all
// get_default_visual.cpp

#include <gdk/gdk.h>
#include <gdk/gdkx.h> // for function-like macros GDK_SCREEN_XDISPLAY, GDK_SCREEN_XNUMBER and function gdk_x11_visual_get_xvisual
#include <gtk/gtk.h> // for function-like macro GTK_CHECK_VERSION

#if GTK_CHECK_VERSION(3, 15, 2)
GdkVisual *get_default_visual(void)
{
    // Get the default X visual.
    GdkScreen *scr = ::gdk_screen_get_default(); // the default physical screen
    Visual *xvDefault = DefaultVisual(
        GDK_SCREEN_XDISPLAY(GDK_X11_SCREEN(scr)), // the connection to the X server.
        GDK_SCREEN_XNUMBER(GDK_X11_SCREEN(scr)) // the index number of the default physical screen
    );
    // Get the list of all available GdkVisual objects.
    GList *list = ::gdk_screen_list_visuals(scr); // The type of the data in each list item is GdkVisual.
    // Find out which GdkVisual wraps the default X visual.
    GdkVisual *gvCurrent = NULL;
    for (GList *item = list; item; item = item->next) {
        gvCurrent = GDK_VISUAL(item->data);
        Visual *xvCurrent = ::gdk_x11_visual_get_xvisual(GDK_X11_VISUAL(gvCurrent));
        if (xvCurrent->visualid == xvDefault->visualid) break;
    }
    ::g_list_free(list);
    // Return the default X visual we just found.
    return gvCurrent;
}
#endif


The program can be compiled with these commands.

Code: Select all
$ dirCEF="/somewhere/cef_binary_xxx_linux64"
$ g++ -Wall -I${dirCEF} $(pkg-config --cflags gtk+-3.0,glib-2.0) -c main.cpp
$ g++ -Wall -I${dirCEF} $(pkg-config --cflags gtk+-3.0,glib-2.0) -c get_default_visual.cpp
$ g++ main.o get_default_visual.o -Wl,-rpath=${dirCEF}/Debug ${dirCEF}/libcef_dll/libcef_dll_wrapper.a \
> -L${dirCEF}/Debug -lcef -lboost_thread -pthread $(pkg-config --libs gtk+-3.0) -o a.out


It can be built and open web page successfully on Debian 10 based distro, e.g. MX 19.2. However, if uncomment the line that calls gtk_main, it crashes immediately. It looks like some CEF messages were wrongly flowing into the message loop managed by gtk_main. Is there any way to tell CEF to send all its messages only to a specific thread? Please help. Thanks. :)
q0e6k
Newbie
 
Posts: 6
Joined: Fri Oct 11, 2019 5:18 am

Re: How to put CEF's message loop into a worker thread?

Postby amaitland » Fri Oct 02, 2020 4:42 am

How about using multi_threaded_message_loop and letting CEF run it's own thread to handle the message loop?

This option is supported on windows and linux.
Maintainer of the CefSharp project.
amaitland
Virtuoso
 
Posts: 1292
Joined: Wed Jan 14, 2015 2:35 am

Re: How to put CEF's message loop into a worker thread?

Postby q0e6k » Fri Oct 02, 2020 10:00 am

amaitland wrote:How about using multi_threaded_message_loop and letting CEF run it's own thread to handle the message loop?


Well, that's exactly what I did in the above code. Plus, according to tests/cefclient/ and some trial-and-errors, g_main_loop_run has to be called in either main thread or a worker thread, which is different from the case when using CefRunMessageLoop that can only be called from main thread.

If g_main_loop_run is not called after setting multi_threaded_message_loop to true, the application will crash. So, there is more to be done than just setting multi_threaded_message_loop to true like what https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-message-loop-integration says.

The question is that I don't know how to have both these 2 message loops run simultaneously:
  • CEF's message loop in a worker thread
  • the message loop of widget toolkit (e.g. gtk_main) in the main thread

If uncomment the gtk_main line (please see the above code), the application will crash. My best guess is that some CEF messages flow wrongly into gtk_main, instead of g_main_loop_run into which they are supposed to go.
q0e6k
Newbie
 
Posts: 6
Joined: Fri Oct 11, 2019 5:18 am

Re: How to put CEF's message loop into a worker thread?

Postby Czarek » Fri Oct 02, 2020 11:18 am

You are running two GTK main loops. When you set multi_threaded_message_loop option to true, then CEF runs a message loop of its own on UI thread. You do not create UI thread. You just run GTK loop on main thread and example code for that is in MainMessageLoopMultithreadedGtk::Run.
Maintainer of the CEF Python, PHP Desktop and CEF C API projects. My LinkedIn.
User avatar
Czarek
Virtuoso
 
Posts: 1927
Joined: Sun Nov 06, 2011 2:12 am

Re: How to put CEF's message loop into a worker thread?

Postby q0e6k » Sat Oct 03, 2020 3:09 am

Czarek wrote:You are running two GTK main loops. When you set multi_threaded_message_loop option to true, then CEF runs a message loop of its own on UI thread. You do not create UI thread. You just run GTK loop on main thread and example code for that is in MainMessageLoopMultithreadedGtk::Run.


Thanks for clear my misunderstanding. I thought g_main_loop_run (in MainMessageLoopMultithreadedGtk::Run) is part of the mysterious multi_threaded_message_loop thing, because I've tried to replace g_main_loop_run with gtk_main and it crashed. So, I thought there shall be both of them running together, one is for CEF and the other for GTK+.

I'm now learning the difference between g_main_loop_run and gtk_main, trying to figure out what is global-default context and thread-default context. Why g_main_loop_run works and gtk_main doesn't. It's quite confusing to me, this GLib/GTK stuff. I don't know if it happens that you are familiar with them as well? This GLib/GTK thing, maybe I shan't ask here on CEF forum. Thanks again for your help. I really appreciate it. :)
q0e6k
Newbie
 
Posts: 6
Joined: Fri Oct 11, 2019 5:18 am

Re: How to put CEF's message loop into a worker thread?

Postby Czarek » Sat Oct 03, 2020 5:58 am

The comment in the source codes explains it:
Code: Select all
 // Chromium uses the default GLib context so we create our own context and
  // make it the default for this thread.

gtk_main uses default GLib context, but Chromium already uses it, so you need to create new context for the loop and the example code in cefclient does it.
Maintainer of the CEF Python, PHP Desktop and CEF C API projects. My LinkedIn.
User avatar
Czarek
Virtuoso
 
Posts: 1927
Joined: Sun Nov 06, 2011 2:12 am

Re: How to put CEF's message loop into a worker thread?

Postby q0e6k » Sun Oct 04, 2020 12:23 am

Czarek wrote:The comment in the source codes explains it:
Code: Select all
 // Chromium uses the default GLib context so we create our own context and
  // make it the default for this thread.

gtk_main uses default GLib context, but Chromium already uses it, so you need to create new context for the loop and the example code in cefclient does it.


This design makes little sense to me, because not only does GTK+ use gtk_main but also wxWidgets. Is there any way to integrate CEF into these widget toolkits without rewriting their event loops?

Thanks, you help me a lot. You probably don't believe this but I do have been stuck on this for months now. :oops:
q0e6k
Newbie
 
Posts: 6
Joined: Fri Oct 11, 2019 5:18 am

Re: How to put CEF's message loop into a worker thread?

Postby Czarek » Sun Oct 04, 2020 2:14 pm

You can patch wx, or you can patch chromium, or you can run chromium in a separate process. If things are too complex then stick with the defaults.
Maintainer of the CEF Python, PHP Desktop and CEF C API projects. My LinkedIn.
User avatar
Czarek
Virtuoso
 
Posts: 1927
Joined: Sun Nov 06, 2011 2:12 am

Re: How to put CEF's message loop into a worker thread?

Postby q0e6k » Sun Oct 04, 2020 7:14 pm

Czarek wrote:You can patch wx, or you can patch chromium, or you can run chromium in a separate process. If things are too complex then stick with the defaults.

I'll look into it. Thank you so much, Czarek. Really appreciate it. The information you provided are very concise and to the point.
q0e6k
Newbie
 
Posts: 6
Joined: Fri Oct 11, 2019 5:18 am


Return to Support Forum

Who is online

Users browsing this forum: Google [Bot] and 68 guests

cron