Первые шаги с Chromium Embedded Framework и .NET

Chromium Embedded Framework (CEF) — это проект с открытыми исходными кодами, созданный в 2008 году как элемент управления Web browser, работающий на базе Chromium от Google.
На данный момент это довольно мощный инструмент для разработки настольных приложений, со списком решений, использующих этот контрол можно ознакомиться здесь. Но достаточно сказать, что его используют такие широко известные продукты, как Evernote и Steam.

Итак, что же дает этот фреймворк?

  • CEF позволяет создать свои обработчики протоколов, таким образом, реализовать свой «закрытый» алгоритм шифрования (да-
    да, несчастные пользователи старого Internet Explorer и корпоративных web-решений, долой ActiveX). Этим же можно воспользоваться, чтобы подгружать данные из статических ресурсов программы
  • CEF позволяет делать обертку над нативными функциями в пространстве объектов виртуальной машины Javascript. Ресурсоемкие операции по обработке больших массивов данных можно переложить на более строгие и быстрые языки программирования
  • CEF позволяет обрабатывать события навигации, скачивания файлов и так далее

В общем, все, что позволит Вам сделать свой собственный браузер наподобие google chrome (только зачем?). Но нам он понадобится, для того, чтобы создавать собственные приложения с HTML5/CSS3 интерфейсом и аппаратно ускоренной графикой.

А теперь о грустном


Библиотека chromiumembedded, ссылка на которую давалась в начале статьи, реализована на C++. Но что делать, если Ваше решение уже работает на другом, управляемом языке программирования? Специально для нас существуют обертки для Java, Delphi, Python и .NET. Об использовании библиотеки CefSharp для .NET и пойдет речь.

Знакомьтесь, CefSharp

CefSharp — библиотека-обертка для chromiumembedded. Однако функционал ее несколько уступает по возможностям последней.
Что же нам доступно:
  1. Создание неограниченного числа компонентов класса WebView
  2. Обработка событий по загрузке страницы, события навигации
  3. Собственные обработчики протоколов.
  4. Внедрение js-кода во время выполнения страницы
  5. Создание глобальных [native code] объектов со статическими методами

Чего не хватает:
  1. Нормальной модели событий. Нет, серьезно, не выглядит это как библиотека .NET:
            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. Внедрения различных объектов для разных экземпляров класса WebView. Придется смириться с тем, что любой можно вызвать абсолютно любой метод из зарегистрированных Вами
  3. Преобразования типов из CLR в JS и обратно. О костыле для визуального решения этой проблемы речь пойдет дальше
  4. Нельзя связать вызываемый метод с конкретной формой, где расположен WebView.
  5. Не совсем минус, но среда для компилирования CefSharp должна быть Visual Studio 2008.

В качестве поощрения можно отметить то, что начать работать с библиотекой можно довольно быстро (при условии, что у вас есть компилированный вариант), а также тот факт, что планка понимания HTML+JS решений ниже, чем у WPF, и для тех, кто боится уходить в многомесячное изучение сложной технологии, можно просто использовать всеми любимый HTML.

Первое знакомство


Три главные вещи, которые нужны для работы — это локальный обработчик протокола, глобальный объект и тот объект, который будет у нас управлять фреймворком.

Локальный обработчик протокола

Реализуется пара классов: фабрика (реализует интерфейс CefSharp.ISchemeHandlerFactory) и, собственно, сам обработчик (реализующий интерфейс CefSharp.ISchemeHandler).
С первым все понятно:
    public class LocalSchemeHandlerFactory : ISchemeHandlerFactory
    {
        public ISchemeHandler Create()
        {
            return new LocalSchemeHandler();
        }
    }

Второй не будет сложнее:
    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
        }
    }

Для того, чтобы подключить js-файл приложения, можно воспользоваться методом GetStream или GetString класса ResourceManager. Из плюсов — исходный код вашего приложения будет находиться внутри .exe или .dll файла. Из минусов — при изменении js-кода придется каждый раз заново компилировать приложение.

Объект-мост между .NET и JS

С ним еще проще — это обычный объект, содержащий методы и поля. Один минус — на весь проект у Вас будет по одному экземпляру каждого такого класса.

Инициализация CEF

Я решил сделать класс наследником ApplicationContext. Для оконного отображения WinForms запускается быстрее, и нет необходимости тянуть за собой WPF
    public class ApplicationController : ApplicationContext
    {
        protected Dictionary<string, object> registeredObjects;

        public ApplicationController(CefSharp.Settings settings = null)
        {
            registeredObjects = new Dictionary<string, object>();
            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);
            }
        }
    }


На этом, собственно и все. Можно создавать форму, добавлять в нее компонент WebView и работать как душе угодно.
Если Вы дочитали до этого места, то Вы — терпеливый человек и я благодарен Вам.

Но нам этого мало


Как я уже отмечал ранее, в CefSharp есть некоторые недостатки. Например, нельзя связать компонент WebView с формой, его содержащую. Для этого родился некоторого рода жестокий костыль, который я представлю на обозрение публики.
Дабы не захламлять статью лоскутами кода, я приведу некоторые выдержки из листинга.

1 Новый класс Window, наследуемый от Form

  • Он содержит подкласс FormEnumerator, который присваивает каждому окну свой уникальный строковый идентификатор и хранит ссылки на все объекты Window. По методу getWindowById можно получить форму.
  • Статический метод JSInvoke, который получает вызов из среды браузера и вызывает функцию формы
  • Метод CSInvoke, который вызывает метод среды JS из .NET
  • Закрытый метод getFormRefrection, который создает «оболочку» для CLR методов формы. Строка формируется StringBuilder'ом на основании данных рефлексии. Выглядит это примерно так:


