Chromium Embedded Framework. Интеграция с MFC

Я уже очень плотно подсел на интернет, потому совсем не удивителен тот факт, что предпочитаю веб-интерфейсы даже в настольных приложениях. На мой взгляд, разработка интерфейса на javascript существенно проще, чем аналогичная работа на C++.

Несколько лет назад я столкнулся с необходимостью сделать grid, в котором можно объеденять ячейки и не нашел ни одного решения, которое можно быстро интегрировать в MFC проект. Тогда я начал искать решение и обнаружил его в связке HTML + javascript, которая отлично справляется с отрисовкой интерфейса, в то время как C++ приложение обеспечивает связь с БД и работу с файловой системой.

В 2007 году мой выбор пал на InternetExplorer, по той причине, что для него есть MFC API в виде класса CHtmlView. За две ночи, остававшиеся до демонстрации проекта был сверстан интерфейс, которой в дальнейшем стал основным в программе. В то время мы еще использовали C++ классы для отображения, но позде отказались от них в пользу jQuery UI.

Ни разу не пришлось жалеть о принятом решении, но все чаще приходилось сталкиваться с особенностями IE и реализации IWebBrowser. Все чаще находились вещи, которые невозможно организовать и все чаще звучали слова о необходимости переходить на другой браузерный движок. Наконец это случилось.

Выбор пал на Chromium, а точнее на CEF. Вряд ли я смогу объяснить причину, даже сейчас. Позже придумаю какое-нибудь грамотное объяснений, но в общем-то выбор достаточно случаен. Может всему виной телевизионная реклама Google Chrome?

Документация по проекту достаочно скудна, и содержит немало ошибок. Информацию по крупицам приходится выбирать с форума по Chromium Embedded Framework и из исходников демонстрационного проекта.

В своем блоге я буду описывать шаги по интеграции CEF в свои проекты, проблемы с которыми придется столкнуться на этом пути и способы их решения. Итак, серия первая:

Интеграция Chromium Embedded Framework в MFC-Приложение

Сначала надо скачать framework. По ссылке архив, который содержит библиотеку CEF в бинарном виде и включает пример использования библиотеки с использованием WINAPI

Тeперь создаем MFC-проект. Так как бинарники библиотеки созданы с флагом компиляции /MD, то использовать статическую линковку библиотеки не выйдет. К счастью разработчики позаботились о страждущих и написали необходимую обертку, которая позволяет подключать CEF динамически. Обертка (libcef_dll_wrapper) так же будет в загруженном архиве

Для подключения libcef_dll_wrapper его нужно построить с теми же настройками, с которыми вы собираетесь строить собственный проект. То есть, если в своем проекте вы подключаете MFC Shared Library статически, то и к врапперу ее надо подключать так же. В противном случае получите изрядное количество ошибок на этапе линковки. Подробнее об этом можно почитать в wiki проекта: Linking Different RunTime Libraries

Ниже будет приведен код, который необходим для запуска фреймворка. Он же присутствует в тестовом проекте, который можно скачать прямо здесь.

Исходный код проекта CEF MFC для VisualStudio 2010

Содержимое архива надо распаковать в ту же папку, где находится CEF, добавить проект в VisualStudio и построить его. Приложение содержит минимальнонеобходимое количество кода для подключения фреймвока. Оно лишь отображает стартовую странцу Google, не предоставляя дополнительных средств навигации. Единственное реализованное взаимодействие с CEF в рамках проекта - это согласованное изменение размеров браузера при изменении размерок окна приложения.

Запуск Cromium Embedded Framework в MFC

Для взаимодействия с браузером нам потребуется класс-handler, наследник от CefClient, который может быть практически пустым, но должен реализовать некоторое количество абстрактных функций базового класса. Для этого используется следующий макрос:

  1. // Include the default reference counting implementation.
  2. IMPLEMENT_REFCOUNTING(ClientHandler);
  3.  

