最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    正文概述 掘金(w4ngzhen)   2021-04-18   875

    在上文《使用CEF(2)— 基于VS2019编写一个简单CEF样例》中,我们介绍了如何编写一个CEF的样例,在文章中提供了一些代码清单,在这些代码清单中提到了一些CEF的定义的类,例如CefAppCefClient等等。它们具体有什么作用,和CEF的进程架构有什么关系呢?本文将逐一进行介绍。

    CEF的进程架构

    CEF3使用多个进程运行。处理窗口创建、绘制和网络访问的主要进程称为浏览器进程。这通常与宿主应用程序的进程相同,大多数应用程序的逻辑将在浏览器进程中运行。使用Blink引擎渲染HTML和JavaScript执行在单独的渲染进程中发生。一些应用程序逻辑(如JavaScript绑定和DOM访问)也将在渲染进程中运行。默认进程模型将为每个唯一源地址(scheme+domain)运行一个新的渲染进程。其他进程将根据需要生成,例如处理Flash等插件插件进程和处理加速合成的GPU进程。综合上述文档,我们整理一下:

    浏览器进程(Browser Process):

    • 窗口创建、绘制
    • 网络访问
    • ......

    渲染进程(Renderer Process):

    • 通过Blink引擎渲染HTML

    • JavaScript执行(V8引擎)

    • ......

    需要注意的是,浏览器进程中会进行窗口绘制,并不是指绘制HTML内容,而是承载网页内容的那个窗体壳,同样渲染进程也不是用来创建窗体的进程。接下来,本人将以官方CefSimple Demo源码入手,逐步介绍Cef的概念。

    本来本人想要使用上一文中的编写的simple-cef进行源码解析,但是为了让本文相对的独立,所以还是决定使用官方的Demo:cefsimple进行源码解析。尽管和simple-cef项目的内容差别不大。需要注意的是一下的源码在解析的时候,会进行适当的删改,读者最好对照源码进行阅读更佳。PS:源码中显示......表明示例代码有所删除。

    cefsimple_win.cc

    // ......
    // Entry point function for all processes.
    int APIENTRY wWinMain(HINSTANCE hInstance,
                          HINSTANCE hPrevInstance,
                          LPTSTR lpCmdLine,
                          int nCmdShow) {
    // ......
    
      // CEF applications have multiple sub-processes (render, plugin, GPU, etc)
      // that share the same executable. This function checks the command-line and,
      // if this is a sub-process, executes the appropriate logic.
      int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
      if (exit_code >= 0) {
        // The sub-process has completed so return here.
        return exit_code;
      }
    
    // ......
    
      // SimpleApp implements application-level callbacks for the browser process.
      // It will create the first browser instance in OnContextInitialized() after
      // CEF has initialized.
      CefRefPtr<SimpleApp> app(new SimpleApp);
    
      // Initialize CEF.
      CefInitialize(main_args, settings, app.get(), sandbox_info);
    
      // Run the CEF message loop. This will block until CefQuitMessageLoop() is
      // called.
      CefRunMessageLoop();
    
      // Shut down CEF.
      CefShutdown();
    
      return 0;
    }
    
    

    首先第一个重要点是:

        // CEF applications have multiple sub-processes (render, plugin, GPU, etc)
        // that share the same executable. This function checks the command-line and,
        // if this is a sub-process, executes the appropriate logic.    
    	int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
        if (exit_code >= 0) {
            // The sub-process has completed so return here.
            return exit_code;
        }
    

    这段代码看起来有点奇怪,对于英文的翻译如下:

    然后,我们查看该函数:CefExecuteProcess

    ///
    // This function should be called from the application entry point function to
    // execute a secondary process. It can be used to run secondary processes from
    // the browser client executable (default behavior) or from a separate
    // executable specified by the CefSettings.browser_subprocess_path value. If
    // called for the browser process (identified by no "type" command-line value)
    // it will return immediately with a value of -1. If called for a recognized
    // secondary process it will block until the process should exit and then return
    // the process exit code. The |application| parameter may be empty. The
    // |windows_sandbox_info| parameter is only used on Windows and may be NULL (see
    // cef_sandbox_win.h for details).
    ///
    /*--cef(api_hash_check,optional_param=application,
            optional_param=windows_sandbox_info)--*/
    int CefExecuteProcess(const CefMainArgs& args,
                          CefRefPtr<CefApp> application,
                          void* windows_sandbox_info);
    

    翻译:

    从这段话我们不难推断出,CEF在以多进程架构下启动的时候,会多次启动自身可执行程序。启动的时候,会通过命令行参数传入某些标识,由CefExecuteProcess内部进行判断。如果是主进程,则该函数立刻返回-1,程序会继续执行下去,那么后续继续运行的代码全部都运行在主进程中;如果是子进程(渲染进程等),那么该函数会阻塞住,直到子进程结束后,该函数会返回一个大于等于0的值,并在main函数直接返回,进而退出。

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    对CefExecuteProcess分析就到这里,细节可以阅读官方文档,我们继续后续的代码分析:

      // SimpleApp implements application-level callbacks for the browser process.
      // It will create the first browser instance in OnContextInitialized() after
      // CEF has initialized.
      CefRefPtr<SimpleApp> app(new SimpleApp);
    

    注释翻译如下

    查看SimpleApp的声明,发现该类继承了CefApp:

    class SimpleApp : public CefApp, public CefBrowserProcessHandler {
     public:
      SimpleApp();
      ......
    }
    

    于是,我们迎来了第一个重要的类:CefApp。

    CefApp

    CefApp在官方文档中,就写了一句话介绍:

    本人一开始看到CefApp时,想到上面提到的CEF的多进程架构,结合后文还会提到的CefClient,以为所谓CefApp就是指浏览器进程,CefClient就对应其他的进程(一个App对应多个Client,多么的自然的理解),然而这样错误的理解,让本人在阅读代码的时候走了很大的弯路。

    首先,我们看一下CefApp的头文件声明:

    class CefApp : public virtual CefBaseRefCounted {
     public:
      virtual void OnBeforeCommandLineProcessing(
          const CefString& process_type,
          CefRefPtr<CefCommandLine> command_line) {}
        
      virtual void OnRegisterCustomSchemes(
          CefRawPtr<CefSchemeRegistrar> registrar) {}
        
      virtual CefRefPtr<CefResourceBundleHandler> GetResourceBundleHandler() {
        return nullptr;
      }
        
      virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() {
        return nullptr;
      }
        
      virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() {
        return nullptr;
      }
    };
    

    先看其中有两个本文讨论的重点方法:GetBrowserProcessHandlerGetRenderProcessHandler。它们的文档注释如下:

    ///
    // Return the handler for functionality specific to the browser process. This
    // method is called on multiple threads in the browser process.
    // 返回浏览器进程特定功能的处理程序。在浏览器进程中的多个线程上调用此方法。
    ///
    virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
    ///
    // Return the handler for functionality specific to the render process. This
    // method is called on the render process main thread.
    // 返回渲染进程特定功能的处理程序。在渲染进程中的主线程上调用此方法。
    ///
    virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler()
    

    读者看到这些注释可能会疑问:为什么注释中一会儿说在浏览器进程中一会儿又说在渲染进程中?难道这个类的实例还会在多个进程中使用吗?对也不对。这个类的实例确实会在浏览器进程和渲染进程中使用,但是我们又知道,两个进程之间的资源是不共享的,包括类实例,所以在浏览器进程运行的过程中,会使用到CefApp的某个实例化对象,而在渲染进程的运行过程中,又会使用到CefApp另一个实例化对象,它们都是CefApp子类的实例,但一定不是同一个实例对象。

    我们可以这样理解:一个CefApp对应了一个进程,而一个进程可以是浏览器进程(Browser Process),可以是渲染进程(Renderer Process)。因此,CefApp提供了GetBrowserProcessHandler和GetRendererProcessHandler来分别在相关进程中获取对应的handler。

    这两个方法的实现由我们来决定,即我们可以通过编程方式来返回handler,但这两个方法不会由我们客户端代码进行调用,而是CEF在运行过程中,由CEF在某个时刻来回调这两个方法。所以,这里虽然写了两个GetXXXProcessHandler,但在浏览器进程渲染进程中只会分别调用GetBrowserProcessHandler和GetRendererProcessHandler。

    按照程序运行的角度讲,当浏览器进程运行的时候,CEF框架就会在某个时候调用CefApp::GetBrowserProcessHandler获得由我们定义的BrowserProcessHandler实例,这个实例会在适当的时候调用它提供的一些方法(后文介绍有哪些方法);当渲染进程运行的时候,CEF框架就会在某个时候调用CefApp::GetRendererProcessHandler得到我们定义的RendererProcessHandler实例,然后在适当的时候调用RenererProcessHandler中的一些方法(后文介绍有哪些方法)。

    在cefsimple的示例代码中只有一个SimpleApp是继承的CefApp,这个类还继承了CefBrowserHandler,表明自身是同时也是CefBrowserHandler,这样实现的GetBrowserProcessHandler就返回自身。那么CEF是如何将我们的CefApp实例关联到CEF运行中的呢?

      // SimpleApp implements application-level callbacks for the browser process.
      // It will create the first browser instance in OnContextInitialized() after
      // CEF has initialized.
      CefRefPtr<SimpleApp> app(new SimpleApp);
    
      // Initialize CEF.
      CefInitialize(main_args, settings, app.get(), sandbox_info);
    

    注意CefInitialize中的app.get()参数,就是将我们的CefApp关联到CEF的运行中的。那么,有些读者会有疑问,在示例代码中,只看到我们创建的SimpleApp类继承了CefApp,并通过GetBrowserProcessHandler返回自身来表明是一个浏览器进程的回调实例,并没有看到体现渲染进程的代码呢?确实,cefsimple作为helloworld级别的代码,没有体现这一点。在cefclient示例代码中(更高阶的CEF示例,也更复杂),你会看到:

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    上图是浏览器进程CefApp子类ClientAppBrowser(这里的”Client“是cefclient示例代码的“client”,请勿和下文的CefClient类混淆)。

    同时你还能找到一个CefApp子类ClientAppRenderer:

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    你甚至还能找到一个名为ClientAppOther的CefApp子类:

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    那么它们在哪儿被使用到呢?

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    看到这里,我相信绝大多数的读者应该能够理解我所说的CefApp代表的是一个进程的抽象了。这块的大体流程是,通过一个工具函数GetProcessType从命令行中解析--type=xxx(浏览器进程没有这个命令参数)来判断进程的类型,然后实例化对应的CefApp子类,最后通过CefExecuteProcess来运行进程。

    在介绍了CefApp的基本概念以后,我们可以继续分析SimpleApp

    通过上文,我们知道SimpleApp是CefApp子类,并且通过只会在浏览器进程中,会使用到该类的实例,因为实现了接口CefBrowserProcessHandler,并且有如下代码:

      // CefApp methods:
      virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
          OVERRIDE {
        return this;
      }
    

    那么在CEF中,作为CefBrowserProcessHandler,有哪些回调可以供我们定制呢?下面是头文件声明,并且我也写了下概要注释:

    class CefBrowserProcessHandler : public virtual CefBaseRefCounted {
     public:
        // Cookie处理定制化
      virtual void GetCookieableSchemes(std::vector<CefString>& schemes,
                                        bool& include_defaults) {}
        // 在CEF上下文初始化后,在浏览器进程UI线程中进行调用。
      virtual void OnContextInitialized() {}
        // 可定制化处理子进程启动时的命令行参数
      virtual void OnBeforeChildProcessLaunch(
          CefRefPtr<CefCommandLine> command_line) {}
        // 打印处理
      virtual CefRefPtr<CefPrintHandler> GetPrintHandler() { return nullptr; }
        // 自定义消息循环的时候,消息循环的频率
      virtual void OnScheduleMessagePumpWork(int64 delay_ms) {}
        // 获取默认的CefClient
      virtual CefRefPtr<CefClient> GetDefaultClient() { return nullptr; }
    };
    

    通过阅读该Handler的头文件以及每个函数的调用说明,我们继续阅读在SimpleApp::OnContextInitialized这个函数:

    void SimpleApp::OnContextInitialized() {
      CEF_REQUIRE_UI_THREAD();
    
      CefRefPtr<CefCommandLine> command_line =
          CefCommandLine::GetGlobalCommandLine();
    
      const bool enable_chrome_runtime =
          command_line->HasSwitch("enable-chrome-runtime"); // 是否启用chrome运行时
    
    #if defined(OS_WIN) || defined(OS_LINUX)
        // Create the browser using the Views framework if "--use-views" is specified
        // via the command-line. Otherwise, create the browser using the native
        // platform framework. The Views framework is currently only supported on
        // Windows and Linux.
        // 如果命令行中指定了"--use-views",那么使用CEF自己的视图框架(Views framework)
        // 否则使用操作系统原生API。视图框架目前只支持Windows和Linux。
      const bool use_views = command_line->HasSwitch("use-views");
    #else
      const bool use_views = false;
    #endif
    
      // SimpleHandler implements browser-level callbacks.
      CefRefPtr<SimpleHandler> handler(new SimpleHandler(use_views));
    
      // Specify CEF browser settings here.
      CefBrowserSettings browser_settings;
    
      std::string url;
    
      // Check if a "--url=" value was provided via the command-line. If so, use
      // that instead of the default URL.
      url = command_line->GetSwitchValue("url");
      if (url.empty())
        url = "http://www.google.com";
    
      if (use_views && !enable_chrome_runtime) {
        // Create the BrowserView.
        CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
            handler, url, browser_settings, nullptr, nullptr,
            new SimpleBrowserViewDelegate());
    
        // Create the Window. It will show itself after creation.
        CefWindow::CreateTopLevelWindow(new SimpleWindowDelegate(browser_view));
      } else {
        // Information used when creating the native window.
        CefWindowInfo window_info;
    
    #if defined(OS_WIN)
        // On Windows we need to specify certain flags that will be passed to
        // CreateWindowEx().
        window_info.SetAsPopup(NULL, "cefsimple");
    #endif
    
        // Create the first browser window.
        CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
                                      nullptr, nullptr);
      }
    }
    

    对于这段代码,我整理了如下流程,方便读者对照阅读:

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    在这个流程中,最关键的3个部分被我用红色标记出来:

    1. SimpleHandler(CefClient子类);
    2. 使用CEF的窗体视图框架创建CefBrowserView和CefWindow;
    3. 使用操作系统原生API构建窗体。

    整个过程中会创建CefClient的子类实例,然后通过CEF提供的API来将CefClient和窗体结合在一起。

    对于使用CEF自己的视图框架,有如下的步骤:

    1. 首先是调用CefBrowserView::CreateBrowserView得到CefBrowserView实例,这个过程会把CefClient实例和View对象通过API绑定。
    2. 调用CefWindow::CreateTopLevelWindow,传入CefBrowserView实例来创建窗体。

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    对于使用操作系统原生API创建浏览器窗体,主要是如下步骤:

    1. 使用CefWindowInfo设置窗体句柄
    2. 调用CefBrowserHost::CreateBrowser将对应窗体句柄的窗体和CefClient绑定起来

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    当然,上述两个窗体的创建过程涉及到CEF的窗体模块,我们不在这里细说,但是两个流程都离不开一个重要的类:CefClient,它具体是什么呢?接下来,我们将对CefClient进行介绍,并对SimpleHandler这个类(CefClient子类)进行一定的源码分析。

    CefClient

    在官方的文档,描述了CefClien的概念:

    首先需要解释一下什么什么是特定浏览器实例,实际上,指的是以下过程产生的浏览器实例:

        CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
            handler, url, browser_settings, nullptr, nullptr,
            new SimpleBrowserViewDelegate());
    // 或
        CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
                                      nullptr, nullptr);
    

    通过上述两种方式创建的浏览器实例,是一个概念上的实例,并不是指你能看得到的浏览器的窗口,窗口只是浏览器实例的宿主而已。而浏览器中发生的事件,例如:生命周期的变化,对话框等,都只会通过CefClient中返回的各种类型Handler以及这些Handler接口实例提供的方法回调。

    下面时CefClient的声明:

    class CefClient : public virtual CefBaseRefCounted {
     public:
      virtual CefRefPtr<CefAudioHandler> GetAudioHandler() { return nullptr; }
    
      virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() {
        return nullptr;
      }
    
      virtual CefRefPtr<CefDialogHandler> GetDialogHandler() { return nullptr; }
    
      virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() { return nullptr; }
    
      virtual CefRefPtr<CefDownloadHandler> GetDownloadHandler() { return nullptr; }
    
      virtual CefRefPtr<CefDragHandler> GetDragHandler() { return nullptr; }
        // ...... 还有很多的Handler
            
    }
    

    在这个CefClient提供了很多GetXXXHandler方法,这些方法会在合适的时候,被CEF调用以得到对应的Handler,然后再调用返回的Handler中的方法。例如,HTML页面中的Title发生变化的时候,就会调用CefClient::CefDisplayHandler()得到一个CefDisplayHandler实例,然后再调用其中的CefDisplayHandler::OnTitleChange,而这些过程不是我们调用的,而是CEF框架完成的。只是具体的实现有我们客户端代码编写。

    那么现在思考一下,为什么会有这个CefClient呢?在本人看来主要是如下的理由:

    在CefClient中各种回调的事件,本质上发生的地方是渲染进程。因为每当一个浏览器实例(不是浏览器进程)创建的时候,会有一个对应的渲染进程创建(也可能由于配置,而共用一个,这里先认为默认多个一对一)。渲染进程中发生的各种V8事件、下载事件,显示事件等触发后,会通过进程间通讯给到浏览器进程,然后在浏览器进程中找到与之相关的CefClient,然后从CefClient中找到对应的Handler,回调Handler对应的方法。

    也就是说,将在渲染进程发生的事件,用在浏览器进程中的CefClient一定的抽象映射,而不是直接在浏览器进程处理器中进行,因为一个浏览器进程可能会创建多个渲染进程,让CefClient作为中间层避免耦合。

    使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    当然,文档也为我们指出,CefClient实例与浏览器实例可以不是一一对应的,多个浏览器实例可以共享一个CefClient,如此一来我们也可以总结关于CefClient的一点:非必要情况,不要编写具有状态的CefClient

    至此,我们通过对Demo源码入手,对CefApp和CefClient已经有了一个整体的认识,读者可以阅读官方文档来更加深入的了解:官方文档。


    起源地下载网 » 使用CEF(3)— CEF官方Demo源码入手解析CEF常见对象与架构

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元