这是我开发Cef功能时对踩过的坑,进行的总结,话说Cef坑真的不少。好在踩完后用起来还是挺爽的。最终的代码可以下载网易云信PC Demo点我跳转
资料准备
这是我集成过程中查到的一些资料,包括了Cef开发的各方面资料
在调试Cef时需要Cef的pdb和源码:
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消息循环的。
不过NIM项目底层是使用谷歌base库的多线程构架,所以没法直接使用CefRunMessageLoop。(PS:实际上Cef的底层消息循环也是谷歌的base库)
要让NIM的消息循环兼容Cef消息循环,有两种方法。
第一种方法
第一种方法是使用CefDoMessageLoopWork函数代替CefRunMessageLoop来完全消息消息循环。CefDoMessageLoopWork函数的作用是让Cef执行一次消息循环,这个函数不会阻塞线程,所以需要在我们现有的消息循环里的适当情况下主动去调用CefDoMessageLoopWork函数,如果调用的太频繁会很消耗CPU,如果调用频率太低会导致Cef来不及处理内部消息,让Cef界面反映变慢,所以这个函数的调用时机很重要。
因为CefDoMessageLoopWork函数应该在原本的消息循环中调用,而base库的UI线程消息循环是封装好的。这里首先说一下定制base库消息循环的方法。在WinMain入口函数里调用UI消息循环的代码如下:
{
MainThread thread; // 创建主线程
thread.RunOnCurrentThreadWithLoop(nbase::MessageLoop::kUIMessageLoop); // 执行主线程循环
}
在RunOnCurrentThreadWithLoop方法的第二个参数里可以指定一个消息分派器指针dispatcher,dispatcher继承自nbase::Dispatcher。base库中的UI消息循环代码如下:
PreProcessMessage(msg);
if (state_->dispatcher)
{
if (!state_->dispatcher->Dispatch(msg))
state_->should_quit = true;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
PostProcessMessage(msg);
如果我们指定了RunOnCurrentThreadWithLoop方法的第二个参数,就不会调用原本的消息循环了,所以可以在这个dispatcher里定制消息循环。我实现CefMessageLoopDispatcher类并重写Dispatch接口。
BOOL CefMessageLoopDispatcher::IsIdleMessage(const MSG* pMsg)
{
switch (pMsg->message)
{
case WM_MOUSEMOVE:
case WM_NCMOUSEMOVE:
case WM_PAINT:
return FALSE;
}
return TRUE;
}
bool CefMessageLoopDispatcher::Dispatch(const MSG &msg)
{
static BOOL bDoIdle = TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
if (IsIdleMessage(&msg))
{
bDoIdle = TRUE;
}
while (bDoIdle && !::PeekMessage(const_cast(&