首页 > 其他分享 >CEF框架理解及应用

CEF框架理解及应用

时间:2024-09-08 21:49:35浏览次数:3  
标签:CEF 浏览器 框架 cef 理解 线程 进程 main

文章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 true
                return true;
            }

            if (name == "sayhi") {
                // 添加自己的处理, 注意一定return true
                return 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);

参考

标签:CEF,浏览器,框架,cef,理解,线程,进程,main
From: https://www.cnblogs.com/quenwaz/p/18401129

相关文章

  • VUE框架基于Vite的Vue3搭建项目的脚手架------VUE框架
    data:redis:lettuce:cluster:refresh:adaptive:trueperiod:2000pool:max-idle:8min-idle:0max-wait:-1msmax-active:8password:abc123......
  • Java并发编程实战 08 | 彻底理解Shutdown Hook
    钩子线程(HookThread)简介在一个Java应用程序即将退出时(比如通过正常执行完成或通过用户关闭应用程序),通常需要进行一些清理操作,例如:释放资源(如文件句柄、网络连接)。关闭数据库连接。保存未完成的数据或状态。我们可以通过钩子线程实现这一点,钩子线程是指在程序结束时,JVM......
  • python+flask计算机毕业设计基于单片机及spring框架的高血压患者居家监测系统(程序+开
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着现代社会生活节奏的加快和人口老龄化趋势的加剧,高血压作为一种常见的慢性疾病,其发病率逐年上升,已成为影响公众健康的重要因素之一。传......
  • Spring MVC: 构建Web应用的强大框架
    SpringMVC:构建现代Web应用的强大框架1.MVC设计模式简介MVC(Model-View-Controller)是一种广泛使用的软件设计模式,它将应用程序的逻辑分为三个相互关联的组件:Model(模型):负责管理数据、业务逻辑和规则。View(视图):负责用户界面的展示,将数据呈现给用户。Con......
  • Go语言中的RPC协议原理解析
    Go语言中的RPC协议原理解析在分布式系统中,不同的服务或组件通常运行在不同的计算机或进程上。为了实现这些服务之间的通信,我们可以使用RPC(RemoteProcedureCall,远程过程调用)协议。RPC允许我们像调用本地函数一样调用远程服务,从而简化了分布式系统中的通信复杂性。本文将详......
  • Java毕业设计源码 - ssm框架网上服装销售系统+jsp+vue+数据库mysql+毕业论文等
    文章目录前言一、毕设成果演示(源代码在文末)二、毕设摘要展示1、开发说明2、需求/流程分析3、系统功能结构三、系统实现展示1、用户功能模块2、管理员功能模块四、毕设内容和源代码获取总结逃逸的卡路里博主介绍:✌️码农一枚|毕设布道师,专注于大学生项目实战开发、......
  • 深入理解指针(一)
    1.内存和地址1.1内存在讲内存和地址之前,我们想有个生活中的案例:假设有一栋宿舍楼,把你放在楼里,楼上有100个房间,但是房间没有编号,你的一个朋友来找你玩儿,如果想找到你,就得安一个房子去找,这样效率很低。但是我们如果根据楼层和楼层的房间情况,应每个房间边上号有的房间号,如果你......
  • 基于python+flask框架的基于web的购物商城系统(开题+程序+论文) 计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,电子商务已成为全球经济的重要组成部分,深刻改变了人们的消费习惯与商业模式。购物商城作为电子商务的核心表现形......
  • 基于python+flask框架的献血管理系统(开题+程序+论文) 计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着社会文明与医疗技术的不断进步,无偿献血已成为保障临床用血安全、缓解血源紧张的重要途径。然而,传统的献血管理模式往往存在信息不对称......
  • 基于python+flask框架的流浪猫救助平台(开题+程序+论文) 计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着城市化进程的加速,流浪猫问题日益凸显,成为城市生态与人文关怀的一大挑战。这些无家可归的小生命,不仅面临着食物短缺、生存环境恶劣的困......