First steps with the Chromium Embedded Framework and .NET

The Chromium Embedded Framework (CEF) is an open source project created in 2008 as a Web browser control powered by Google’s Chromium.
At the moment, this is a fairly powerful tool for developing desktop applications, a list of solutions using this control can be found here . But suffice it to say that it is used by such well-known products as Evernote and Steam.

So what does this framework give?

  • CEF allows you to create your own protocol handlers, thus implementing your own “closed” encryption algorithm (
    yes, unfortunate users of the old Internet Explorer and corporate web solutions, down with ActiveX). The same can be used to load data from static program resources
  • CEF allows you to wrap native functions in the object space of a Javascript virtual machine. Resource-intensive operations for processing large amounts of data can be shifted to more rigorous and faster programming languages.
  • CEF allows you to handle events of navigation, file downloads, and so on.

In general, everything that allows you to make your own browser like google chrome (but why?). But we will need it in order to create our own applications with HTML5 / CSS3 interface and hardware accelerated graphics.

And now about the sad


The chromiumembedded library , the link to which was given at the beginning of the article, is implemented in C ++. But what if your solution already works in a different, controlled programming language? Especially for us there are wrappers for Java, Delphi, Python and .NET. The use of the CefSharp library for .NET will be discussed.

Meet CefSharp

CefSharp is a wrapper library for chromiumembedded. However, its functionality is somewhat inferior in capabilities of the latter.
What is available to us:
  1. Create an unlimited number of WebView class components
  2. Handling page load events, navigation events
  3. Custom protocol handlers.
  4. Js code injection at page execution time
  5. Creating global [native code] objects with static methods

What is missing:
  1. Normal event model. No, seriously, this does not look like a .NET library:
            public Window(string Url, CefSharp.BrowserSettings settings = null)
            {
                // ...
                _Browser = new WebView(Url, settings ?? new CefSharp.BrowserSettings { DefaultEncoding = "UTF-8" });
                _Browser.PropertyChanged += _Browser_PropertyChanged;
                // ...
            }
            void _Browser_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "IsBrowserInitialized" && !isInitialized)
                    doSomeStuff();
                //и так далее
            }
    


  2. Embedding various objects for different instances of the WebView class. You’ll have to come to terms with the fact that anyone can call absolutely any method of your registered
  3. Type conversions from CLR to JS and vice versa. The crutch for a visual solution to this problem will be discussed further.
  4. You cannot associate the called method with the specific form where the WebView is located.
  5. Not really a minus, but the environment for compiling CefSharp should be Visual Studio 2008.

As an encouragement, we can note that you can start working with the library quite quickly (provided that you have a compiled version), as well as the fact that the level of understanding HTML + JS solutions is lower than that of WPF, and for those who afraid to go into the many months of studying complex technology, you can simply use your favorite HTML.

First meeting


The three main things that are needed for work are the local protocol handler, the global object, and the object that we will manage the framework for.

Local protocol handler

A couple of classes are implemented: the factory (implements the CefSharp.ISchemeHandlerFactory interface) and, in fact, the handler itself (implements the CefSharp.ISchemeHandler interface).
With the first, everything is clear:
    public class LocalSchemeHandlerFactory : ISchemeHandlerFactory
    {
        public ISchemeHandler Create()
        {
            return new LocalSchemeHandler();
        }
    }

The second will not be harder:
    public class LocalSchemeHandler : ISchemeHandler
    {
        // здесь могут быть свои конструкторы, личные методы класса и все остальное, что нужно для работы обработчика
        public bool ProcessRequest(IRequest request, ref string mimeType, ref Stream stream)
        {
             // через IRequest мы можем получить ссылку 
             // в mimeType нужно правильно указать MIME-тип данных. Это необходимо, хотя Chromium сумеет и отличить text/javascript от text/plain или image/png
             // в Stream мы передаем поток, зачастую это - MemoryStream данных, что мы считали с жесткого диска или из локального хранилища
             return true; // в случае успешного получения данных. Иначе false
        }
    }

In order to include the js-file of the application, you can use the GetStream or GetString method of the ResourceManager class. Of the pluses - the source code of your application will be located inside a .exe or .dll file. Of the minuses - when changing the js-code, you will have to recompile the application every time.

Bridge Object between .NET and JS

It is even simpler with it - it is a regular object containing methods and fields. One minus - for the whole project you will have one copy of each such class.

CEF Initialization

I decided to make the class a successor of ApplicationContext. For window display, WinForms runs faster and there is no need to drag WPF
    public class ApplicationController : ApplicationContext
    {
        protected Dictionary registeredObjects;
        public ApplicationController(CefSharp.Settings settings = null)
        {
            registeredObjects = new Dictionary();
            string resources = Path.Combine(Directory.GetCurrentDirectory(), "cache");
            if (Directory.Exists(resources))
                Directory.CreateDirectory(resources);
            CefSharp.CEF.Initialize(settings ?? new CefSharp.Settings() { 
                Locale = "ru",
                CachePath = resources
            });
            CefSharp.CEF.RegisterScheme("local", new LocalSchemeHandlerFactory());
            registerJsObject("Form", new WindowObject()); // а здесь уже регистрируется объект-мост.
        }
        public void registerJsObject(string Name, object Object)
        {
            if (!registeredObjects.ContainsKey(Name))
            {
                registeredObjects.Add(Name, Object);
                CefSharp.CEF.RegisterJsObject(Name, Object);
            }
        }
    }


On this, in fact, is all. You can create a form, add a WebView component to it and work as you like.
If you have read up to this place, then you are a patient person and I am grateful to you.

But this is not enough for us


As I noted earlier, there are some flaws in CefSharp. For example, you cannot associate a WebView component with a form that contains it. For this, a kind of cruel crutch was born , which I will present to the public.
In order not to litter the article with scraps of code, I will give some excerpts from the listing.

1 New Window class inherited from Form

  • It contains a subclass of FormEnumerator, which assigns a unique string identifier to each window and stores links to all Window objects. Using the getWindowById method, you can get the form.
  • JSInvoke static method that receives a call from the browser environment and calls a form function
  • The CSInvoke method that invokes the .NET JS method
  • The private method getFormRefrection, which creates a wrapper for the CLR methods of the form. The string is formed by StringBuilder based on the reflection data. It looks something like this:


2 Common Call Bridge Object

It performs a call operation from JS to C #:
public class WindowObject
    {
        public string Invoke(string Id, string Method, string JSONData)
        {
            object x = JSON.JsonDecode(JSONData);
            if (x is ArrayList)
            {
                ArrayList y = (ArrayList)x;
                object[] args = new object[y.Count];
                for (var i = 0; i < args.Length; i++)
                    args[i] = y[i];
                return JScripter.CreateString(Window.JSInvoke(Id, Method, args));
            } else
                return JScripter.CreateString(Window.JSInvoke(Id, Method, new object[] { x }));
        }
        public void Close(string Id)
        {
            Window.FormEnumerator.getWindow(Id).Close();
        }
    }

An attentive reader will notice something is wrong: instead of normal objects, CefSharp allows you to exchange only simple types, such as int, double, bool, string. But in real life it is usually just the opposite. Therefore, this crutch uses data packing / unpacking in JSON. The solution is imperfect, it takes a lot of time in vain, but these are the limitations of the library.
Since the DataContractJsonSerializer only works with certain types, it is problematic to use it. Therefore, the project used a 100% managed parser. Also a crutch.

You can find the code here .

Also popular now: