天天看點

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分支

繼續閱讀