CEF3 使用多进程。其中包括:一个浏览器进程、一个渲染进程和若干其他进程(处理插件的进程、处理GUP的进程等)。
- 浏览器进程处理窗口的创建和绘制,以及网络访问等,浏览器进程包含了应用程序的主要逻辑。
- 渲染进程负责渲染 HTML 以及执行 JavaScript ,访问 DOM 等。
- 其他进程则进行插件处理,GPU 处理等(如果有的话)。
- 多个进程之间通过 IPC (Inter-Process Communication) 通信。
首先启动浏览器进程(Browser),然后启动渲染进程(Render),再启动其他进程。
默认情况下 CEF3 使用一个 exe 的多个实例来实现上述的多个进程。比如在 CEF3:用CEF3实现最简单的浏览器 中,在 WinMain 开始时会执行下面的代码:
// 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, NULL, NULL);
if (exit_code >= 0)
{
// The sub-process has completed so return here.
return exit_code;
}
这里 CefExecuteProcess() 会根据不同的命令行参数来执行不同的进程,如果是浏览器进程,该函数立即返回,返回值为 -1。如果是其他进程,则在浏览器退出时才返回,返回值是一个大于0的数。 运行结果如下图,可以看到同一个 exe 运行了三个实例。
也可以通过设置 CefSettings.browser_subprocess_path 来以不同的 exe 实现 CEF 的多个进程。下面通过示例说明这一点。
创建两个工程,一个命名为 BrowserProcess 作为浏览器进程,另一个命名为 BrowserSubProcess 作为其他子进程。工程的设置可以参见 CEF3:用CEF3实现最简单的浏览器。
在 BrowserProcess 工程下创建 main.cpp,编写如下代码。注意这里不再调用 CefExecuteProcess(),而是设置 CefSettings.browser_subprocess_path 的值来指定子进程路径。
#include "include/cef_app.h"
#include "include/cef_browser.h"
#include "include/cef_client.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
#include
class MyClient : public CefClient, public CefLifeSpanHandler
{
public:
virtual ~MyClient() {}
virtual CefRefPtr GetLifeSpanHandler() override { return this; }
virtual void OnBeforeClose(CefRefPtr browser) override { CefQuitMessageLoop(); }
private:
IMPLEMENT_REFCOUNTING(MyClient);
};
class MyApp : public CefApp, public CefBrowserProcessHandler
{
public:
virtual ~MyApp() {}
virtual CefRefPtr GetBrowserProcessHandler() override { return this; }
virtual void OnContextInitialized() override
{
CEF_REQUIRE_UI_THREAD();
CefWindowInfo window_info;
window_info.SetAsPopup(NULL, "cefsimple");
CefRefPtr client(new MyClient());
CefString url = "http://www.baidu.com";
CefBrowserSettings browser_settings;
CefBrowserHost::CreateBrowser(window_info, client, url, browser_settings, NULL);
}
private:
IMPLEMENT_REFCOUNTING(MyApp);
};
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
CefMainArgs main_args(hInstance);
CefSettings settings;
settings.no_sandbox = true;
// 获取子进程路径
WCHAR subProcessPath[MAX_PATH] = { 0 };
GetModuleFileNameW(hInstance, subProcessPath, MAX_PATH);
*(wcsrchr(subProcessPath, L'\\') + 1) = L'\0';
LPCWSTR SUB_PROCESS_NAME = L"BrowserSubProcess.exe";
wcsncat_s(subProcessPath, MAX_PATH, SUB_PROCESS_NAME, wcslen(SUB_PROCESS_NAME));
// 设置子进程路径
cef_string_from_wide(subProcessPath, MAX_PATH, &settings.browser_subprocess_path);
auto myApp = CefRefPtr(new MyApp());
CefInitialize(main_args, settings, myApp.get(), NULL);
CefRunMessageLoop();
CefShutdown();
return 0;
}
在 BrowserSubProcess 工程下创建 main.cpp,编写如下代码。代码相当简单,仅仅是调用了 CefExecuteProcess()。
#include "include/cef_app.h"
#include
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
CefMainArgs main_args(hInstance);
return CefExecuteProcess(main_args, NULL, NULL);
}
编译两个工程后,确保两个 exe 在同一目录下,运行,结果如下。可以看到运行了不同的 exe 。
一、如果你设置代码的断点,默认情况下,VS只会跟踪那些在主进程Browser代码中的那些断点。VS提供了"Attach To Process"的方法。比如当Render Process启动之后,可以用菜单"Debug"=>"Attach To Process"选项,选择那个新产生的进程,然后在你需要跟踪的代码处设置断点,就可以。但是这种方法,只能在子进程启动之后,才比较有效,如果我们想在子进程启动时,跟踪某些代码的执行,就没有办法了。
二、针对这个Chrome从源代码级别提供了支持。共有两种方法:
1. 用启动选项“--single-process“,以单进程的方法来启动Chrome。
2.用启动选项"--renderer-startup-dialog"和"--no-sandbox"。两个选项将会让子进程在启动之后,弹出一个模态对话框。之后在关闭这个对话框之后才可以继续运行代码。在这期间,我们可以用上述"Attach To Process"的方法来跟踪子进程代码的执行。
之所以要加上"--no-sandbox",是因为默认情况下Chrome的子进程的创建和启动是在sanbox中(也就说访问系统资源是严格限制的),无法显示模态对话框UI。
Render进程的主函数如下:
int RendererMain(const MainFunctionParams& parameters) {
const CommandLine& parsed_command_line = parameters.command_line_;
base::ScopedNSAutoreleasePool* pool = parameters.autorelease_pool_;
// This function allows pausing execution using the --renderer-startup-dialog
// flag allowing us to attach a debugger.
// Do not move this function down since that would mean we can't easily debug
// whatever occurs before it.
HandleRendererErrorTestParameters(parsed_command_line);
HandleRenderErrorTestParameters函数会显示这个模态对话框。
// This function provides some ways to test crash and assertion handling
// behavior of the renderer.
static void HandleRendererErrorTestParameters(const CommandLine& command_line) {
// This parameter causes an assertion.
if (command_line.HasSwitch(switches::kRendererAssertTest)) {
DCHECK(false);
}
// This parameter causes a null pointer crash (crash reporter trigger).
if (command_line.HasSwitch(switches::kRendererCrashTest)) {
int* bad_pointer = NULL;
*bad_pointer = 0;
}
if (command_line.HasSwitch(switches::kRendererStartupDialog)) {
#if defined(OS_WIN)
std::wstring title = l10n_util::GetString(IDS_PRODUCT_NAME);
title += L" renderer"; // makes attaching to process easier
::MessageBox(NULL, L"renderer starting...", title.c_str(),
MB_OK | MB_SETFOREGROUND);
#elif defined(OS_LINUX)