APP下载

CEF应用与交互研究

2021-04-20付莎

电子技术与软件工程 2021年3期
关键词:调用浏览器进程

付莎

(云南省少数民族语文指导工作委员会办公室 云南省昆明市 650021)

传统软件开发C/S、B/S 模式各有利弊。C/S 模式可控性、定制性高,但技术门槛高、开发周期长、综合成本大。B/S 模式技术门槛稍低,开发周期相对较短,构建UI 交互高效快捷。但B/S 模式也因受限浏览器限制,在一些需要和操作系统、硬件设备等底层进行数据交互时近乎无能为力。复杂UI 交互如使用C/S 模式则需要花费大量时间精力且未必能达到B/S 模式效果。

CEF(Chromium Embedded Framework)是一套基于Google Chromium 的BSD 协议开源跨平台项目。通过CEF 提供的接口,第三方应用程序可方便、高效的将Google Chromium 集成进自己的项目[3]。

通过CEF 的嵌入使用,应用程序可使用现代化的Web 技术快速、高效的构建应用程序UI 交互。而少部分其他需要和操作系统、第三方API 或硬件设备等底层进行交互的功能模块又可全部交由原生软件完成。并通过CEF 相关接口和Web UI 进行数据交互。

使用CEF 可使应用系统构建为一种混合模式。大量的UI 交互使用Web 技术完成。而不可以使用Web 技术完成的或使用Web 构建较为复杂的以及使用Web 效率低下的部分则可使用原生C++编写完成。可充分最大化C/S 和B/S 模式的各自优势。使软件系统开发效率高效、而成本低廉。

最近几年随着Google Chromium 项目和计算机硬件的不断发展,混合模式的开发优势逐渐凸显。获得了大量行业龙头企业的认可与应用。例如腾讯QQ、微信PC 版、网易云音乐、阿里旺旺、阿里钉钉等都使用了CEF 技术或基于CEF 的深度订制技术。

1 CEF简介

CEF 是一套基于Google Chromium[6]项目的开源嵌入式框架。随着Google Chromium 项目的推进,其一开始使用的WebKit 内核被新的Blink 内核取代[1],并推出了全新的Chromium content API。新版增强了对HTML5 和GPU 硬件加速的支持,机制更现代化,极大改进了浏览器性能和稳定性。但随之而来的是原有底层结构大幅改动,因此CEF 也随content API 推出了全新版本:CEF3。原版本改名为CEF1。因此现今一般CEF 特指CEF3 版本。本文所述也特指CEF3 版本[3]。

Google Chromium 采用C++语言编写。由于功能强大与技术先进,代码量极其庞大和复杂,扩展及二次开发极为困难[5]。直接使用Chromium 源码进行集成使用极为复杂,学习与使用成本高昂,一般开发人员掌握、使用都非常困难[4]。CEF 使用C++语言对Chromium 公共接口进行了重新封装。对大量的 Chromium 接口进行了默认实现,隔离了Chromium 极为庞大的源码与具体实现。提供了新的易于使用的对外接口。第三方嵌入时只需使用CEF 接口提供的默认实现,并通过少量的代码编写即可将 Chromium 嵌入第三方应用使用。

2 CEF构架与主要对外接口

CEF 是多进程模型。在CEF 构架中定义了Broswer 和Render两种不同类型的进程。Browser 进程负责UI 部分的窗口管理、界面绘制和相关网络传输。Render 进程负责Blink 内核的渲染和JavaScript 执行。嵌入使用时主应用程序的一些应用逻辑例如:JavaScript 绑定、DOM 节点的访问等也在Render 进行中执行。默认的进程模型中,会为每个页面创建一个新的Render 进程。进程之间通过IPC 进行通信。Browser 和Render 进程可以通过发送异步消息进行双向通信[2]。

CEF 提供了libcef_dll_wrapper 工程导出了相关C++接口。使用时只需使用此包装接口即可快速集成CEF 到第三方主应用程序,从而隔绝了Chromium 的复杂接口。以下CEF 主要C++接口均来自libcef_dll_wrapper 工程包的导出。

