本小节主要理解CEF的多进程模型,为后面的JavaScript与C++ 通信做一些知识储备,顺便再次梳理一下CEF的启动流程。

1.任务管理器观察多进程

CEF基于Chromium,是多进程模型。怎么理解多进程?启动一个Chrome浏览器,多打开几个网站,然后再打开资源管理器查看Chrome的进程,现在对任务管理器做一些调整,目的是查看每个进程所启动的命令行以及命令行所携带的参数。如下图:勾选 “PID”和“命令行”

此时可以看到Chrome浏览器开启的进程:

可以看到Chrome浏览器启动了多个进程,2852 这个进程的命令行后面并没有携带任何其它参数,它就是browser进程 ,而其它进程如 4296 这个进程携带了 --type=renderer ... 多个参数,它是 render进程

打开CEF的自带的cefsimple 应用也能看到多个进程的存在。那么这些进程是怎么启动的?都有哪些作用?

2.Chromium的进程分类

  • browser 没有type参数时默认为browser进程
  • renderer 渲染进程
  • plugin 插件进程
  • ppapi-broker
  • ppapi
  • sandbox-ipc
  • utility
  • zygote
  • gpu-process

这里我们必须要理解的是 browser 进程 和 render进程

browser进程: 处理窗口创建、窗口绘制、网络交互以及大部分的主要逻辑。browser进程通常就是宿主进程。

render进程: 每个页面都是运行在自己的进程里,这些进程我们称之为render进程。render进程会在窗口中渲染出web页面(引用了CSS,JavaScript,图片等的HTML文件)。

需要注意的是,browser 进程中会进行窗口绘制,并不是指绘制HTML内容,而是承载网页内容的那个窗体壳,同样render进程也不是用来创建窗体的进程。

3.进程怎么启动

查看sefsimple示例程序的入口函数:

// ...其它省略
int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);if (exit_code >= 0) {return exit_code;
}
// ...其它省略
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(::GetCommandLineW());
CefSettings settings;
settings.no_sandbox = true;
CefRefPtr<SimpleApp> app(new SimpleApp);
// 初始化CEF
CefInitialize(main_args, settings, app.get(), sandbox_info);
// 开启消息循环
CefRunMessageLoop();
// 释放CEFCefShutdown();
// ...其它省略

创建进程是用 CefExecuteProcess 函数创建的。这个函数在 libcef_dll_wrapper.cc 中,它内部又调用了cef_execute_process方法(libcef_dll.cc),cef_execute_process又调用了libcef/browser/context.cc文件内实现的CefExecuteProcess方法。源码中有这样一句代码:

int CefExecuteProcess(const CefMainArgs& args,CefRefPtr<CefApp> application,void* windows_sandbox_info) {base::CommandLine command_line(base::CommandLine::NO_PROGRAM);// 省略...// If no process type is specified then it represents the browser process and// we do nothing.std::string process_type =command_line.GetSwitchValueASCII(switches::kProcessType);if (process_type.empty())return -1;CefMainDelegate main_delegate(application);// Execute the secondary process.// 省略...content::ContentMainParams params(&main_delegate);params.instance = args.instance;// 省略...params.argc = args.argc;params.argv = const_cast<const char**>(args.argv);return content::ContentMain(params);
#endif

它分析了命令行参数,提取”type”参数,如果为空,说明是Browser进程,返回-1,sefsimple 入口函数继续向后执行,创建了 SimpleApp 它实现了CefApp 接口,给了我们控制CEF的入口,接着就进行了初始化。初始化完毕之后,回调了SimpleApp::OnContextInitialized() 方法,在这个方法中我们创建了 浏览器,并用到了 SimpleHandler 而它实现了 CefClient

如果”type”参数不为空,做一些判断,最后调用了content::ContentMain方法,创建子进程,直到这个方法结束,子进程才结束。

编译后的可执行文件为"sefsimple.exe", 在进程管理器中可以看到每个进程都是从 sefsimple.exe开始的,只不过是参数不同而已。

观察CefExecuteProcess 方法的第二个参数,它是一个CefApp 类型的,在我们自己的例子程序和sefsimple 中都传递了NULL,实际上它也可以为它传递一个 CefApp对象。

分析完这些,现在我们清楚了,Browser进程,需要CefApp(SimpleApp实现了这个接口)和CefClient(SimpleHandler实现了这个接口)。而Renderer进程只要CefApp。 前面我们自己编写的示例和sefsimple 都传递了NULL。

现在再打开示例程序cefclient的入口函数:

int RunMain(HINSTANCE hInstance, int nCmdShow) {// ...省略CefMainArgs main_args(hInstance);void* sandbox_info = nullptr;// Create a ClientApp of the correct type.CefRefPtr<CefApp> app;ClientApp::ProcessType process_type = ClientApp::GetProcessType(command_line);if (process_type == ClientApp::BrowserProcess)app = new ClientAppBrowser();else if (process_type == ClientApp::RendererProcess)app = new ClientAppRenderer();else if (process_type == ClientApp::OtherProcess)app = new ClientAppOther();// Execute the secondary process, if any.int exit_code = CefExecuteProcess(main_args, app, sandbox_info);if (exit_code >= 0)return exit_code;// ...省略return result;
}

这段代码上我们看到了这段代码根据 进程类型,创建了不同的CefApp,看看 ClientAppBrowser (创建browser进程用)和 ClientAppRenderer (创建render进程用) 有什么不一样的?

// client_app_browser.h
class ClientAppBrowser : public ClientApp, public CefBrowserProcessHandler {//...
}// client_app_renderer.h
class ClientAppRenderer : public ClientApp, public CefRenderProcessHandler {//...
}

可以看到明显的:

browser进程 需要 CefApp和 CefClient, CefApp要提供CefBrowserProcessHandler 接口,

render进程 只需要 CefApp ,CefApp要提供 CefRenderProcessHandler 接口。

CefBrowserProcessHandler 接口用来处理 browser进程 的一些回调, CefRenderProcessHandler 接口用来处理 render进程 的一些回调。

我们自己的例子和 sefsimple 示例程序没有针对Render进程做特别处理,因为 CefApp 没有实现CefRenderProcessHandler 接口,所以会缺失一部分功能。当我们需要在JavaScript中调用 C++代码时,这就完成不了了。

4 单进程启动

我们在开发的时候,如果在多进程的情况下,调试将变得比较困难,有没有办法让我们开发的时候在单进程中运行?答案是为添加启动参数。

4.1 快捷方式上添加启动参数

我们前面开发的示例,Debug目录中为可执行文件创建一个快捷方式,然后为快捷方式添加启动参数==–single-process==

4.2 VisioStudio 2019中添加启动参数

在命令参数后面添加 –single-process

再次启动后,发现任务管理器中就只有一个进程了。

5 单一执行体与分离执行体

前面我们自己编写的例子,cefsimple 和cefclient 这两个例子都使用的是单一执行体。所谓单一执行体就是启动同一个 exe文件来分别创建不同的进程。(多进程)

当以单一执行体运行时,如我们自己的例子和cefsimple ,cefclient 它们都是先执行进程,然后再初始化的。

所谓分离执行体就是启动 browser进程 的是一个exe文件,而其它进程比如render进程 执行的是另外一个exe,它是先初始化,告诉 browser进程(主进程)分离的执行体是哪个exe文件,然后再执行子进程。当使用独立的子进程执行体时,我们需要2个分开的可执行工程和入口函数。

下面就看看如何开发分离执行体的应用。

5.1 分离执行体项目

5.1.1 创建分离体项目

在原有的解决方案中再添加一个子项目,名称为QyRender,将来的分离执行体就是 QyRender.exe。 这个项目使用 QT Console Application ,因为它不会用到图形界面

添加项目配置include目录和lib引用



告诉操作系统使用Windows的方式运行这个exe,如果不设置这一步,当这个进程启动后,会弹出控制台窗口。

5.1.2 实现CefApp,CefRenderProcessHandler

我们说创建渲染进程的时候,为CefExecuteProcess 函数传递一个 CefApp,用于将来JavaScript与C++的通信,暂时这个类中什么也不做,先放在这里

// QyAppRender.h
#include <qobject.h>
#include "include/cef_app.h"class QyAppRenderer :public CefApp, public CefRenderProcessHandler {public:QyAppRenderer();//重写CefApp 中的GetRenderProcessHandler方法CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE{return this;}//实现 CefRenderProcessHandler 接口中的方法void OnBrowserCreated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefDictionaryValue> extra_info) OVERRIDE;private:// Include the default reference counting implementation.IMPLEMENT_REFCOUNTING(QyAppRenderer);
};// QyAppRender.cpp
#include "QyAppRenderer.h"/// <summary>
/// 构造方法空实现
/// </summary>
QyAppRenderer::QyAppRenderer() {}
/// <summary>
/// 当CefBrowser对象已经创建的时候回调,将来JS与c++通信的时候会用到,现在只做空实现
/// </summary>
/// <param name="browser"></param>
/// <param name="extra_info"></param>
void QyAppRenderer::OnBrowserCreated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefDictionaryValue> extra_info) {}

