CEF框架理解及应用

news/2025/1/15 19:37:41/文章来源:https://www.cnblogs.com/quenwaz/p/18401129

文章CEF开发环境搭建提到了如何配置cef windows开发环境, 接下来梳理开发cef过程中对其框架的理解。

什么是CEF

Chromium 嵌入式框架 (CEF, Chromium Embedded Framework) 是一个用于将基于 Chromium 的浏览器嵌入到其他应用程序中的简单框架。

CEF 是一个 BSD 许可的开源项目, 与Google Chrome 应用程序开发的 Chromium 项目不同,CEF 专注于促进第三方应用程序中的嵌入式浏览器用例。

CEF 至今存在三个版本, 而仍在开发受支持的版本是CEF3

  • CEF1, 单进程实现, 使用chromium webkit API(已停产)
  • CEF2, 基于Chromium浏览器构建的多进程实现(已停产)
  • CEF3, 使用 Chromium Content API(称为"Alloy runtime")或完整的 Chrome UI(称为"Chrome runtime")实现多进程

依赖项

CEF 项目依赖几个由第三方维护的项目, 分别是:

  • Chromium: 提供通用的功能, 比如网络栈、多线程、消息循环、日志记录及进程控制。 实现Blink与V8通讯的代码。
  • Blink: Chromium 使用的渲染实现。提供 DOM 解析、布局、事件处理、渲染和 HTML5 JavaScript API。一些 HTML5 实现分布在 Blink 和 Chromium 代码库之间。
  • V8:Javascript引擎
  • Skia: 用于渲染非加速内容的 2D 图形库。
  • Angle:适用于 Windows 的 3D 图形转换层,可将 GLES 调用转换为 DirectX。

整体架构

CEF3 使用多个进程来保护整个应用程序免受渲染引擎或其他组件中的错误和故障的影响。它还限制每个渲染引擎进程对其他进程和系统其余部分的访问。在某些方面,这为网页浏览带来了内存保护和访问控制为操作系统带来的好处。

通常主应用程序进程称为"浏览器"进程。同时创建渲染器、插件、GPU 等子进程。主进程主要处理窗口创建、UI 和网络访问,并且大多数应用程序逻辑将在浏览器进程中运行。

Blink 渲染和 JavaScript 执行发生在单独的"render"进程中。一些应用程序逻辑(例如 JavaScript 绑定和 DOM 访问)也将在渲染进程中运行。默认进程模型将为每个唯一来源(scheme + domain)生成一个新的渲染进程。其他进程将根据需要生成。

在 Windows 和 Linux 上,主进程和子进程可以使用相同的可执行文件。在 OS X 上,您需要为子进程创建单独的可执行文件和应用程序包。

基础用法

提供一个入口点函数来初始化 CEF 并运行子进程可执行逻辑或 CEF 消息循环。

构建嵌入cef程序基本流程

当主进程与子进程共用可执行程序文件的情形:

int main(int argc, char* argv[]) {// Structure for passing command-line arguments.// The definition of this structure is platform-specific.CefMainArgs main_args(argc, argv);// Implementation of the CefApp interface.CefRefPtr<MyApp> app(new MyApp);// 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());if (exit_code >= 0) {// The sub-process terminated, exit now.return exit_code;}// Populate this structure to customize CEF behavior.CefSettings settings;// Initialize CEF in the main process.CefInitialize(main_args, settings, app.get());// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.CefRunMessageLoop();// Shut down CEF.CefShutdown();return 0;
}
  1. 在入口函数处, 进行一些基础初始化工作。 然后调用CefExecuteProcess启动子进程。 而调用CefExecuteProcess必须传入CefMainArgs参数。 可通过执行代码CefMainArgs main_args(hInstance)CefMainArgs main_args(argc, argv)(后者适用于Linux或MacOS)构建此参数。 CefExecuteProcess通过不同的命令行参数启动不同的子进程。而CefApp参数对于不同的子进程, 可以编写不同的CefApp的继承类进行处理。
  2. CefExecuteProcess之后的代码只会被浏览器进程执行。 渲染进程、插件进程、GPU进程都将不会执行后面的逻辑。
  3. 调用CefInitialize对浏览器进程进行初始化, 此过程会创建第一个浏览器实例, CefBrowserProcessHandler::OnContextInitialized被调用后,初始化完成。
  4. 在适当的时机,调用CreateBrower()CreateBrowserSync()并传入一个CefClient实例,创建浏览器窗口。
  5. 调用CefRunMessageLoop()CefDoMessageLoopWork()启动消息循环
  6. 在进程退出之前调用CefShutdown()对Cef进行一些清理工作。

