1.介绍
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md
翻译来自:cefutil
CEF全称Chromium Embedded Framework,是一个基于Google Chromium 的开源项目。Google Chromium项目主要是为Google Chrome应用开发的,而CEF的目标则是为第三方应用提供可嵌入浏览器支持。CEF隔离底层Chromium和Blink的复杂代码,并提供一套产品级稳定的API,发布跟踪具体Chromium版本的分支,以及二进制包。CEF的大部分特性都提供了丰富的默认实现,让使用者做尽量少的定制即可满足需求。在本文发布的时候,世界上已经有很多公司和机构采用CEF,CEF的安装量超过了100万。[CEF wikipedia]页面上有使用CEF的公司和机构的不完全的列表。CEF的典型应用场景包括:
-
嵌入一个兼容HTML5的浏览器控件到一个已经存在的本地应用。
-
创建一个轻量化的壳浏览器,用以托管主要用Web技术开发的应用。
-
有些应用有独立的绘制框架,使用CEF对Web内容做离线渲染。
-
使用CEF做自动化Web测试。
CEF3是基于Chomuim Content API多进程构架的下一代CEF,拥有下列优势:
-
改进的性能和稳定性(JavaScript和插件在一个独立的进程内执行)。
-
支持Retina显示器。
-
支持WebGL和3D CSS的GPU加速。
-
类似WebRTC和语音输入这样的前卫特性。
-
通过DevTools远程调试协议以及ChromeDriver2提供更好的自动化UI测试。
-
更快获得当前以及未来的Web特性和标准的能力。
本文档介绍CEF3开发中涉及到的一般概念。
2.开始
2.1.1.使用二进制包
CEF3的二进制包可以在这个页面下载。其中包含了在特定平台(Windows,Mac OS X 以及 Linux)编译特定版本CEF3所需的全部文件。不同平台拥有共同的结构:
-
cefclient
-
Debug
-
include
-
libcef_dll
-
Release
-
Resources
-
tools
每个二进制包包含一个README.txt文件和一个LICENSE.txt文件,README.txt用以描述平台相关的细节,而LICENSE.txt包含CEF的BSD版权说明。如果你发布了基于CEF的应用,则应该在应用程序的某个地方包含该版权声明。例如,你可以在"关于”和“授权"页面列出该版权声明,或者单独一个文档包含该版权声明。“关于”和“授权”信息也可以分别在CEF浏览器的"about:license"和"about:credits"页面查看。
基于CEF二进制包的应用程序可以使用每个平台上的经典编译工具。包括Windows平台上的Visual Studio,Mac OSX平台上的Xcode,以及Linux平台上的gcc/make编译工具链。CEF项目的下载页面包含了这些平台上编译特定版本CEF所需的编译工具的版本信息。在Linux上编译CEF时需要特别注意依赖工具链。
Tutorial Wiki页面有更多关于如何使用CEF3二进制包创建简单应用程序的细节。
2.1.2.从源码编译(Building from Source Code)
CEF可以从源码编译,用户可以使用本地编译系统或者像TeamCity这样的自动化编译系统编译。首先你需要使用svn或者git下载Chromium和CEF的源码。由于Chromium源码很大,只建议在内存大于4GB的现代机器上编译。编译Chromium和CEF的细节请参考BranchesAndBuilding页面。
2.1.3.示例应用程序(Sample Application)
cefclient是一个完整的CEF客户端应用程序示例,并且它的源码包含在CEF每个二进制发布包中。使用CEF创建一个新的应用程序,最简单的方法是先从cefclient应用程序开始,删除你不需要的部分。本文档中许多示例都是来源于cefclient应用程序。
2.1.4.重要概念(Important Concepts)
在开发基于CEF3的应用程序前,有一些重要的基础概念应该被理解。
2.1.4.1.C++ 封装(C++ Wrapper)
libcef 动态链接库导出 C API 使得使用者不用关心CEF运行库和基础代码。libcef_dll_wrapper 工程把 C API 封装成 C++ API同时包含在客户端应用程序工程中,与cefclient一样,源代码作为CEF二进制发布包的一部分共同发布。C/C++ API的转换层代码是由转换工具自动生成。UsingTheCAPI 页面描述了如何使用C API。
2.1.4.2.进程(Processes)
CEF3是多进程架构的。Browser被定义为主进程,负责窗口管理,界面绘制和网络交互。Blink的渲染和Js的执行被放在一个独立的Render 进程中;除此之外,Render进程还负责Js Binding和对Dom节点的访问。 默认的进程模型中,会为每个标签页创建一个新的Render进程。其他进程按需创建,例如管理插件的进程以及处理合成加速的进程等都是按需创建。
默认情况下,主应用程序会被多次启动运行各自独立的进程。这是通过传递不同的命令行参数给CefExecuteProcess函数做到的。如果主应用程序很大,加载时间比较长,或者不能在非浏览器进程里使用,则宿主程序可使用独立的可执行文件去运行这些进程。这可以通过配置CefSettings.browser_subprocess_path变量做到。更多细节请参考Application Structure一节。
CEF3的进程之间可以通过IPC进行通信。Browser和Render进程可以通过发送异步消息进行双向通信。甚至在Render进程可以注册在Browser进程响应的异步JavaScript API。 更多细节,请参考Inter-Process Communication一节。
通过设置命令行的--single-process
,CEF3就可以支持用于调试目的的单进程运行模型。支持的平台为:Windows,Mac OS X 和Linux。
2.1.4.3.线程(Threads)
在CEF3中,每个进程都会运行多个线程。完整的线程类型表请参照cef_thread_id_t。例如,在Browser进程中包含如下主要的线程:
-
TID_UI 线程是浏览器的主线程。如果应用程序在调用调用CefInitialize()时,传递CefSettings.multi_threaded_message_loop=false,这个线程也是应用程序的主线程。
-
TID_IO 线程主要负责处理IPC消息以及网络通信。
-
TID_FILE 线程负责与文件系统交互。
由于CEF采用多线程架构,有必要使用锁和闭包来保证数据的线程安全语义。IMPLEMENT_LOCKING定义提供了Lock()和Unlock()方法以及AutoLock对象来保证不同代码块同步访问数据。CefPostTask函数组支持简易的线程间异步消息传递。更多信息,请参考Posting Tasks章节。
可以通过CefCurrentlyOn()方法判断当前所在的线程环境,cefclient工程使用下面的定义来确保方法在期望的线程中被执行。
#define REQUIRE_UI_THREAD() ASSERT(CefCurrentlyOn(TID_UI));
#define REQUIRE_IO_THREAD() ASSERT(CefCurrentlyOn(TID_IO));
#define REQUIRE_FILE_THREAD() ASSERT(CefCurrentlyOn(TID_FILE));
2.1.4.4.引用计数(Reference Counting)
所有的框架类从CefBase继承,实例指针由CefRefPtr管理,CefRefPtr通过调用AddRef()和Release()方法自动管理引用计数。框架类的实现方式如下:
class MyClass : public CefBase {
public:
// Various class methods here...
private:
// Various class members here...
IMPLEMENT_REFCOUNTING(MyClass); // Provides atomic refcounting implementation.
};
// References a MyClass instance
CefRefPtr<MyClass> my_class = new MyClass();
2.1.4.5.字符串(Strings)
CEF为字符串定义了自己的数据结构。主要是出于以下原因:
-
libcef包和宿主程序可能使用不同的运行时,对堆管理的方式也不同。所有的对象,包括字符串,需要确保和申请堆内存使用相同的运行时环境。
-
libcef包可以编译为支持不同的字符串类型(UTF8,UTF16以及WIDE)。默认采用的是UTF16,默认字符集可以通过更改cef_string.h文件中的定义,然后重新编译来修改。当使用宽字节集的时候,切记字符的长度由当前使用的平台决定。
UTF16字符串结构体示例如下:
typedef struct _cef_string_utf16_t {
char16* str; // Pointer to the string
size_t length; // String length
void (*dtor)(char16* str); // Destructor for freeing the string on the correct heap
} cef_string_utf16_t;
通过typedef来设置常用的字符编码。
typedef char16 cef_char_t;
typedef cef_string_utf16_t cef_string_t;
CEF提供了一批C语言的方法来操作字符串(通过#define的方式来适应不同的字符编码)
-
cef_string_set 对制定的字符串变量赋值(支持深拷贝或浅拷贝)。
-
cef_string_clear 清空字符串。
-
cef_string_cmp 比较两个字符串。
CEF也提供了字符串不同编码之间相互转换的方法。具体函数列表请查阅cef_string.h和cef_string_types.h文件。
在C++中,通常使用CefString类来管理CEF的字符串。CefString支持与std::string(UTF8)、std::wstring(wide)类型的相互转换。也可以用来包裹一个cef_string_t结构来对其进行赋值。
和std::string的相互转换:
std::string str = “Some UTF8 string”;
// Equivalent ways of assigning |str| to |cef_str|. Conversion from UTF8 will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromString(str);
// Equivalent ways of assigning |cef_str| to |str|. Conversion to UTF8 will occur if necessary.
str = cef_str;
str = cef_str.ToString();
和std::wstring的相互转换:
std::wstring str = “Some wide string”;
// Equivalent ways of assigning |str| to |cef_str|. Conversion from wide will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromWString(str);
// Equivalent ways of assigning |cef_str| to |str|. Conversion to wide will occur if necessary.
str = cef_str;
str = cef_str.ToWString();
如果是ASCII编码,使用FromASCII进行赋值:
const char* cstr = “Some ASCII string”;
CefString cef_str;
cef_str.FromASCII(cstr);
一些结构体(比如CefSettings)含有cef_string_t类型的成员,CefString支持直接赋值给这些成员。
CefSettings settings;
const char* path = “/path/to/log.txt”;
// Equivalent assignments.
CefString(&settings.log_file).FromASCII(path);
cef_string_from_ascii(path, strlen(path), &settings.log_file);
2.1.4.6.命令行参数(Command Line Arguments)
在CEF3和Chromium中许多特性可以使用命令行参数进行配置。这些参数采用--some-argument[=optional-param]
形式,并通过CefExecuteProcess()和CefMainArgs结构(参考下面的应用资源布局章节)传递给CEF。在传递CefSettings结构给CefInitialize()之前,我们可以设置CefSettings.command_line_args_disabled为true来禁用对命令行参数的处理。如果想指定命令行参数传入主应用程序,实现CefApp::OnBeforeCommandLineProcessing()方法。更多关于如何查找已支持的命令行选项的信息,请查看client_switches.cpp文件的注释。
2.1.5.应用程序布局(Application Layout)
应用资源布局依赖于平台,有很大的不同。比如,在Mac OS X上,你的资源布局必须遵循特定的app bundles结构;Window与Linux则更灵活,允许你定制CEF库文件与资源文件所在的位置。为了获取到特定可以正常工作的示例,你可以从工程的下载页面下载到一个client压缩包。每个平台对应的README.txt文件详细说明了哪些文件是可选的,哪些文件是必须的。
2.1.5.1.Windows操作系统(Windows)
在Windows平台上,默认的资源布局将libcef库文件、相关资源与可执行文件放置在同级目录,文件夹结构大致如下:
Application/
cefclient.exe <= cefclient application executable
libcef.dll <= main CEF library
icudt.dll <= ICU unicode support library
ffmpegsumo.dll <= HTML5 audio/video support library
libEGL.dll, libGLESv2.dll, … <= accelerated compositing support libraries
cef.pak, devtools_resources.pak <= non-localized resources and strings
locales/
en-US.pak, … <= locale-specific resources and strings
使用结构体CefSettings可以定制CEF库文件、资源文件的位置(查看README.txt文件或者本文中CefSettings部分获取更详细的信息)。虽然在Windows平台上,cefclient项目将资源文件以二进制形式编译进cefclient.rc文件,但是改为从文件系统加载资源也很容易。
2.1.6.应用程序结构(Application Structure)
每个CEF3应用程序都是相同的结构
-
提供入口函数,用于初始化CEF、运行子进程执行逻辑或者CEF消息循环。
-
提供CefApp实现,用于处理进程相关的回调。
-
提供CefClient实现,用于处理Browser实例相关的回调。
-
执行CefBrowserHost::CreateBrowser()创建一个Browser实例,使用CefLifeSpanHandler管理Browser对象生命周期。
2.1.6.1.入口函数(Entry-Point Function)
像本文中进程章节描述的那样,一个CEF3应用程序会运行多个进程,这些进程能够使用同一个执行器或者为子进程定制的、单独的执行器。进程的执行从入口函数开始,示例cefclient_win.cc、cefclient_gtk.cc、cefclient_mac.mm分别对应Windows、Linux和Mac OS-X平台下的实现。
当执行子进程时,CEF将使用命令行参数指定配置信息,这些命令行参数必须通过CefMainArgs结构体传入到CefExecuteProcess函数。CefMainArgs的定义与平台相关,在Linux、Mac OS X平台下,它接收main函数传入的argc和argv参数值。
CefMainArgs main_args(argc, argv);
在Windows平台下,它接收wWinMain函数传入的参数:实例句柄(HINSTANCE),这个实例能够通过函数GetModuleHandle(NULL)获取。
CefMainArgs main_args(hInstance);
2.1.7.单一执行体(Single Executable)
当以单一执行体运行时,根据不同的进程类型,入口函数有差异。Windows、Linux平台支持单一执行体架构,Mac OS X平台则不行。
int main(int argc, char* argv[]) {
// Structure for passing command-line arguments.
// The definition of this structure is platform-specific.
CefMainArgs main_args(argc, argv);
// Optional implementation of the CefApp interface.
CefRefPtr<MyApp> app(new MyApp);
// Execute the sub-process logic, if any. This will either return immediately for the browser
// process or block until the sub-process should exit.
int exit_code = CefExecuteProcess(main_args, app.get());
if (exit_code >= 0) {
// The sub-process terminated, exit now.
return exit_code;
}
// Populate this structure to customize CEF behavior.
CefSettings settings;
// Initialize CEF in the main process.
CefInitialize(main_args, settings, app.get());
// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
CefRunMessageLoop();