Cef功能开发经验总结.docx

上传人:b****5 文档编号:6238177 上传时间:2023-01-04 格式:DOCX 页数:14 大小:25.13KB
下载 相关 举报
Cef功能开发经验总结.docx_第1页
第1页 / 共14页
Cef功能开发经验总结.docx_第2页
第2页 / 共14页
Cef功能开发经验总结.docx_第3页
第3页 / 共14页
Cef功能开发经验总结.docx_第4页
第4页 / 共14页
Cef功能开发经验总结.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

Cef功能开发经验总结.docx

《Cef功能开发经验总结.docx》由会员分享,可在线阅读,更多相关《Cef功能开发经验总结.docx(14页珍藏版)》请在冰豆网上搜索。

Cef功能开发经验总结.docx

Cef功能开发经验总结

Cef功能开发经验总结

Cef功能开发经验总结2017-04-1412:

54

2072人阅读

评论(0)

收藏

举报分类:

Windows(110)C/C++(105)javascript/vbs/lua/python(39)http/ftp/IE/Chrome/Firefox(43)网络(66)WTL/MFC(3)目录(?

)[+]

转载请说明原出处,谢谢~~:

这是我开发Cef功能时对踩过的坑,进行的总结,话说Cef坑真的不少。

好在踩完后用起来还是挺爽的。

最终的代码可以下载网易云信PCDemo点我跳转

资料准备

这是我集成过程中查到的一些资料,包括了Cef开发的各方面资料

在调试Cef时需要Cef的pdb和源码:

Cef及pdb文件下载地址,其中的CefClient包含了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消息循环的。

不过NIM项目底层是使用谷歌base库的多线程构架,所以没法直接使用CefRunMessageLoop。

(PS:

实际上Cef的底层消息循环也是谷歌的base库)

要让NIM的消息循环兼容Cef消息循环,有两种方法。

第一种方法

第一种方法是使用CefDoMessageLoopWork函数代替CefRunMessageLoop来完全消息消息循环。

CefDoMessageLoopWork函数的作用是让Cef执行一次消息循环,这个函数不会阻塞线程,所以需要在我们现有的消息循环里的适当情况下主动去调用CefDoMessageLoopWork函数,如果调用的太频繁会很消耗CPU,如果调用频率太低会导致Cef来不及处理内部消息,让Cef界面反映变慢,所以这个函数的调用时机很重要。

因为CefDoMessageLoopWork函数应该在原本的消息循环中调用,而base库的UI线程消息循环是封装好的。

这里首先说一下定制base库消息循环的方法。

在WinMain入口函数里调用UI消息循环的代码如下:

