Chromium Embedded Framework(Cef)开发总结
Chromium Embedded Framework(CEF)是一个简单的框架,用于在其他应用程序中嵌入基于 Chromium 的浏览器,它支持 Windows,Linux,Mac 平台。
下载编译
你可以在 CEF 官方网站下源码,打开 https://bitbucket.org/chromiumembedded/cef/overview ,复制 git 地址,使用 git 工具下载源码。不过由于种种网络限制、编译的繁琐性和对机器性能的要求,不推荐使用源码进行编译,建议直接下载已经编好的 CEF 库。
你可以直接在 cefbuilds 或 CEF Automated Builds 这两个网站下载已经编好的 CEF 库。我这里使用的是 Windows 系统,下载完成后解压,然后还需要使用 CMake + Visual Studio 编译 CEF 的 C++ 封装库 libcef_dll_wrapper
,编译过程很简单就不过多阐述了。
CEF 接口
你可以在 官方网站 查看 CEF 的相关文档,你可以在 CEF 官方文档中文翻译版 查看 CEF 导读和一些经验文档。
下面简单介绍下 CEF 的主要接口。
CefApp 类
CefApp
提供了不同进程的可定制回调函数,每一个进程对应一个 CefApp
接口。CefBrowserProcessHandler
对应浏览器进程的回调,CefRenderProcessHandler
对应渲染进程的回调。我们应该继承 CefApp
、CefBrowserProcessHandler
、CefRenderProcessHandler
接口。如果完全使用多进程模式,可以分别在浏览器进程和渲染进程里分开继承接口。
CefApp::OnBeforeCommandLineProcessing
方法里可以附加传入给 CEF 的命令行参数,这里可以附加很多控制参数。CefRenderProcessHandler::OnWebKitInitialized
方法可以在渲染进程初始化时用来注册JS扩展代码,实现C++与JS交互。CefRenderProcessHandler::OnFocusedNodeChanged
方法可以检测当前获取到焦点 HTML 元素,获取到一些元素信息可以通过进程通信发送给浏览器进程来辅助做进一步的判断CefRenderProcessHandler::OnProcessMessageReceived
方法用于接收浏览器进程发来的消息,在做 C++ 与 JS 交互时会用到
CefClient 类
每一个 CefBrowser
对象会对应一个 CefClient
接口,用于处理浏览器页面的各种回调信息,包括了 Browser
的生命周期,右键菜单,对话框,状态通知显示,下载事件,拖曳事件,焦点事件,键盘事件,离屏渲染事件。随着 CEF 版本的更新这些接口也会扩展和更新,多数对 CEF 进行行为控制的方法都集中在这些接口,如果对 CEF 有新的功能需求,一般都可以先翻翻这些接口中有没有提供相关功能
CefClient::OnProcessMessageReceived
方法用于接收渲染进程发到的消息,在做 C++ 与 JS 交互时会用到
CefSettings 结构体
CefSettings
结构体定义了 CEF 的全局配置信息,比如指定单进程模式、指定渲染子进程路径、设置localstorage
路径、设置日志等级、CEF 资源文件路径。其中对于项目最重要的字段是 single_process
、multi_threaded_message_loop
、windowless_rendering_enabled
,分别用于指定单进程模式、多线程渲染模式、离屏渲染模式。
兼容现有的消息循环
如果是 UI 线程消息循环构架较简单的项目,可以直接调用 CefRunMessageLoop
来使用 CEF 自带的消息循环,它会阻塞线程直到调用了 CefQuitMessageLoop
函数,CefRunMessageLoop
是兼容传统的 Win32 消息循环的。
嵌入客户端
嵌入客户端可以参考 cefclient
示例程序中的代码,不是很复杂。基本流程如下:
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
// Enable High-DPI support on Windows 7 or newer.
CefEnableHighDPISupport();
// 在 Windows 平台下,CefMainArgs 接收 wWinMain 函数传入的参数:实例句柄(HINSTANCE),
// 这个实例能够通过函数GetModuleHandle(NULL)获取。
// 在 Linux、Mac OS X 平台下,它接收 main 函数传入的 argc 和 argv 参数值,
// CefMainArgs main_args(argc, argv);
CefMainArgs main_args(hInstance);
void* sandbox_info = NULL;
// Manage the life span of the sandbox information object. This is necessary
// for sandbox support on Windows. See cef_sandbox_win.h for complete details.
CefScopedSandboxInfo scoped_sandbox;
sandbox_info = scoped_sandbox.sandbox_info();
// 可选择性地实现CefApp接口
CefRefPtr<MyCefApp> app(new MyCefApp);
// 填充这个结构体,用于定制CEF的行为。
CefSettings settings;
//CefSettingsTraits::init(&settings);
//settings.multi_threaded_message_loop = true; //使用主程序消息循环
//settings.single_process = false; //使用多进程模式
//settings.ignore_certificate_errors = true; //忽略掉ssl证书验证错误
//settings.command_line_args_disabled = true;
//CefString(&settings.locale).FromASCII("zh-CN");
// Execute the sub-process logic, if any. This will either return immediately for the browser
// process or block until the sub-process should exit.
int exit_code = CefExecuteProcess(main_args, app.get(), sandbox_info);
if (exit_code >= 0) {
// The sub-process terminated, exit now.
return exit_code;
}
// Initialize CEF in the main process.
CefInitialize(main_args, settings, app.get(), sandbox_info);
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
// Create the child windows used for navigation
RECT rect;
GetWindowRect(g_hWnd, &rect);
CefRefPtr<MyCefHandler> client(new MyCefHandler());
// Information about the window that will be created including parenting, size, etc.
// The definition of this structure is platform-specific.
CefWindowInfo info;
// Customize this structure to control browser behavior.
CefBrowserSettings browser_settings;
// Initialize window info to the defaults for a child window
info.SetAsChild(g_hWnd, rect);
// Creat the new child browser window asynchronously
int nRet = CefBrowserHost::CreateBrowser(info,
client.get(),
"http://www.baidu.com", browser_settings, nullptr);
int result = 0;
// settings.multi_threaded_message_loop 是 true 还是 false 的处理 会影响到
// 关闭 CefShutdown() 函数,如果是 settings.multi_threaded_message_loop = true
// 单处理不是 CefRunMessageLoop() , 在准备退出并调用 CefShutdown 就会崩溃。
if (!settings.multi_threaded_message_loop) {
// Run the CEF message loop. This function will block until the application
// recieves a WM_QUIT message.
CefRunMessageLoop();
}
else {
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
result = static_cast<int>(msg.wParam);
}
// Shut down CEF.
// cefShutdown() 崩溃下面会提到。
CefShutdown();
return result;
//return (int) msg.wParam;
}
客户端编好后,需要把 cef_binary_3.3396.1785.ga27bbfa_windows64 解压后根目录下 Debug 或 Release 目录下 libGLESv2.dll , libEGL.dll, libcef.dll, icudt.dll 和 locales 目录都拷到客户端程序的根目录, 否则可能导致了程序在执行初始化 CefInitialize(settings, app);
的时候崩溃。
推荐文章:Cef功能开发经验总结