Теперь запустим CEF, для чего переопределим метод OnInitialUpdate у представления

  1. void CCEFView::OnInitialUpdate()
  2. {
  3. CView::OnInitialUpdate();
  4.  
  5. CefRefPtr<ClientHandler> client(new ClientHandler());
  6. m_clientHandler = client;
  7.  
  8. CefSettings settings;
  9. settings.multi_threaded_message_loop = true;
  10. CefRefPtr<CefApp> app;
  11. // Initialize CEF.
  12. CefInitialize(settings, app);
  13.  
  14. CefWindowInfo info;
  15. RECT rect;
  16. GetClientRect(&rect);
  17. info.SetAsChild(GetSafeHwnd(), rect);
  18.  
  19. CefBrowserSettings browserSettings;
  20.  
  21. CefBrowser::CreateBrowser(info, static_cast<CefRefPtr<CefClient> >(client), L"http://www.google.com", browserSettings);
  22.  
  23. // TODO: Add your specialized code here and/or call the base class
  24. }
  25.  

Этого вполне достаточно для того чтбы увидеть первые плоды интеграции. Запущенный в таком виде проект отобразит окно браузера и загрузит стартовую страницу. Однако при изменнии размеров окна браузер не будет растягиваться и сжиматься, сохраняя размер заданый ему при инициализации. Для реализации согласованного ресайза потребуется переопределить метод OnSize, но сначала необходимо получить ссылку на созданный экземпляр браузера.

Изменение размеров CEF-браузера

Изменим класс ClientHandler и "подпишемся" на уведомление о создании браузера, для чего унаследуемся от CefLifeSpanHandler и реализуем 2 метода GetLifeSpanHandler и OnAfterCreated

  1. class ClientHandler :
  2. public CefClient,
  3. public CefLifeSpanHandler
  4. {
  5. protected:
  6. CefRefPtr<CefBrowser> m_Browser;
  7.  
  8. public:
  9. ClientHandler(void);
  10. ~ClientHandler(void);
  11.  
  12. CefRefPtr<CefBrowser> GetBrowser() { return m_Browser; }
  13.  
  14. // CefClient methods
  15. virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE
  16. { return this; }
  17.  
  18. virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
  19.  
  20. // Include the default reference counting implementation.
  21. IMPLEMENT_REFCOUNTING(ClientHandler);
  22. // Include the default locking implementation.
  23. IMPLEMENT_LOCKING(ClientHandler);
  24. };
  25.  

В реализации OnAfterCreated сохраним ссылку на созданный браузер

  1.  
  2. void ClientHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser)
  3. {
  4. m_Browser = browser;
  5. }
  6.  

Теперь осталось изменить размер браузера при получении сообщения WM_SIZE

  1. void CCEFView::OnSize(UINT nType, int cx, int cy)
  2. {
  3. CView::OnSize(nType, cx, cy);
  4. if(m_clientHandler.get())
  5. {
  6. CefRefPtr<CefBrowser> browser = m_clientHandler->GetBrowser();
  7. if(browser)
  8. {
  9. CefWindowHandle hwnd = browser->GetWindowHandle();
  10. RECT rect;
  11. this->GetClientRect(&rect);
  12. ::SetWindowPos(hwnd, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
  13. }
  14. }
  15. // TODO: Add your message handler code here
  16. }
  17.  

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

P.S. 2012.03.26 Как выяснилось в приложенном проекте есть пара ошибок, связанных с освобождением ресурсов. Архив оставлю здесь для истории, а актуальную версию с исправлениями теперь можно найти на githab https://github.com/VladimirRybakov/cef-mfc

Читайте в блоге

MFC. Использование HTML-интерфейса.
Раскрашиваем код на сайте. GeShi - PHP Code Colorer
Получение уникального числа в коммандном файле Windows

Комментарии:

Дмитрий @ 06.06.2012 17:41

Здравствуйте не плохая статья, возник вопрос при определении системами браузер определяется как chrome и версия Есть ли возможность это изменить?

Войдите на сайт, чтобы оставить комментарий