{

MainThreadthread;//创建主线程

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接口。

BOOLCefMessageLoopDispatcher:

:

IsIdleMessage(constMSG*pMsg)

{

switch(pMsg->message)

{

caseWM_MOUSEMOVE:

caseWM_NCMOUSEMOVE:

caseWM_PAINT:

returnFALSE;

}

returnTRUE;

}

boolCefMessageLoopDispatcher:

:

Dispatch(constMSG&msg)

{

staticBOOLbDoIdle=TRUE;

TranslateMessage(&msg);

DispatchMessage(&msg);

if(IsIdleMessage(&msg))

{

bDoIdle=TRUE;

}

while(bDoIdle&&!

:

:

PeekMessage(const_cast<MSG*>(&msg),NULL,0,0,PM_NOREMOVE))

{

CefDoMessageLoopWork();

bDoIdle=FALSE;

}

returntrue;

}

 

在定制消息循环里,如果判断当前消息队列为空并且刚才处理的消息不会指定的几个消息,就去调用CefDoMessageLoopWork函数。

WM_PAINT、WM_MOUSEMOVE等消息的处理比较复杂,所以不在这里调用CefDoMessageLoopWork函数

这个方法基本可以使用,但是还存在一些问题,这里CefDoMessageLoopWork函数的调用机制还不够好,Cef界面不够顺畅,而且因为Cef与项目base库的冲突,导致在程序结束时有些问题。

这个方法有待优化

第二种方法

CefSettings结构体的multi_threaded_message_loop(多线程消息循环)为false时,可以调用CefRunMessageLoop或者CefDoMessageLoopWork函数来触发Cef消息循环,这时浏览器进程的UI线程就是调用CefRunMessageLoop或者CefDoMessageLoopWork函数的线程。

如果CefSettings结构体的multi_threaded_message_loop为true时。

浏览器进程的UI线程是另外的线程。

设置multi_threaded_message_loop为true则使用多线程消息循环。

通过对比CefDemo的多线程消息循环代码,可以确定在NIM项目中直接开启多线程消息循环,不需要修改现有消息循环代码就可以正常使用Cef了。

不过需要注意的是,使用多线程消息循环后某些函数就无法使用了,比如CreateBrowserSync,这种函数要求必须在Cef的UI线程调用。

另外,在很多版本的Cef里,如果开启了多线程消息循环,会导致程序在结束时触发中断,这属于Cef的bug,不过在release版本的Cef中没有问题。

应该在项目中使用这个方法。

不过使用了多线程消息循环后,很多Cef对象触发的回调函数,都是在Cef的UI线程而不是我们的UI线程,所以这时操作我们的UI线程就比较麻烦,要注意一些多线程问题,尽量把操作转发到我们的UI线程,不转发的话必须确定所操作的代码不会影响我们的UI线程,切记!

CefClient接口介绍

CefLifeSpanHandler

CefBrowser对象的生命周期事件的回调接口。

OnAfterCreated:

当调用CreateBrowser函数创建浏览器对象后会立马触发这个回调,在这里可以保存浏览器对象的指针DoClose:

当调用CloseBrowser函数后触发这个回调OnBeforeClose:

当浏览器对象即将销毁时会触发这个回调,在这里一定要释放所有对CefBrowser对象的引用,否则会导致程序无法退出。

切记这个坑。

OnBeforePopup:

当单击了网页中会弹出新窗口的链接时,会触发这个回调。

我们的项目里应该禁止新窗口的弹出,而在原控件中跳转链接

CefRenderHandler

要使用离屏渲染功能,就必须要实现这个接口类。

因为项目中使用的duilib库,目前使用分层窗体机制实现异形窗体效果,不支持显示子窗口只能自绘控件,所以没法使用比较简单的子窗口的形式显示Cef浏览器对象。

只能使用离屏渲染方法,离屏渲染的数据会通过CefRenderHandler接口回调。

首先必须要开启CefSettings结构体的windowless_rendering_enabled字段

GetRootScreenRect:

浏览器对象创建后触发的回调,返回最外层窗体在屏幕中的位置GetViewRect:

在浏览器对象初始化后,或者浏览器大小改变时,触发这个回调来获取浏览器对象的位置。

因为浏览器对象会平铺满整个控件,所以这里返回控件的位置。

其中返回的左上位置要准确,否则Cef在处理一些坐标信息时会出错GetScreenPoint:

在这里把传入的坐标值,由客户区坐标转换为屏幕坐标OnCursorChange:

当需要修改鼠标光标时触发这个回调OnPaint:

当浏览器对象有新的渲染数据后,会触发这个回调,包含了脏区和渲染数据。

应该保存这些数据,然后在适当的时候贴到目标窗体上OnPopupShow:

当浏览器中要弹出内部对话框时(比如弹出一个下拉菜单),触发这个回调,通知要显示或者隐藏弹出框OnPopupSize:

当浏览器中要弹出内部对话框时,触发这个回调,通知弹出框的位置和大小

离屏渲染的实现

离屏渲染的效率不如真窗口渲染,如果不是必须要离屏渲染的情况,还是用真窗口比较好。

CefControl控件实现了duilib嵌入Cef浏览器对象。

在控件初始化触发Init函数时,调用CreateBrowser函数创建CefBrowser对象,这会触发CefRenderHandler:

:

GetViewRect回调,在这个回调里返回控件的位置。

随后网页第一次渲染时触发CefRenderHandler:

:

OnPaint回调。

绘制渲染数据的流程

当网页渲染数据改变、或者我们主动调用了CefBrowser对象的Invalidate方法时,会触发CefRenderHandler:

:

OnPaint回调。

我写了一个内存位图缓冲类MemoryDC来保存Cef传来的渲染数据,在CefControl控件中dc_cef_成员变量负责保存渲染数据。

在CefRenderHandler:

:

OnPaint回调里,根据渲染数据初始化dc_cef_,然后根据脏区把渲染数据拷贝到dc_cef_中。

数据拷贝完之后,调用CefControl控件的Invalidate方法通知窗体重绘控件在CefControl控件的Paint方法里,把dc_cef_的位图数据拷贝到duilib传入的HDC中

CefControl对事件的处理

修改CefBrowser尺寸

在离屏渲染模式下,无法直接修改CefBrowser对象的尺寸。

CefControl控件重写SetPos函数,在这里调用CefBrowser对象的WasResized接口通知CefBrowser对象需要改变尺寸,之后GetViewRect接口会被触发,这时依然是返回CefControl控件的位置就可以了。

之后OnPaint接口会被自动触发,按照前一节的流程进行一次渲染数据的刷新

设置CefBrowser隐藏(显示)

CefControl控件重写SetVisible函数和SetInternVisible函数,在这里调用CefBrowser对象的WasHidden接口通知CefBrowser对象隐藏或显示

对系统消息的处理

在控件初始化触发Init函数时,调用窗体类的AddMessageFilter函数把自己注册到窗体的消息过滤队列里。

CefControl控件继承IUIMessageFilter接口类并重写MessageHandler函数。

当系统消息进入窗体后会依次调用消息过滤队列指针来过滤消息。

在MessageHandler函数里处理我们感兴趣的消息,其他消息并不过滤处理各种鼠标类消息时,判断如果鼠标不在控件范围内则不处理相关消息。

获取当前鼠标的坐标,因为CefBrowser的坐标值是以自身左上角作为原点的,所以获取的鼠标坐标要减去CefControl控件的左上角坐标值。

其中处理ButtonDown、ButtonUp、MouseMove消息时,不会中断消息继续传递给窗体,这里需要让duilib窗体类处理SetCapture、ReleaseCapture等函数处理键盘消息时,判断当前控件是否获取焦点,只处理有焦点的情况WM_SETCURSOR消息处理,在MessageHandler函数拦截WM_SETCURSOR消息,直接调用窗体类的默认消息处理函数,不让duilib处理这个消息。

CefRenderHandler:

:

OnCursorChange接口会修改鼠标光标并修改窗体的默认光标样式,而duilib处理WM_SETCURSOR消息时会另外修改光标,所以需要拦截

渲染Popup弹出框

浏览器中,弹出框的渲染数据是需要自己额外处理的。

如下拉菜单等弹出框,否则浏览器中不会显示出弹出框当需要显示弹出框时,CefRenderHandler:

:

OnPopupSize接口会传入弹出框的位置和尺寸等数据,在这里把数据保存到rect_popup_成员变量之后会触发CefRenderHandler:

:

OnPaint回调,并且渲染类型会被指定为弹出框类型PET_POPUP。

这时把弹出框的渲染数据保存到MemoryDC类型的dc_cef_popup_成员变量中数据拷贝完之后,调用CefControl控件的Invalidate方法通知窗体重绘控件在CefControl控件的Paint方法里,把dc_cef_popup_的位图数据按照rect_popup_的信息拷贝到duilib传入的HDC中当弹出框消失时,触发CefRenderHandler:

:

OnPopupShow接口,这里重置rect_popup_的信息,并且通知CefBrowser刷新页面

多进程渲染

Cef3支持多进程和单进程渲染,但是单进程渲染不够稳定,只应该在Debug模式下作为调试目的使用。

在Cef3.1916等好几个版本中,调试状态下使用单进程模式,当程序初始化或者退出时,会触发中断。

但是在多进程模式下没有问题。

官方也明确说明不推荐使用单进程模式

CefManager类实现了Cef3的初始化和销毁功能。

初始化函数Initialize里调用的CefExecuteProcess函数会检测当前的进程类型,如果是浏览器进程则函数会直接返回,在其他进程的话这个函数会阻塞直接进程销毁。

ClientApp类继承CefBrowserProcessHandler和CefRenderProcessHandler,可以同时处理浏览器进程和渲染进程的消息。

原本多进程模式中,浏览器进程和渲染进程可以同用一个程序。

但是由于我们的主程序的代码比较复杂,如果让主程序多开进程的话,会占用较多的内存和CPU,同时触发不必要的问题。

所以专门另写了一个cef_render项目来作为渲染子进程

cef_render项目代码比较简单,主要代码都是继承CefRenderProcessHandler接口的CefRenderProcessHandler类。

考虑到代码周全,以后可以在cef_render项目补充一些崩溃Dump处理等代码。

务必要保证主程序的CefRenderProcessHandler接口实现代码与cef_render程序的CefRenderProcessHandler接口实现代一致。

否则单进程和多进程模式下会出现不同的处理结果

在浏览器进程启动时,通过附加参数可以指定渲染子进程的路径

command_line->AppendSwitchWithValue("browser-subprocess-path","render.exe");

 

C++与JS交互

C++调用JS

在browser进程和render进程都可以直接执行JS代码,直接调用CefFrame对象的ExecuteJavaScript方法就可以

JS调用C++

网页中的一些JS回调和对网页的JS扩展,都必须在渲染进程操作。

让JS调用C++的方法有三个,

我们项目里,只需要给JS开放一个函数接口,而且接口并不复杂,所以直接采用JS扩展的方法注册JS回调函数就可以。

在CefRenderProcessHandler:

:

OnWebKitInitialized接口里,注册JS扩展代码

std:

:

stringextensionCode=

"(function(){"

"CefTestWebFunction=function(param){"

"nativefunctionCefTestWebFunction(param);"

"returnCefTestWebFunction(param);"

"};"

"})();";

CefRefPtr<CefV8Handler>handler=newCefJSHandler();

CefRegisterExtension("v8/extern",extensionCode,handler);

 

CefRegisterExtension函数会执行扩展代码。

网上例子都是创建一个全局对象,然后把JS函数和变量绑定到这个对象上。

这里直接申明一个FunExternal的全局函数。

当JS代码中调用FunExternal函数时,会根据native关键字后的函数名,去通知C++代码调用对应的native函数

CefJSHandler类继承CefV8Handler接口并实现Execute方法,在CefRegisterExtension传入CefJSHandle指针,当JS代码需要调用native函数时会,会主动触发CefJSHandler:

:

Execute方法

boolCefJSHandler:

:

Execute(constCefString&name,CefRefPtr<CefV8Value>object,constCefV8ValueList&arguments,CefRefPtr<CefV8Value>&retval,CefString&exception)

{

if(name=="CefTestWebFunction"&&arguments.size()==1)

{

for(auto&it:

arguments)

{

if(it->IsString())

{

CefStringparam=it->GetStringValue();

CefRefPtr<CefBrowser>browser=CefV8Context:

:

GetCurrentContext()->GetBrowser();

CefRefPtr<CefProcessMessage>message=CefProcessMessage:

:

Create(kJsCallbackMessage);

message->GetArgumentList()->SetString(0,name);

message->GetArgumentList()->SetString(1,param);

browser->SendProcessMessage(PID_BROWSER,message);

retval=CefV8Value:

:

CreateBool(true);

}

}

returntrue;

}

//Functiondoesnotexist.

returnfalse;

}

 

在这里可以获取到JS要调用的函数名,以及传入的参数等信息。

获取到这些信息后,把他们包装为CefProcessMessage结构,通过IPC把信息发送到Browser进程进

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 入党转正申请

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1