【CEF】《CEF 桌面软件开发实战》笔记-Chapter2-起步阶段

本文介绍了CEF框架的基础知识,包括创建程序的入口函数,初始化CEF框架,以及自定义协议处理本地资源的方法。通过CefApp和CefBrowserProcessHandler,我们可以控制浏览器进程的行为,而CefResourceHandler则用于处理自定义协议的资源请求。
二、起步阶段
  1. 入口程序
  • 在上一讲中我们已经创建了一个名为 main.cpp 的程序文件,这是我们整个程序的入口文件,接下来我们先把这个文件的代码写好:
    #include <windows.h>
    #include "App.h"
    //整个应用的入口函数
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) 
    {
         
         
        CefEnableHighDPISupport();
        CefMainArgs main_args(hInstance);
        CefSettings settings;
        int exit_code = CefExecuteProcess(main_args, nullptr, nullptr);
        if (exit_code >= 0) {
         
         
            return exit_code;
        }
        CefRefPtr<App> app(new App());
        CefInitialize(main_args, settings, app.get(), nullptr);
        CefRunMessageLoop();
        CefShutdown();
        return 0;
    }
    
    • 这是整个应用程序的入口,应用启动后执行的第一段逻辑,我们在这段逻辑中完成了一些初始化工作、为不同的进程分派了不同的处理逻辑、开启了 CEF 的消息循环、在应用退出后释放 CEF 占用的资源等。
  • 操作系统入口函数
    • wWinMain是操作系统指定的应用程序的入口函数。这个函数有 4 个参数,参数前有 In 修饰符表示该参数是必填的输入参数,有 In_opt 修饰符的意思是该参数是可选的输入参数。
    • 这 4 个参数都是由操作系统传递给 wWinMain 方法的:
      • hInstance 是应用程序的实例句柄,也叫模块句柄;
      • hPrevInstance 没有实际意义,是老版本 Windows 系统的历史遗留产物;
      • lpCmdLine 是命令行参数;
      • nCmdShow 表示应用程序的窗口是最小化、最大化还是正常显示。
    • 这个方法返回一个 int 类型的数字,操作系统会直接抛弃这个值。但如果有外部应用唤起你这个应用,那么这个返回值对于它来说可能是有意义的,一般返回 0 表示应用程序正常退出,返回其他值表示应用程序因异常而退出。
  • 应用程序初始化
    • CefEnableHighDPISupport 方法启用高分屏支持,如果不调用这个方法,你的应用程序在一些高分辨率的屏幕下将显示得很模糊。
    • CefMainArgs 是 CEF 对应用程序实例句柄的包装类,用于多进程启动。这里我们使用 hInstance 实例化了这个类的对象,名为 main_args 。
    • CefSettings 是 CEF 的配置对象,类似日志级别、调试端口等都是通过这个对象设置的,这里我们就全部使用默认值。
  • CEF 框架如何启动多个进程
    • CefExecuteProcess 负责启动进程。这里需要详细介绍一下,一般我们启动一个 CEF 应用,你会发现任务管理器里有好几个进程。这些进程中除了主进程是由用户启动的外,其他子进程都是 CEF 框架通过 CefExecuteProcess 方法启动的。
    • 主进程启动后,执行到此方法时,此方法会立即返回 -1 。接下去主进程就会进入 CEF 的消息循环,在适当的时候主进程会以特殊的命令行参数,多次启动你的可执行文件,这样就创建了多个子进程。子进程启动后也会执行到这个 CefExecuteProcess 方法,但子进程执行此方法会被阻塞(不会继续执行后面的逻辑),当子进程执行完它们的任务后,这个方法将返回一个大于等于 0 的值。也就是说子进程在第10行代码处就退出执行了,子进程不会执行 12~18 行代码。
    • CefExecuteProcess 方法的第一个参数就是我们前面介绍的 CefMainArgs 对象,第二个参数可以是不同进程的业务处理对象,也可以为空。基于一切从简的原则,我们这里传递了一个空指针。第三个参数与 Chromium 沙箱有关,此处我们也没有设置。
  • CEF 特有的智能指针
    • CefRefPtr 是一个智能指针类型,它内部有一个引用计数,持有这个指针的使用者越多,计数就越高,使用完了之后,计数也会相应地减少;当没有任何使用者之后,指针就会自动释放它指向的对象。
    • 这与现代 C++ 中的智能指针并没有什么明显区别,CEF 框架大量使用了类似的智能指针,减轻了开发者的心智负担。
  • CEF 框架初始化及消息循环
    • 接下来主进程会执行 CefInitialize 方法,这个方法负责初始化 CEF 的浏览器进程处理类(注意:后文我们提到的浏览器进程与前文提到的主进程属于同一个进程)。这个方法的第一个参数仍然是我们前面创建的 CefMainArgs 对象,第二个参数是 CefSettings 对象,第三个参数就是 App 对象的指针,这里是通过 CefRefPtr 智能指针的 get 方法获取的。第四个参数与沙箱有关,这里我们依然置空。
    • CefRunMessageLoop 负责开启 CEF 消息循环,这个方法会阻塞后面代码的执行,一直到应用程序的某个地方调用了 CefQuitMessageLoop 方法之后,这个方法才会退出执行。(CefQuitMessageLoop 方法会发射应用程序退出的消息,CefRunMessageLoop 方法会收到这个消息,收到这个消息后就退出方法了。)
    • CefShutdown 方法会结束主进程,释放资源。最后应用程序退出。
  1. 浏览器进程入口
  • 操作系统调用完程序的入口函数后,CEF 框架就通过其自身的消息循环机制接管了接下来的执行工作,在上一小节中我们提到了自定义的 App 对象,并且把这个对象传递给了 CEF 的 CefInitialize 方法,CEF 框架收到这个对象之后,会把浏览器进程的一些逻辑交给 App 对象执行,也就是说 App 对象就是我们浏览器进程的入口程序。先看一下它的头文件的代码:
    #pragma once
    #include "include/cef_app.h"
    class App : public CefApp, public CefBrowserProcessHandler
    {
         
         
    public:
        App() = default;
        CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override {
         
          return this; }
        void OnContextInitialized() override;
    private:
        IMPLEMENT_REFCOUNTING(App);
    };
    
  • 头文件里的宏
    • 头文件中 #pragma once 宏指令告诉编译器这个文件只会被编译一次。这是现代 C++ 编译器新增的一个指令,这个指令出现之前 C++ 开发者都是通过如下方式来保证头文件不会被重复编译的。
      #ifndef _FileA
      #define _FileA
      // code 
      #endif
      
      • 这种方式虽然可以兼容古老的低版本编译器,但书写起来繁琐,编译时要预先分析文件内容,又非常低效,所以推荐使用 #pragma once 指令。
    • App 类的头文件中使用了宏 IMPLEMENT_REFCOUNTING ,这个宏为 App 类附加了一些特殊的方法,这些方法保证 App 类型的对象指针可以被 CefRefPtr 包裹。
  • 浏览器进程的行为
    • App 类继承自 CefApp 和 CefBrowserProcessHandler 类,这两个基类提供了对浏览器进程的行为描述,比如:OnContextInitialized(浏览器进程的渲染线程初始化成功后被调用)、OnBeforeCommandLineProcessing (命令行参数被 CEF 和 Chromium 处理之前触发)等。我们在 App 类里只重写了 2 个基类方法,下面我们一个一个来介绍。
    • GetBrowserProcessHandler 方法返回浏览器进程的处理类实例指针,这里我们返回了 App 类的实例指针自身(也就是它自己)。浏览器进程内可能会有多个线程在执行,任何一个线程都有可能调用这个方法。
    • OnContextInitialized 方法在浏览器进程的主线程初始化成功后被调用,代表着浏览器进程已经初始化成功了。
  • 窗口创建逻辑
    • 我们在 OnContextInitialized 方法中创建了第一个窗口,这个方法代码在类的源码文件中完成,下面我们来看一下它的代码:
      //App.cpp
      #include "App.h"
      #include "include/cef_browser.h"
      #include "include/views/cef_browser_view.h"
      #include "include/views/cef_window.h"
      #include "include/wrapper/cef_helpers.h"
      #include "WindowDelegate.h"
      //CEF主进程上下文环境初始化成功
      void App::OnContextInitialized() {
             
             
          CEF_REQUIRE_UI_THREAD();
          auto url = "https://www.baidu.com";
          CefBrowserSettings settings;
          CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(nullptr, url, settings, nullptr, nullptr, nullptr);
          CefWindow::CreateTopLevelWindow(new WindowDelegate(browser_view));
      }
      
    • 这个方法的实现逻辑稍微多一些,我们一步步详细解释这段代码的执行逻辑。
      • CEF_REQUIRE_UI_THREAD() 是一个宏,这个宏保证执行此方法的是浏览器进程的主线程。
      • url 字符串变量用于存放应用的首页地址。
      • settings 对象,是 CEF 配置对象,它的作用我们前文已经说过了,这里同样也是只用它默认的配置。
      • browser_view 是我们通过 CefBrowserView 类的 CreateBrowserView 静态方法创建了一个 BrowserView 对象。这个方法的第一个参数是一个 CefClient 类型的智能指针,它负责处理一切与页面相关的事件,比如下载、拖动、聚焦等,这里我们没有设置它的值,直接传递了一个空指针,要求 CEF 框架按照默认的行为处理页面相关的事件; url 字符串和 settings 对象作为第二个和第三个参数;第四个参数是一个用户自定义的附加信息对象,我们暂时用不到它;第五个参数是一个处理页面请求的对象,我们就使用 CEF 默认的请求处理机制,所以暂时也用不到它;第六个参数是 BrowserView 的代理对象,我们可以通过这个对象处理一些与 BrowserView 相关的事件,比如与该 BrowserView 关联的 Browser 对象创建成功的事件,为了简便,我们也没有用这个参数。
      • 最后我们通过 CefWindow 类的 CreateTopLevelWindow 静态方法创建了一个窗口,这个方法只有一个参数,就是 CefWindowDelegate 对象。WindowDelegate 是我们自定义的一个类型,这个类型继承自 CefWindowDelegate 对象,所以它的实例也是 CefWindowDelegate 类型的对象,我们通过这个 WindowDelegate 对象设置窗口的大小,聚焦窗口内的页面等,接下去我们就介绍它。
  1. 窗口代理对象
  • 前文我们介绍了当浏览器进程的主线程初始化成功后,App 对象的 OnContextInitialized 方法会被执行,在这个方法的最后,我们通过 CreateTopLevelWindow 方法为 CEF 框架提供了一个窗口代理对象。 CEF 框架会把与窗口创建有关的逻辑交给这个对象来执行,接下来我们就看一下这个对象的头文件代码:
    // WindowDelegate.h
    #pragma once
    #include "include
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值