当主进程与子进程为独立的可执行文件时:

int main(int argc, char* argv[]) {// Load the CEF framework library at runtime instead of linking directly// as required by the macOS sandbox implementation.CefScopedLibraryLoader library_loader;if (!library_loader.LoadInMain())return 1;// Structure for passing command-line arguments.// The definition of this structure is platform-specific.CefMainArgs main_args(argc, argv);// Implementation of the CefApp interface.CefRefPtr<MyApp> app(new MyApp);// Populate this structure to customize CEF behavior.CefSettings settings;// Specify the path for the sub-process executable.CefString(&settings.browser_subprocess_path).FromASCII(“/path/to/subprocess”);// Initialize CEF in the main process.CefInitialize(main_args, settings, app.get());// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.CefRunMessageLoop();// Shut down CEF.CefShutdown();return 0;
}

与单个可执行程序相比, 多个可执行程序的差异就是在主进程中无需执行CefExecuteProcess, 仅需通过CefSettingsbrowser_subprocess_path参数设置子进程可执行文件路径即可。 其他与单个可执行文件无异。

子进程可执行文件的基本逻辑如下:

int main(int argc, char* argv[]) {// Initialize the macOS sandbox for this helper process.CefScopedSandboxContext sandbox_context;if (!sandbox_context.Initialize(argc, argv))return 1;// Load the CEF framework library at runtime instead of linking directly// as required by the macOS sandbox implementation.CefScopedLibraryLoader library_loader;if (!library_loader.LoadInHelper())return 1;// Structure for passing command-line arguments.// The definition of this structure is platform-specific.CefMainArgs main_args(argc, argv);// Implementation of the CefApp interface.CefRefPtr<MyApp> app(new MyApp);// Execute the sub-process logic. This will block until the sub-process should exit.return CefExecuteProcess(main_args, app.get());
}

核心代码就是仅需调用CefExecuteProcess传入对应CefApp的类实例即可。

CEF线程

每个cef进程都是一个多线程程序, 在枚举类型cef_thread_id_t中定义了可能的线程类型。下面仅仅对几个常用的线程进行说明:

  • TID_UI: 该线程是浏览器进程中的主线程。如果使用 CefSettings.multi_threaded_message_loop 值 false 调用 CefInitialize(),则此线程将与主应用程序线程相同。
  • TID_IO: 该线程用于浏览器进程中处理IPC和网络消息。
  • TID_FILE_*: 该线程用于浏览器进程与文件系统交互。阻塞操作只能在此线程或客户端应用程序创建的 CefThread 上执行。
  • TID_RENDERER: 该线程是渲染进程中的主线程。所有 Blink 和 V8 交互都必须在此线程上进行。

消息循环

cef 消息循环处理有三种方式:

  1. 直接使用CefRunMessageLoop, 在windows平台中,该函数会处理win32的消息。 而无需单独对win32消息进一步处理了。
  2. 在Windows平台中, 如果桌面应用已经有了自己的消息循环, 可以在适当的时机调用CefDoMessageLoopWork来处理cef消息。手动调用CefDoMessageLoopWork一定确保其频率, 频率低导致cef性能降低, 高则会消耗无谓的cpu资源。
  3. 如果你的应用中已经有自己的消息循环, 可以通过设置CefSettings.multi_threaded_message_loop参数为true,来确保cef消息循环的执行, 此时cef消息循环将在独立的线程中, 与主应用程序的线程不同。 更明确的说, cef浏览器ui线程将在独立的线程中处理。 如果与cef浏览器ui线程通讯, 需要添加额外的逻辑。 可参考cef提供的示例中MainMessageLoopMultithreadedWin的处理逻辑(通过消息的方式进行同步)

常用类

CefBrowser与CefFrame