2 Объект-мост общих вызовов

Он выполняет операцию по вызову из JS в 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();
        }
    }

Внимательный читатель заметит неладное: вместо нормальных объектов CefSharp позволяет обмениваться только простыми типами, такими как int, double, bool, string. Но в реальной жизни обычно, как раз, наоборот. Поэтому данный костыль использует упаковку/распаковку данных в JSON. Решение неидеальное, затрачивается множество времени зря, но таковы данные ограничения библиотеки.
Поскольку DataContractJsonSerializer работает только с определенными типами, его использовать проблематично. Поэтому в проекте был использован 100% managed парсер. Тоже костыль.

С кодом можете ознакомиться здесь.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 18
  • +7
    За один только флажок «tutorial» можно было плюсануть)

    Но, Дмитрий Сергеевич, от сдачи лабораторных работ это вас не освобождает)
    • 0
      Да и тэги со вкусом подобраны
    • 0
      А как же CefGlue и Xilium.CefGlue? И да, забыли упомянуть что CefSharp уже 2 месяца не обновляется…
      • 0
        Спасибо за ссылки, на досуге изучу предлагаемые ими решения. Дело в том, что на googlecode давалась ссылка именно на CefSharp как на «официальный враппер» для chromiumembedded, других решений там не предлагалось, что не исключает их право на жизнь.
        Вопрос изучения CEF передо мной стал месяца четыре назад, так что на момент компиляции исходной библиотеки проект еще был актуален
        • 0
          На гугло-коде ссылки есть на все биндинги. Включая cefsharp и cefglue двух версий. Теже ссылки есть на википедии. :) Это видимо глаз замылился просто. :)
        • 0
          Туториал по Xilium.CefGlue хотел написать сам автор, но он пока занят.
          А CefGlue.1 — он забросил, хотя его несложно обновить до более современных версий.
          Впрочем пока особо сильно там описывать нечего.
          В первую очередь на самом деле нужно быть знакомым с самим CEF и не ленится читать вики, форум и мануалы — тогда вопросов скорее всего не будет.
        • +2
          Есть анлогичная штука awesomium.com/, она полу-комерческая и поговаривают достаточно качественная.
          • +1
            Судя по API для .NET функционал ее на порядки выше, чем у CefSharp. Спасибо за ссылку
            • 0
              Она бесплатная для разработчиков с доходом менее 50к у.е., если не ошибаюсь.
              Использовал при реализации своего дипломного проекта, штука отменная.
              • 0
                Штука не аналогичная — она исключительно off-screen ренедрингом занимается, да и возможностей у него существенно ниже.
                • 0
                  > off-screen ренедрингом

                  я хз что это значит, на нем есть куча приложений который ШТМЛ показывают, MarckPad одно из них.
                  • 0
                    Это значит, что это сначало всё дело рендерится в память, а затем на экран. Без этого не обойтись используй вы WPF или допустим встраивая браузер в игру. Но для 99.9% бизнес приложений — люди даже не понимают, что им этот «нативный» WPF не нужен. Более того в WPF3-4 есть баг, который возможно уже и починили, из-за которого происходила полная инвалидация области, вместо предлагаемому ему куска — из-за этого мы получаем очень красивые приложения с двумя кнопками и 30% жрущих проц. :)
                    Chromium же отображая ваши странички на экране — использует «максимально» прямой рендеринг, насколько это возможно в той или иной системе (к слову сказать — лучше всего с этим только в виндовс, и похоже что стало не так давно в макос) — но тем не менее — просто, тут случай, что не плохо было бы понимать, для чего нужен html engine — а потом уже выбирать средства. Да, awesomium неплох, но сейчас лучше всех CEF3 и относясь к .NET — это Xilium.CefGlue. Хотя CEF3 пока что не имеет возможности off-screen rendering (CEF1 имеет) — но, меня вот лично это никак не парит. Зато я сделал вещь которая работает не только на венде, но и используется на линуксе в реальных целях. Так что это лишь ещё один параметр — каждый выбирает то, что ему нравится, и то что ему больше подходит.
                    Awesomium сильно выигрывает против CEF тем что, awesomium банально старее и имеет красивый сайт. На этом его преимущества заканчиваются.
                    С любым из продуктов если возникнет необходимость править нативный код — с CEF это возможно — с awesomium я не знаю. Да, это иногда приходится делать, и баги иногда далеко не в CEF, а в chromium. Но опять же CEF3 (wrapping of chromium's content api) — это очень хороший баланс, если интересует стабильность.
              • 0
                Это подаёт мне мысль сочинить блогозапись на Хабрахабре ещё об одной обёртке вокруг CEF. И надеюсь написать, но попозже.
              • +1
                Если нужно не всеми функциями библиотеки, то можно ее использовать из-под C++ CLI сборки, методы управляемых классов которой без проблем вызываются из .Net сборок.
                • 0
                  Натолкнуло на мысль слабать браузер с расширениями для препроцессинга контента на C# и F# (ну не люблю я JavaScript). Но думая дальше пришёл к выводу, что логичнее таки сделать proxy-сервер. Вообще вещь интересная, спасибо.
                  • 0
                    Кстати смотря на chillitom/cefsharp не стоит забывать, что последние годы его мэйнтейнит ataranto/cefsharp (на гитхабе). Сейчас версии вроде бы обновлены — но долгое время, основная страница содержала просто морально устаревшие сборки.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.