如何构建CEF多进程模式
上一篇介绍如何在win32项目中引入CEF。这一篇介绍一下,如何管理CEF的多进程模式,并进行调试。
CEF是一个浏览器控件,进一步说是多组接口集合。嵌入CEF就是实现基于接口的二次开发。但这些接口又比较特殊,因为是开源的,所以可以跟踪、调试、甚至根据自己的需要进行定制、修改。
接口就不做介绍了。下面就从项目由浅入深引入的CEF模块,分块做一下介绍。
-
CEF的多进程模式
如果把CEF结构大致分一下,会分出三大块来,browser、render、其他。CEF也支持单进程模式,可以通过设置信息修改。默认多进程模式。其中browser模块主要处理UI部分、Render部分主要做js解释运行、DOM结构解析,其他模块非必须,比如GPU加速渲染、第三方插件等。
-
管理多个进程
这里找不到一个合适的词,只能暂时用管理,其实不是管理,更贴切说是多进程的入口创建。先看一下CEF源码提供的demo。cefsimple实现了browser的接口,render使用默认行为
// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Enable High-DPI support on Windows 7 or newer.
CefEnableHighDPISupport();
void* sandbox_info = NULL;
#if defined(CEF_USE_SANDBOX)
// Manage the life span of the sandbox information object. This is necessary
// for sandbox support on Windows. See cef_sandbox_win.h for complete details.
CefScopedSandboxInfo scoped_sandbox;
sandbox_info = scoped_sandbox.sandbox_info();
#endif
// Provide CEF with command-line arguments.
CefMainArgs main_args(hInstance);
// 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, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
// Specify CEF global settings here.
CefSettings settings;
#if !defined(CEF_USE_SANDBOX)
settings.no_sandbox = true;
#endif
// 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;
}
再看一下cefclient,实现自己对browser和render行为管理
namespace client {
namespace {
int RunMain(HINSTANCE hInstance, int nCmdShow) {
// Enable High-DPI support on Windows 7 or newer.
CefEnableHighDPISupport();
CefMainArgs main_args(hInstance);
void* sandbox_info = NULL;
#if defined(CEF_USE_SANDBOX)
// Manage the life span of the sandbox information object. This is necessary
// for sandbox support on Windows. See cef_sandbox_win.h for complete details.
CefScopedSandboxInfo scoped_sandbox;
sandbox_info = scoped_sandbox.sandbox_info();
#endif
// Parse command-line arguments.
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(::GetCommandLineW());
// 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;
// Create the main context object.
scoped_ptr<MainContextImpl> context(new MainContextImpl(command_line, true));
CefSettings settings;
#if !defined(CEF_USE_SANDBOX)
settings.no_sandbox = true;
#endif
// Populate the settings based on command line arguments.
context->PopulateSettings(&settings);
// Create the main message loop object.
scoped_ptr<MainMessageLoop> message_loop;
if (settings.multi_threaded_message_loop)
message_loop.reset(new MainMessageLoopMultithreadedWin);
else if (settings.external_message_pump)
message_loop = MainMessageLoopExternalPump::Create();
else
message_loop.reset(new MainMessageLoopStd);
// Initialize CEF.
context->Initialize(main_args, settings, app, sandbox_info);
// Register scheme handlers.
test_runner::RegisterSchemeHandlers();
RootWindowConfig window_config;
window_config.with_controls =
!command_line->HasSwitch(switches::kHideControls);
window_config.with_osr = settings.windowless_rendering_enabled ? true : false;
// Create the first window.
context->GetRootWindowManager()->CreateRootWindow(window_config);
// Run the message loop. This will block until Quit() is called by the
// RootWindowManager after all windows have been destroyed.
int result = message_loop->Run();
// Shut down CEF.
context->Shutdown();
// Release objects in reverse order of creation.
message_loop.reset();
context.reset();
return result;
}
} // namespace
} // namespace client
// Program entry point function.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
return client::RunMain(hInstance, nCmdShow);
}
最主要的区别在这里
// 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();
默认都是多进程模式。下面的这种方式可以直接把进程attach到当前进程中就可以进行调试了。如果使用第一种方式创建进程,render等其他进程使用默认行为,调试的时候需要下载对应版本的cef.pdb。
- 单进程模式
可以通过参数进行设置,我试着使用了单进程,但一直无法调试,可能需要额外的pdb信息,时间紧迫也没时间做更多尝试。浏览器设置信息如下
// Specify CEF global settings here.
CefSettings settings;#if !defined(CEF_USE_SANDBOX)
settings.no_sandbox = true;
#endifCefSettingsTraits::init(&settings);
//这个设置能实现单进程运行Cef浏览器插件,我们实际应用中采用的是双进程模式,即一个主进程,一个render渲染进程
//settings.single_process = true;
//settings.no_sandbox = true;//设置渲染进程的名称,因为在相同目录下,没有指定路径
//CefString(&settings.browser_subprocess_path).FromWString(L"Render.exe");//禁用Cef的消息循环,采用DuiLib的消息循环
//settings.multi_threaded_message_loop = true;//设置本地语言
//CefString(&settings.locale).FromWString(L"zh-CN");//缓存数据路径
//CefString(&settings.cache_path).FromWString(cache_path);//debug日志路径
//CefString(&settings.log_file).FromWString(log_file);// Initialize CEF.
CefInitialize(main_args, settings, app.get(), sandbox_info);
以上信息是项目进行之余,临时做的记录和摘要,比较粗糙,希望还有时间完善。