天天看点

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

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

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

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

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

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

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

可以看到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();
// 释放CEF
 CefShutdown();
// ...其它省略 
           

创建进程是用

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==

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

4.2 VisioStudio 2019中添加启动参数

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

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

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

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

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

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

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

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

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

5.1 分离执行体项目

5.1.1 创建分离体项目

在原有的解决方案中再添加一个子项目,名称为QyRender,将来的分离执行体就是

QyRender.exe

。 这个项目使用 QT Console Application ,因为它不会用到图形界面

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

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

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体
QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

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

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

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 获取 HINSTANCE
    HINSTANCE 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);
    // 初始化CEF
    CefInitialize(main_args, settings, app.get(), nullptr);
    int ret = a.exec();
    // Shut down CEF.
    CefShutdown();
    return ret;
}
           
这里的重点是 settings.browser_subprocess_path设置为执行体文件路径,去掉CefExecuteProcess 函数

5.3 编译整个解决方案

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

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

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

QT集成CEF07-理解CEF多进程1.任务管理器观察多进程2.Chromium的进程分类3.进程怎么启动4 单进程启动5 单一执行体与分离执行体

注意分离的 QyRender虽然名字有个render,但是它是可以启动除了 browser 进程以外所有进程的。

代码请访问 GitHub qt_cef_07分支

继续阅读