CefBrowser 对象代表当前的浏览器窗口。通过CefBrowser 对象可以获取当前Browser 的各项信息,并对Browser 进行特定操作。例如操作Browser 打开页面、回退、强制刷新当前页面等。

CefFrame 对象代表Broswer 下的Frames。每个CefBrowser 对象包含一个主CefFrame 对象,主CefFrame 对象代表Web 页面的顶层frame。Browser 对象下可以包含零个或多个的CefFrame 对象,分别代表不同的子Frame。获取Frame 对象后可对Frame 进行特定的操作。例如当前Frame 下的源码获取、DOM 获取、Frame 内容拷贝以及当前Frame 执行JavaScript 语句等。

CefClient 接口提供访问浏览器实例的回调接口。浏览器实例的相关各种控制:例如自定义处理浏览器的生命周期、右键菜单、下载处理、对话框、通知显示、拖曳事件、焦点事件、键盘事件等,都必须通过CefClient 接口相应方法指定处理handler。一个CefClient 对象实现可以在任意数量的Browser 进程中共享。

CefBrowserProcessHandler 接口对应Browser 进程的回调。可用于在Browser 创建时进行一些特定的初始化。

CefRenderProcessHandler 接口对应Render 进程的回调。自定义的一些应用逻辑:例如JavaScript 绑定、扩展等需要在此接口实现。

CefApp 接口提供访问进程相关的回调。包括代码化设置启动参数、自定义请求头部、设置Browser 和Render 进程处理Handler。一些特殊的设置,例如开始摄像头支持、允许使用Flash、关闭同源策略、允许访问本机文件等都可在CefApp 提供的接口中设置。初始化CEF 函数CefInitialize 需提供此接口的实例。必须实现。

CefLifeSpanHandler 接口提供管理Browser 生命周期回调。

CefV8Handler 用于实现自注册的JS 函数处理逻辑。当我们在CefRenderProcessHandler 接口上注册了自定义函数或对象,在Web端执行相应的JS 代码时,将会调用本接口的Execute 方法。

CefSettings 对象用于定义全局的CEF 配置项。例如是否使用Cookie 缓存、日志输出、定义当前语言、远程调试端口等。

CefString CEF 由于跨平台和不同运行时编译等原因提供了统一的字符串管理,提供统一的内存堆管理、支持UTF8,UTF16 等字符串类型,为CEF 字符串定义了自己的数据结构。在使用CEF 的过程中所有使用字符串的地方都需要使用CefString 结构。

CefInitialize 函数用于在主应用程序中初始化CEF Broswer 进程。

CefShutdown 函数用于在主应用程序中关闭使CEF 停止工作。

3 宿主应用程序结构

使用CEF 时,宿主应用程序据结构应按下述步骤构建才能使CEF 正常工作,主要有以下四个步骤[2]:

(1)提供CefApp 的实现,用于处理CEF 所需的进程相关的回调。

定义CCefClientApp 类。继承于CefApp、CefBrowserProcess Handler 以及CefRenderProcessHandler。实现OnBeforeCommandLine Processing、GetBrowserProcessHandler 与GetRenderProcessHandler三个主要回调。主要用于参数设置。定义Browser 与Render 进程回调处理。

(2)提供CefClient 的实现,用于处理CEF 所需的Browser 实例相关的回调。

定义CCefClientHandler 类。继承于CefClient 与CefLifeSpan Handler。实现GetDisplayHandler、GetLifeSpanHandler、GetLoad Handler、OnProcessMessageReceived 以及OnAfterCreated、DoClose、OnBeforeClose。定义Brower 实例的各相关回调。

(3)在CefClient 实现中使用CEF 提供的CefBrowserHost::CreateBrowser()函数创建一个Browser 实例。

在CCefClientHandler 类中定义CreateBrowser 函数。使用静态函数CefBrowserHost::CreateBrowser()创建Browser 实例。当宿主应用程序窗口创建时,调用CreateBrowser 函数创建Browser 实例窗口。

(4)使用CefInitialize 初始化CEF。