CefBrowser 和 CefFrame 对象用于向浏览器发送命令并在回调方法中检索状态信息。每个 CefBrowser 对象将有一个表示顶级框架的主 CefFrame 对象和零个或多个表示子框架的 CefFrame 对象。例如,加载两个 iframe 的浏览器将有三个 CefFrame 对象(顶级框架和两个 iframe)。

例如在主框架中加载url:

browser->GetMainFrame()->LoadURL(some_url);

浏览器后退操作:

browser->GoBack();

CefBrowser 和 CefFrame 对象存在于浏览器进程和渲染进程中。可以通过 CefBrowser::GetHost() 方法在浏览器进程中控制主机行为。例如,可以按如下方式检索窗口浏览器的句柄:

// CefWindowHandle is defined as HWND on Windows, NSView* on MacOS
// and (usually) X11 Window on Linux.
CefWindowHandle window_handle = browser->GetHost()->GetWindowHandle();

还有其他方法可用于历史记录导航、加载字符串和请求、发送编辑命令、检索文本/html 内容等。

CefApp

CefApp 接口提供对特定于进程的回调的访问。重要的回调包括:

  • OnBeforeCommandLineProcessing 提供了以编程方式设置命令行参数的机会。
  • OnRegisterCustomSchemes 提供了注册自定义方案的机会。常见的方案包括http、https、ftp等等
  • GetBrowserProcessHandler 返回特定于浏览器进程的功能的处理程序,继承自CefBrowserProcessHandler, 主要处理OnContextInitialized
  • GetRenderProcessHandler 返回特定于渲染进程的功能的处理程序,继承自CefRenderProcessHandler。这包括与 JavaScript 相关的回调和进程消息。

通常会实现GetBrowserProcessHandlerGetRenderProcessHandler回调。 用于针对浏览器进程与渲染进程的逻辑处理。

CefClient

CefClient 主要用于浏览器进程,添加各种处理回调。单个 CefClient 实例可以在任意数量的浏览器之间共享。重要的回调包括:

  • 处理程序用于处理浏览器生命周期(GetLifeSpanHandler)、上下文菜单、对话框、显示通知、拖动事件、焦点事件、键盘事件等。大多数处理程序都是可选的。通常通过形如Get***Handler的接口返回对应的处理实例, 如GetLifeSpanHandlerGetDisplayHandler
  • OnProcessMessageReceived 在从渲染进程接收到 IPC 消息时调用。

浏览器生命周期

浏览器生命周期从调用 CefBrowserHost::CreateBrowser()CefBrowserHost::CreateBrowserSync() 开始。执行此逻辑的位置可以在 CefBrowserProcessHandler::OnContextInitialized()回调中或特定于平台的消息处理程序,例如 Windows 上的 WM_CREATE中。

CefLifeSpanHandler 类提供了管理浏览器生命周期所需的回调。

后台任务

可以使用 CefPostTask 系列方法在单个进程中的各个线程之间发布任务(完整列表请参阅 include/cef_task.h 头文件)。任务将在目标线程的消息循环上异步执行。

进程间通信 (IPC)

由于 CEF3 在多个进程中运行,因此有必要提供在这些进程之间进行通信的机制。CefBrowser 和 CefFrame 对象存在于浏览器和渲染进程中,这有助于促进此过程。每个 CefBrowser 和 CefFrame 对象还具有与其关联的唯一 ID 值,该值将在进程边界的两侧匹配。

如果想在进程启动时向各个进程传递数据,可以在创建时通过 CefRefPtr extra_info 参数与 CefBrowserHost::CreateBrowser 关联到特定的 CefBrowser 实例。该 extra_info 数据将通过 CefRenderProcessHandler::OnBrowserCreated 回调传递到与该 CefBrowser 关联的每个渲染器进程。

在运行时进行进程间通讯:

可以使用 CefProcessMessage 类在运行时在进程之间传递消息。这些消息与特定的 CefBrowserCefFrame 实例相关联,并使用 CefFrame::SendProcessMessage() 方法发送。进程消息应包含通过 CefProcessMessage::GetArgumentList() 所需的任何状态信息。如下所示, 浏览器进程向渲染进程发送消息:

// Create the message object.
CefRefPtr<CefProcessMessage> msg= CefProcessMessage::Create(“my_message”);// Retrieve the argument list object.
CefRefPtr<CefListValue> args = msg>GetArgumentList();// Populate the argument values.
args->SetString(0, “my string”);
args->SetInt(0, 10);// Send the process message to the main frame in the render process.
// Use PID_BROWSER instead when sending a message to the browser process.
browser->GetMainFrame()->SendProcessMessage(PID_RENDERER, msg);

从浏览器进程发送到渲染进程的消息在回调CefRenderProcessHandler::OnProcessMessageReceived()中接收。从渲染进程发送到浏览器进程的消息在回调CefClient::OnProcessMessageReceived()中接收。

另外一种发送消息的方式是通过CefSharedProcessMessageBuilder构建消息, 通过CefSharedProcessMessageBuilder::build构建一个CefProcessMessage对象。

javascript调用c++

void V8HanderDelegate::OnContextCreated(CefRefPtr<RendererApp> app,CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefV8Context> context) 
{CEF_REQUIRE_RENDERER_THREAD();OutputDebugStringA("V8HanderDelegate::OnContextCreated");CefRefPtr<CefV8Handler> handler = new V8HandlerImpl(this);// Register function handlers with the 'window' object.auto window = context->GetGlobal();window->SetValue(CefString("sayhello"),CefV8Value::CreateFunction(CefString("sayhello"), handler),static_cast<CefV8Value::PropertyAttribute>(V8_PROPERTY_ATTRIBUTE_READONLY | V8_PROPERTY_ATTRIBUTE_DONTENUM |V8_PROPERTY_ATTRIBUTE_DONTDELETE));window->SetValue(CefString("sayhi"),CefV8Value::CreateFunction(CefString("sayhi"), handler),static_cast<CefV8Value::PropertyAttribute>(V8_PROPERTY_ATTRIBUTE_READONLY | V8_PROPERTY_ATTRIBUTE_DONTENUM |V8_PROPERTY_ATTRIBUTE_DONTDELETE));
}

在渲染进程的处理器CefRenderProcessHandler继承类中实现OnContextCreated, 在OnContextCreated中构建一个继承自CefV8Handler的v8处理类对象。然后在js的全局变量windos中添加函数。 这样就可以在js中调用对应函数来触发c++调用。

