Clone wiki

ChromiumFX / Walkthrough 02

Walkthrough 02 - Getting started with the remoting framework

Note: This document is not up to date. While the overall concepts are still valid, some of the functions mentioned in it may have changed.

This walkthrough is about getting started with ChromiumFX and it's remoting framework if you don't want or don't need the ChromiumWebBrowser control. See also Walkthrough 01 - Getting started with the Browser Control

In order to use ChromiumFX, you have to reference ChromiumFX.dll. The native libcfx.dll library and the CEF libraries have to be in a place where they can be found: by default, in the same directory as the executing assembly or in a subdirectory called "cef".

The ChromiumFX assembly provides 2 namespaces:

    using Chromium;
    using Chromium.Remote;

The Chromium namespace contains all framework classes, most of them wrappers of the underlying CEF classes. So for cef_app you have CfxApp, for cef_browser you have CfxBrowser and so on.

The Chromium.Remote namespace contains all the remote framework classes, which are proxies for the framework classes in the render process. For instance, you can use CfrBrowser in the browser process as a proxy to the corresponding CfxBrowser object in the render process and get access to the browser's DOM and V8. "Cfr" stands for "ChromiumFX Remote".

First of all, initialize the framework:

    public class Program {

        [STAThread]
        public static void Main() {

            CfxRuntime.LoadLibraries();

In the secondary process' main function, now you call CfxRuntime.ExecuteProcess() or CfxRemoting.ExecuteProcess(). In the first case, the secondary process will run without setting up a remoting framework, leaving the proxy classes Cfr* in the browser process inoperacional. In this walkthrough we go for the second option and set up ChromiumFX with a working remoting framework.

Note that your secondary process can run in a separate assembly or in the same assembly as the browser process. In the first case, this is all you need for the secondary assembly:

    class Program {
        public static void Main() {
            CfxRuntime.LoadLibraries();
            int retval = CfxRemoting.ExecuteProcess();
            Environment.Exit(retval);
        }
    }

In the second case, continue the previous line with the following statements:

            int retval = CfxRemoting.ExecuteProcess();
            if(retval >= 0)
                Environment.Exit(retval);

So what happens in CfxRemoting.ExecuteProcess()? There are three outcomes: If called for the browser process it will return immediately with a value of -1. If called for a recognized secondary process other than a render process it will call return CfxRuntime.ExecuteProcess() and block until the process should exit and then return the process exit code. See also the description for cef_execute_process() or CfxRuntime.ExecuteProcess().

If called for a render process it will setup a IPC connection to the browser process and pass control flow into the browser process. More about this later.

In the browser process, continue with initialization:

            var app = new CfxApp();
            var processHandler = new CfxBrowserProcessHandler();
            app.GetBrowserProcessHandler += (sender, e) => e.SetReturnValue(processHandler);
            //(...) add more handlers to app
            if(!CfxRuntime.Initialize(settings, app, RenderProcessStartup))
                throw new CfxException("Failed to initialize CEF library.");

Note the additional parameter RenderProcessStartup which is a CfxRenderProcessStartupDelegate and the entry point for the remote renderer main thread. More about the RenderProcessStartup callback function later.

app and processHandler are optional. If your application does not need to listen to the events provided by the CfxApp object, you can replace the previous lines by the following lines:

            if(!CfxRuntime.Initialize(settings, null, RenderProcessStartup))
                throw new CfxException("Failed to initialize CEF library.");

Setup is done. We can now create and use browsers etc. There are plenty of ressources on the web about how to get started with CEF browser setup, and ChromiumFX works just the same. Depending on what you want to do, you will need CfxWindowInfo, CfxClient, CfxBrowserHost etc. Also take a look at the ChromiumWebBrowser code in this project.

Back to the remoting setup:

The remoting framework will now be listening to OnBeforeChildProcessLaunch events. Whenever a render process is launched, it will set up named pipe streams and add their names to the child process' command line. In the child process, CfxRemoting.ExecuteProcess() will detect those pipe names and connect back to the server process. It will then pass control flow into the browser process and block. The browser process will pickup and pass control flow into the application defined RenderProcessStartup callback function previously passed into CfxRuntime.Initialize().

The callback has the following signature:

        internal static int RenderProcessStartup(CfrRuntime remoteRuntime) {

You can think of it as if the render process main thread, going through CfxRemoting.ExecuteProcess(), ends up here. Of course, since those are two separate processes, not the render thread itself will enter this function. It's a threadpool thread from the browser process triggered by an IPC message that does the work here, while the render main thread waits on a monitor in the render process until this function returns. If any function on the Cfr* proxy classes is called from inside the scope of this callback, another IPC message will be sent back to the render process and cause the render process to be pulsed and do the work.

var processHandler = new CfrRenderProcessHandler(remoteRuntime);

All Cfr* classes take a CfrRuntime argument in the constructor (if they have one) because the object has to know to which render process it belongs. It is a proxy for the CfxRuntime functions in the render process. Every render process to be started gets it's own CfrRuntime object in the browser process, which will be passed into this startup function. For this to work, you have to call remoteRuntime.ExecuteProcess() at the end of the startup function, which will cause CfxRuntime.ExecuteProcess() to be called in the render process . But first let's setup something useful to do with the remoting framework.

We want to listen to render process handler events: for example, we want to be notified when a browser object is created in the render process.

        internal static int RenderProcessStartup(CfrRuntime remoteRuntime) {

            var processHandler = new CfrRenderProcessHandler(remoteRuntime);
            processHandler.OnBrowserCreated += processHandler_OnBrowserCreated;
            var app = new CfrApp(remoteRuntime);
            app.GetRenderProcessHandler += (sender, e) => { e.SetReturnValue(processHandler); };

            return remoteRuntime.ExecuteProcess(app);
        }

        static void processHandler_OnBrowserCreated(object sender, CfrOnBrowserCreatedEventArgs e) {

            var context = e.Browser.MainFrame.V8Context;
            CfrV8Value retval;
            CfrV8Exception exception;
            context.Eval("return 'Hello V8'", out retval, out exception);

            CfrDomVisitor visitor = new CfrDomVisitor(e.RemoteRuntime);
            visitor.Visit += visitor_Visit;
            e.Browser.MainFrame.VisitDom(visitor);

        }

        static void visitor_Visit(object sender, CfrDomVisitorVisitEventArgs e) {
            if(e.Document.Body.HasChildren)
                Console.WriteLine("DOM document body has children!");
        }

Those are some examples for things you can do with the remoting framework. Basically, you can access the whole CEF API in the render process without splitting your application's logic.

Note that remoteRuntime.ExecuteProcess(app) will execute on the render thread. Also processHandler_OnBrowserCreated() and visitor_Visit, while running on a threadpool thread in the browser process, will maintain affinity with the render thread, so calls like context.Eval() and e.Browser.MainFrame.VisitDom(visitor) will actually run on the main thread in the render process.

Remember that render processes are volatile and so are all Cfr* objects created for them. A render process may get killed by the browser process at any time, for instance when you navigate from one URL to another. You have to keep that in mind when designing application logic around the remoting API. For an example about how to use this in a sensible way, check the code for the ChromiumWebBrowser control. It takes advantage of the remoting API to provide functionality like injection of javascript functions with C# callback and access to the DOM.

Updated