在宿主应用程序入口处,使用CefInitialize 函数,传入上述定义的CCefClientApp 对象作为启动参数启动CEF。

在实现上述四个步骤的相关定义后,即可在宿主应用程序中嵌入CEF。由于使用了CEF 的预制默认实现,上述大部分实现函数只需直接return this;即可启动CEF。实现了方便、简单的嵌入使用。

4 CEF下C++与JavaScript 相互交互

CEF 的嵌入使用最为关键的一项技术应用即为构建的Web 页面怎么样和原生C++程序相互交互。只有C++与JS 语言之间能实现数据相互交互、协助运行,本文所述的混合模式、CEF 的嵌入使用才能发挥最大的作用。

C++调用JavaScript:

C++调用执行JavaScript 相对简单。通过CefBrowser 获取包含的CefFrame 对象,执行CefFrame 对象方法ExecuteJavaScript()函数即可实现C++调用JavaScript。该函数在Browser 与Render 进程均可调用。需要注意的是ExecuteJavaScript 不支持JS 处理结果返回[7]。

JavaScript 调用C++:

(1)CEF 窗口绑定。允许C++将变量、对象或函数附加到一个Frame 的window 对象上,供JavaScript 端调用。窗口绑定需在CefRenderProcessHandler::OnContextCreated() 方法中实现并使用CefV8Value::SetValue 函数添加到上下文中,即上述CCefClientApp类中实现OnContextCreated 方法。可针对不同的Frame 设置同一个对象不同的值。

(2)CEF 扩展。与窗口绑定类似,区别是扩展将变量、对象或函数附加到特定对象上,非窗口绑定的window 对象。扩展将加载到所有Frame 对象上,且不能修改其值。扩展需在Ce fRenderProcessHandler::OnWebKitInitialized() 方法中实现,即上述CCefClientApp 类中实现OnWebKitInitialized 方法,并使用CefRegisterExtension 函数注册。

当JS 需要调用C++函数代码时,一般处理方式为:首先将函数在CefV8Handler::Execute()方法中实现,然后使用以上两种方式之一注册对应函数。当JS 调用相应函数时,将会触发CefV8Handler::Execute()方法[8]。

5 Windows下CEF应用实例

本实例将演示使用CEF 嵌入式浏览器技术支持使用第三方厂家提供的二代身份证读卡器SDK 读取居民身份证信息。通过Web UI 上JS 调用宿主C++代码操作身份证读卡器获取相应信息。通过本实例可充分展示CEF 下JS 与原生C++代码的相互交互过程。

本实例使用Windows 下Visual Studio2015 版本构架MFC Dialog 应用项目,使用CEF3 3325Chromium 65 版本构建。将展示两种方式:同步与异步JS 调用原生C++代码。本示例主要展示CEF 使用中最核心的JS 与C++交互部分,其他部分如宿主应用程序构数据结构、类定义等不再展示,只列出核心代码。将采用最小化构建。

MFC 工程中初始化CEF,并创建Browser 窗口进程。

(1)App::InitInstance()函数CEF 初始化。

(2)Dlg::OnInitDialog()函数中初始化Browser 窗口进程。

(3)Dlg::OnClose()函数中关闭Browser 进行,并销毁CEF

(4)使用C++封装读卡器SDK 相关函数,创建读取身份证信息函数ReadIDCardInfo(wstring &info);

5.1 同步版本

JS 端调用C++函数,并在Render 进程处理返回。

(1)派生MyCefApp、MyCefClient 与MyCefV8Handler 类,实现CEF CefApp、CefRenderProcessHandler、CefClient、CefLife SpanHandler、CefV8Handler 等接口。

(2)实现CefRenderProcessHandler::OnContextCreated(),使用CEF Window 绑定,建并绑定JS 函数ReadIDCard 至浏览器window对象。

(3)实现CefV8Handler::Execute()。当Web 端JS 代码中调用ReadIDCard 函数时,CEF 将会触发Execute()。在Execute()中将调C++原生ReadIDCardInfo 函数获取居民身份证信息并返回给JS。