触发c++调用实在CefV8Handler::Execute回调中。 可以在这个回调中执行c++函数:

    class V8HandlerImpl final : public CefV8Handler {public:explicit V8HandlerImpl(const CefRefPtr<V8HanderDelegate>& delegate): delegate_(delegate) {}V8HandlerImpl(const V8HandlerImpl&) = delete;V8HandlerImpl& operator=(const V8HandlerImpl&) = delete;bool Execute(const CefString& name,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments,CefRefPtr<CefV8Value>& retval,CefString& exception) override {if (name == "sayhello") {// 添加自己的处理, 注意一定return truereturn true;}if (name == "sayhi") {// 添加自己的处理, 注意一定return truereturn true;}return false;}IMPLEMENT_REFCOUNTING(V8HandlerImpl);};

c++调用javascript

c++ 调用javascript相对比较简单, 只需要得到CefFrame对象就可以执行其成员函数ExecuteJavaScript进行js调用。 在浏览器进程及渲染进程中都可以拿到该对象。 如下示例是在浏览器进程中执行js:

browser->GetMainFrame()->ExecuteJavaScript("jsfunction([1,2,3,4,5]);", browser->GetMainFrame()->GetURL(), 0);

参考

  • https://www.chromium.org/developers/design-documents
  • https://bitbucket.org/chromiumembedded/cef/wiki/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/794202.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

格路计数

前言 完全借鉴于 洛谷日报 前提条件 在一个网格图上, 每一次可以从 \((x,y)\) 走到 \((x+1,y-1)\) 或者 \((x+1,y-1)\) 限制是一条直线 \(y=k\) 题外话 我们其实可以发现这和每一次可以从 \((x,y)\) 走到 \((x+1,y)\) 或者 \((x, y+1)\) 限制是一条 \(45\)的斜线 \(y=x+k\) 是等…

9.2-9.8 总结

zhicheng123456做题 因为联考众多,所以说只有不多的做题。主要得知了两个结论:找树剖到根的轻链(动态)的方法和把线段(均匀随机分界点)分为 \(n\) 段的 \(k\) 短值的计算。 联考:https://www.cnblogs.com/british-union/p/liankao.html 还是时常在简单的东西上失败,但是…

第一章预习作业

第一周预习报告 学习内容《WindowsC/C++加密解密实战》第1,2章 第一章概念复习 第二章主要在Linux(Ubuntu,openEuler)上把软件更新到最新版(3.0版本以上),如果默认不是,参考下面脚本。 AI对学习内容的总结 要求 让AI(kimi,元宝等)阅读学习内容并进行总结,教材内容可使…

9月第一周漏洞学习

蜂信物联(FastBee)物联网平台download存在任意文件下载漏洞 漏洞描述 FastBee是一款开源物联网平台,致力于为全球开发者提供稳定、搞笑的物联网解决方案。FastBee在download接口中存在任意文件下载漏洞,可能导致敏感信息泄露、数据盗窃及其他安全风险,从而对系统和用户造成…

AtCoder Beginner Contest 252 A~G 题解

前言这是我第一次写7题(A~G)的ABC题解,若有写得不好或者不到位的地方请多多指教,我将万分感激,感谢大家的支持!A - ASCII code 题目大意 给定正整数\(N\),输出ASCII码是\(N\)的字母。 \(97\le N\le 122\) 输入格式 \(N\) 输出格式 输出ASCII码是\(N\)的字母。 分析 注意…

苯乙烯

周线 日线: 60分钟: 短线看能否走出下跌五浪吧:观望

AtCoder Beginner Contest 205 A~E 题解

A - kcal 题目大意 我们有一种每\(100\)毫升含有\(A\)千卡热量的饮料。\(B\)毫升的这种饮料含有多少千卡热量? \(0\le A, B\le 1000\) 输入格式 \(A~B\) 输出格式 输出\(B\)毫升这种饮料包含的的千卡数。最大允许浮点数精度误差\(10^{-6}\)。 样例\(A\) \(B\) 输出\(45\) \(20…

AtCoder Beginner Contest 196 A~E 题解

A - Difference Max 题目大意 给定四个整数\(a,b,c\)和\(d\)。 我们要选择两个整数\(x\)和\(y\)(\(a\le x\le b\);\(c\le y\le d\))。输出最大的\(x-y\)。 \(-100\le a\le b\le 100\) \(-100\le c\le d\le 100\) 输入格式 \(a~~b\) \(c~~d\) 输出格式 输出最大的\(x-y\)。 样…

AtCoder Beginner Contest 173 A~D 题解

A - Payment 题目大意 如果使用价值\(1000\)元的纸币(假设有)支付\(N\)元,服务员会找多少钱? \(1\le N\le 10000\) 输入格式 \(N\) 输出格式 一行,即服务员找的钱数。 样例输入 输出1900 1003000 0分析 特判: 如果\(N\)除以\(1000\)能整除,那么不需要找钱,输出\(0\); …

AtCoder Beginner Contest 188 A~D 题解

A - Three-Point Shot 题目大意 有两个球队,分别得到\(X\)分和\(Y\)分,问得分较少的球队能否在获得三分后超越对方。 \(0\le X,Y\le 100\) \(X \ne Y\) \(X\)和\(Y\)都是整数。 输入格式 \(X~Y\) 输出格式 如果能,输出Yes;否则,输出No。 样例X Y 输出3 5 Yes分析 这个不用…

Eclipse安装包下载慢解决方法

最近开始学习Java,使用经典的Eclipse IDE,结果发现下载太慢……问题分析Eclipse的下载依赖于其他镜像,(在我这里)默认为朝鲜的镜像(可能在不同电脑中默认不同):点击Select Another Mirror:选择中国的镜像:

程序无法启动,因为您的计算机缺少msvcr71.dll。

背景 打开CrystalTile2这个软件出现此提示。 解决 有很多解决办法,最简单粗暴也是见效最快的就是直接从网上下载dll文件放到对应位置。 下载 https://msvcr71.dll-box.com/zh/自己存了一份,以免网址失效(虽然按道理来说一般不会失效)。 https://www.123pan.com/s/EhW3jv-IW…