5.1.3 分离体项目main函数

分离器项目的main函数很简单,拿到命令行参数,创建 CefApp (QyAppRenderer)后执行CefExecuteProcess即可

#include "include/cef_app.h"
#include "include/cef_command_line.h"
#include "QyAppRenderer.h"
int main(int argc, char *argv[])
{HINSTANCE hInstance = GetModuleHandle(nullptr);//构造命令行CefMainArgs main_args(hInstance);// Optional implementation of the CefApp interface.// 可选择性地实现CefApp接口CefRefPtr<QyAppRenderer> app(new QyAppRenderer);// Execute the sub-process logic. This will block until the sub-process should exit.// 执行子进程逻辑,此时会堵塞直到子进程退出。return CefExecuteProcess(main_args, app.get(),nullptr);
}

5.2 主项目main函数

在主项目的 main函数中,就不需要执行 CefExecuteProcess了,只需要在 CefSettings中配置执行体exe文件路径即可。主项目启动的就是browser 进行它只需要执行初始化cef和开始消息循环就完事了

#include "mainwindow.h"
#include <QtWidgets/QApplication>
#include "include/cef_command_line.h"
#include "include/cef_sandbox_win.h"
#include "cef/simple_app.h"
#include <qdebug.h>
int main(int argc, char *argv[])
{// Enable High-DPI support on Windows 7 or newer.CefEnableHighDPISupport();// 通过GetModuleHandle 获取 HINSTANCEHINSTANCE hInstance = GetModuleHandle(nullptr);// 开启 QT 消息循环SimpleApp* cefApp=new SimpleApp;QApplication a(argc, argv);//CEF 命令行参数CefMainArgs main_args(hInstance);CefSettings settings;settings.no_sandbox = true;settings.multi_threaded_message_loop = true;//分离的执行体QString executerPath = QApplication::applicationDirPath().append("\\QyRender.exe");CefString(&settings.browser_subprocess_path).FromASCII(executerPath.toStdString().c_str());MainWindow w(cefApp, nullptr);w.show();CefRefPtr<SimpleApp> app(cefApp);// 初始化CEFCefInitialize(main_args, settings, app.get(), nullptr);int ret = a.exec();// Shut down CEF.CefShutdown();return ret;
}

这里的重点是 settings.browser_subprocess_path设置为执行体文件路径,去掉CefExecuteProcess 函数

5.3 编译整个解决方案

编译以后,会把可执行文件放入到解决方案的Debug目录中。

可以看到有两个可执行文件,现在启动 QyCefVS.exe, 查看进程:

注意分离的 QyRender虽然名字有个render,但是它是可以启动除了 browser 进程以外所有进程的。
代码请访问 GitHub qt_cef_07分支