详细步骤如下:

(1)定义MyCefApp 类,实现CefRenderProcessHandler 接口中的OnContextCreated。并在OnContextCreated 函数中定义身份证信息读取函数ReadIDCard 供Web 端JS 调用。

(2)定义MyCefClient 类,继承CefClient 与CefLifeSpan Handler。CefClient 提供访问Browser 实例的回调接口。CefLifeSpan Handler 是与之相关的Browser 实例相关生存周期回调接口,管理Brower 生存周期。

(3)定义MyCefV8Handler 类,继承CefV8Handler。CefV8 Handler::Execute 提供JS 窗口绑定和扩展的函数回调。实现Execute函数,当Web 端JS 调用ReadIDCard 函数时将回调此函数。

5.2 异步调用

CEF 提供了一个通用的消息路由实现CefMessageRouter,用于在Render 进程中执行的JS 和在Brower 进程中执行的C++之间传递异步消息。使用此消息路由即可实现我们所希望的JS 异步调用。

(1)建立全局CefMessageRouterConfig 类实例。并在CEF 初始化时定义CefMessageRouterConfig 中定义在Web 端JS 需调用函数。

CefMessageRouterConfig g_messageRouterConfig;

messageRouterConfig.js_query_function=“cefQuery”;

messageRouterConfig.js_cancel_function=“cefQueryCancel”

(2)在Render 进程处理类MyCefApp 中添加CefMessage RouterRendererSide 成员变量,实现Render 一侧消息路由。

m_renderer_side_router=CefMessageRouterRendererSide::Create(g_messageRouterConfig);

(3)在MyCefApp 中分别实现并调用CefMessageRouter RendererSide 预留的OnContextCreated、OnProcessMessageReceived与OnProcessMessageReceived 三个同名接口,使用默认实现即可。

(4)在Browser 进程处理类MyCefClient 中添加CefMessage RouterBrowserSide 成员变量,用于实现Browser 一侧消息路由。

m_browser_side_router=CefMessageRouterBrowserSide::Create(g_messageRouterConfig);

(5)在MyCefClient 中分别实现并调用CefMessageRouter BrowserSide 预留 的OnProcessMessageReceived、OnBeforeClose、OnBeforeBrowse 与OnRenderProcessTerminated 四个同名接口,使用默认实现即可。

(6)实现JSHandler 类,继承自CefMessageRouterBrowserSide::Handler 接口,实现OnQuery 与OnQueryCanceled 函数,用于处理JS 端调用C++而被传递过来的消息。

(7)在MyCefClient 类中调用CefMessageRouterBrowserSide成员变量AddHandler 函数,将上述(6)步骤实现的接口实例添加到消息路由中。

JSHandler m_jsHandler;

...

m_browser_side_router->AddHandler(&m_jsHandler,true);

通过上述步骤,当在Web 端调用JS 函数window.cefQuer(‘ReadIDCard’,...)时,Rander 进程将首先捕获调动,并通过消息路由路由至Browser 进程一侧,最终将调用交由CefMessageRouter BrowserSide::Handler 接口实现二代身份证信息的读取功能,并在完成后异步将数据传递给Web 端。

6 总结

通过上述两例同步与异步应用实例,展示了嵌入式使用CEF时Web 端JS 与宿主端C++的相互调用与数据传递过程。通过实例可见CEF为我们隔离了大量的Chromium接口初始化以及实现过程,并提供了丰富的默认实现,第三方应用程序只需定义少量关键业务核心代码即可将CEF 嵌入到第三方应用程序中使用。为我们开发混合应用实例提供了极大的便利。

猜你喜欢

调用浏览器进程
核电项目物项调用管理的应用研究
债券市场对外开放的进程与展望
反浏览器指纹追踪
LabWindows/CVI下基于ActiveX技术的Excel调用
基于系统调用的恶意软件检测技术研究
环球浏览器
社会进程中的新闻学探寻
我国高等教育改革进程与反思
Linux僵死进程的产生与避免
利用RFC技术实现SAP系统接口通信