QT集成CEF07-理解CEF多进程相关推荐

  1. QT集成QML和JavaScript

    QT 集成QML和JavaScript 集成QML和JavaScript JavaScript表达式 JavaScript资源 JavaScript导入 JavaScript主机环境 精调JavaSc ...

  2. QT之深入理解QThread

    QT之深入理解QThread 理解QThread之前需要了解下QThread类,QThread拥有的资源如下(摘录于QT 5.1 帮助文档): 在以上资源中,本文重点关注槽:start():信号:st ...

  3. 旺谷图控与QT集成开发

    1.与QT集成开发 1.1旺谷图控QT开发包 QT开发包中包含:头文件.静态库.动态库.元件库.vgs工程或元件组成.其中元件库和vgs工程由vgs开发工具发布,拷贝到Qt工程的debug和relea ...

  4. Qt 集成 FFmpeg 实现颜色格式转换

    目录 1. Qt 集成 FFmpeg 1.1 下载 FFmpeg 1.2 Qt 集成 FFmpeg 1.2.1 修改 .pro 文件 1.2.2 放入 dll 文件 1.2.3 代码中使用 FFmpe ...

  5. Qt 集成miniblink浏览器库之1编译使用

    1.miniblink简介 miniblink是一款精简小巧的浏览器控件,由龙泉寺扫地僧基于chromium精简而成,是市面上最小巧的chromium内核控件没有之一. 它仅10余M大小,只需一个dl ...

  6. 【机器视觉】Qt集成Halcon开发环境详解(二)

    00. 目录 文章目录 00. 目录 01. 概述 02. Halcon动态库配置方案一 03. Halcon动态库配置方案二 04. 问题讨论 05. 附录 01. 概述 Qt中集成Halcon开发 ...

  7. 个人对持续集成的理解和实践

    目前团队人数很少,也没有真正意义上的测试人员,那么如何保证代码质量呢?最近看了<持续交付--发布可靠软件的系统方法>很受启发,突然也想通了很多集成开发工具的设计理念,并做了一些实践,特别记 ...

  8. Qt 集成miniblink浏览器库之4 解决兼容性问题

    之前介绍了如何miniblink集成到qt,采用wkeCreateWebWindow来创建一个浏览器窗口,wkeCreateWebWindow有三种方式 typedef enum _wkeWindow ...

  9. Qt+腾讯IM开发笔记(一):腾讯IM介绍、使用和Qt集成腾讯IM-SDK的工程模板Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/119305601 长期持续带来更多项目与技术分享,咨询请 ...

最新文章

  1. golang 绘图库_golang在图片上绘制中文不乱码的方法
  2. 大脑模拟AI学习策略,这项逼近反向传播的研究登上《自然-神经科学》
  3. 中的挂起是什么意思_数字博物馆是什么意思?数字博物馆用到了哪些技术?
  4. redis 4.0.8 源码包安装集群
  5. php键名改为0.1.2.3,揭秘 0.1 + 0.2 != 0.3(php 请自觉点用round)
  6. 如何看待程序员内卷现象
  7. 11)PHP,单选框和复选框的post提交方式处理
  8. java 人脸识别 demo_java引用Arcface,实现人脸识别(demo)
  9. DataNode,NameNode,JobTracker,TaskTracker用jps查看无法启动解决办法
  10. MaxCompute - ODPS重装上阵 第一弹 - 善用MaxCompute编译器的错误和警告
  11. python面试自我介绍_如何拿到半数面试公司Offer——我的Python求职之路
  12. 前端小白程序员入门之前知道这些,半年后都拿到8K+的offer
  13. WinHEC 2008 China
  14. 怎么快速将Excel文件转为DBF格式文件
  15. html yy直播,网页YY直播间进入方法 网页YY迷你版怎么用
  16. 阿里云(飞天)里的 盘古
  17. 常用统计量及其常见分布
  18. CentOS虚拟机根分区磁盘扩容操作
  19. 使用 C# 进行 Outlook 2007 编程
  20. 中国电信数字中南智慧网自动登录油猴脚本

热门文章

  1. 2021 栉风沐雨 , 砥砺前行
  2. mustache java 性能_mustache模板技术
  3. 人工智能公司,主要的商业模式是什么?
  4. 讲一下prototype是什么东西,原型链的理解,什么时候用prototype?
  5. 莫言家人回应陈光标赠送别墅:不食嗟来之食-陈光标-别墅-莫言
  6. OpenCV-图像旋转Rotate
  7. transform:rotate
  8. 腾讯是如何挑战360安全卫士霸主地位
  9. 基于NB-IOT无线红外通信电力参数远程仪器抄表方案
  10. 卷积运算与互相关运算