CEF General Usage(CEF3预览)

介绍

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开发中涉及到的一般概念。

开始

使用二进制包

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二进制包创建简单应用程序的细节。

从源码编译(Building from Source Code)

CEF可以从源码编译,用户可以使用本地编译系统或者像TeamCity这样的自动化编译系统编译。首先你需要使用svn或者git下载Chromium和CEF的源码。由于Chromium源码很大,只建议在内存大于4GB的现代机器上编译。编译Chromium和CEF的细节请参考BranchesAndBuilding页面。

示例应用程序(Sample Application)

cefclient是一个完整的CEF客户端应用程序示例,并且它的源码包含在CEF每个二进制发布包中。使用CEF创建一个新的应用程序,最简单的方法是先从cefclient应用程序开始,删除你不需要的部分。本文档中许多示例都是来源于cefclient应用程序。

重要概念(Important Concepts)

在开发基于CEF3的应用程序前,有一些重要的基础概念应该被理解。

C++ 封装(C++ Wrapper)

libcef 动态链接库导出 C API 使得使用者不用关心CEF运行库和基础代码。libcef_dll_wrapper 工程把 C API 封装成 C++ API同时包含在客户端应用程序工程中,与cefclient一样,源代码作为CEF二进制发布包的一部分共同发布。C/C++ API的转换层代码是由转换工具自动生成。UsingTheCAPI 页面描述了如何使用C API。

进程(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。

线程(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工程使用下面的定义来确保方法在期望的线程中被执行。

  1. #define REQUIRE_UI_THREAD() ASSERT(CefCurrentlyOn(TID_UI));
  2. #define REQUIRE_IO_THREAD() ASSERT(CefCurrentlyOn(TID_IO));
  3. #define REQUIRE_FILE_THREAD() ASSERT(CefCurrentlyOn(TID_FILE));
引用计数(Reference Counting)

所有的框架类从CefBase继承,实例指针由CefRefPtr管理,CefRefPtr通过调用AddRef()和Release()方法自动管理引用计数。框架类的实现方式如下:

  1. class MyClass : public CefBase {
  2. public:
  3. // Various class methods here...
  4. private:
  5. // Various class members here...
  6. IMPLEMENT_REFCOUNTING(MyClass); // Provides atomic refcounting implementation.
  7. };
  8. // References a MyClass instance
  9. CefRefPtr<MyClass> my_class = new MyClass();
字符串(Strings)

CEF为字符串定义了自己的数据结构。主要是出于以下原因:

  • libcef包和宿主程序可能使用不同的运行时,对堆管理的方式也不同。所有的对象,包括字符串,需要确保和申请堆内存使用相同的运行时环境。
  • libcef包可以编译为支持不同的字符串类型(UTF8,UTF16以及WIDE)。默认采用的是UTF16,默认字符集可以通过更改cef_string.h文件中的定义,然后重新编译来修改。当使用宽字节集的时候,切记字符的长度由当前使用的平台决定。

UTF16字符串结构体示例如下:

  1. typedef struct _cef_string_utf16_t {
  2. char16* str; // Pointer to the string
  3. size_t length; // String length
  4. void (*dtor)(char16* str); // Destructor for freeing the string on the correct heap
  5. } cef_string_utf16_t;

通过typedef来设置常用的字符编码。

  1. typedef char16 cef_char_t;
  2. 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的相互转换:

  1. std::string str = Some UTF8 string”;
  2. // Equivalent ways of assigning |str| to |cef_str|. Conversion from UTF8 will occur if necessary.
  3. CefString cef_str(str);
  4. cef_str = str;
  5. cef_str.FromString(str);
  6. // Equivalent ways of assigning |cef_str| to |str|. Conversion to UTF8 will occur if necessary.
  7. str = cef_str;
  8. str = cef_str.ToString();

和std::wstring的相互转换:

  1. std::wstring str = Some wide string”;
  2. // Equivalent ways of assigning |str| to |cef_str|. Conversion from wide will occur if necessary.
  3. CefString cef_str(str);
  4. cef_str = str;
  5. cef_str.FromWString(str);
  6. // Equivalent ways of assigning |cef_str| to |str|. Conversion to wide will occur if necessary.
  7. str = cef_str;
  8. str = cef_str.ToWString();

如果是ASCII编码,使用FromASCII进行赋值:

  1. const char* cstr = Some ASCII string”;
  2. CefString cef_str;
  3. cef_str.FromASCII(cstr);

一些结构体(比如CefSettings)含有cef_string_t类型的成员,CefString支持直接赋值给这些成员。

  1. CefSettings settings;
  2. const char* path = “/path/to/log.txt”;
  3. // Equivalent assignments.
  4. CefString(&settings.log_file).FromASCII(path);
  5. cef_string_from_ascii(path, strlen(path), &settings.log_file);
命令行参数(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文件的注释。

应用程序布局(Application Layout)

应用资源布局依赖于平台,有很大的不同。比如,在Mac OS X上,你的资源布局必须遵循特定的app bundles结构;Window与Linux则更灵活,允许你定制CEF库文件与资源文件所在的位置。为了获取到特定可以正常工作的示例,你可以从工程的下载页面下载到一个client压缩包。每个平台对应的README.txt文件详细说明了哪些文件是可选的,哪些文件是必须的。

Windows操作系统(Windows)

在Windows平台上,默认的资源布局将libcef库文件、相关资源与可执行文件放置在同级目录,文件夹结构大致如下:

  1. Application/
  2. cefclient.exe <= cefclient application executable
  3. libcef.dll <= main CEF library
  4. icudt.dll <= ICU unicode support library
  5. ffmpegsumo.dll <= HTML5 audio/video support library
  6. libEGL.dll, libGLESv2.dll, <= accelerated compositing support libraries
  7. cef.pak, devtools_resources.pak <= non-localized resources and strings
  8. locales/
  9. en-US.pak, <= locale-specific resources and strings

使用结构体CefSettings可以定制CEF库文件、资源文件的位置(查看README.txt文件或者本文中CefSettings部分获取更详细的信息)。虽然在Windows平台上,cefclient项目将资源文件以二进制形式编译进cefclient.rc文件,但是改为从文件系统加载资源也很容易。

Linux操作系统(Linux)

在Linux平台上,默认的资源布局将libcef库文件、相关资源与可执行文件放置在同级目录。注意:在你编译的版本与发行版本应用程序中,libcef.so的位置是有差异的,此文件的位置取决于编译可执行程序时,编译器rpath的值。比如,编译选项为“-Wl,-rpath,.”(“.”意思是当前文件夹),这样libcef.so与可执行文件处于同级目录。libcef.so文件的路径可以通过环境变量中的“LD_LIBRARY_PATH”指定。

  1. Application/
  2. cefclient <= cefclient application executable
  3. libcef.so <= main CEF library
  4. ffmpegsumo.so <-- HTML5 audio/video support library
  5. cef.pak, devtools_resources.pak <= non-localized resources and strings
  6. locales/
  7. en-US.pak, <= locale-specific resources and strings
  8. files/
  9. binding.html, <= cefclient application resources

使用结构体CefSettings可以定制CEF库文件、资源文件(查看README.txt文件或者本文中CefSettings部分获取更详细的信息)。

Mac X平台(Mac OS X)

在Mac X平台上,app bundles委托给了Chromium实现,因此不是很灵活。文件夹结构大致如下:

  1. cefclient.app/
  2. Contents/
  3. Frameworks/
  4. Chromium Embedded Framework.framework/
  5. Libraries/
  6. ffmpegsumo.so <= HTML5 audio/video support library
  7. libcef.dylib <= main CEF library
  8. Resources/
  9. cef.pak, devtools_resources.pak <= non-localized resources and strings
  10. *.png, *.tiff <= Blink image and cursor resources
  11. en.lproj/, <= locale-specific resources and strings
  12. libplugin_carbon_interpose.dylib <= plugin support library
  13. cefclient Helper.app/
  14. Contents/
  15. Info.plist
  16. MacOS/
  17. cefclient Helper <= helper executable
  18. Pkginfo
  19. cefclient Helper EH.app/
  20. Contents/
  21. Info.plist
  22. MacOS/
  23. cefclient Helper EH <= helper executable
  24. Pkginfo
  25. cefclient Helper NP.app/
  26. Contents/
  27. Info.plist
  28. MacOS/
  29. cefclient Helper NP <= helper executable
  30. Pkginfo
  31. Info.plist
  32. MacOS/
  33. cefclient <= cefclient application executable
  34. Pkginfo
  35. Resources/
  36. binding.html, <= cefclient application resources

列表中的“Chromium Embedded Framework.framework”,这个未受版本管控的框架包含了所有的CEF库文件、资源文件。使用install_name_tool与@executable_path,将cefclient,cefclient helper等可执行文件,连接到了libcef.dylib上。

应用程序cefclient helper用来执行不同特点、独立的进程(Renderer,plugin等),这些进程需要独立的资源布局与Info.plist等文件,它们没有显示停靠图标。用来启动插件进程的EH Helper清除了MH_NO_HEAP_EXECUTION标志位,这样就允许一个可执行堆。只能用来启动NaCL插件进程的NP Helper,清除了MH_PIE标志位,这样就禁用了ASLR。这些都是tools文件夹下面,用来构建进程脚本的一部分。为了理清脚本的依赖关系,更好的做法是检查发行版本中的Xcode工程或者原始文件cefclient.gyp。

应用程序结构(Application Structure)

每个CEF3应用程序都是相同的结构

  • 提供入口函数,用于初始化CEF、运行子进程执行逻辑或者CEF消息循环。
  • 提供CefApp实现,用于处理进程相关的回调。
  • 提供CefClient实现,用于处理Browser实例相关的回调。
  • 执行CefBrowserHost::CreateBrowser()创建一个Browser实例,使用CefLifeSpanHandler管理Browser对象生命周期。
入口函数(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参数值。

  1. CefMainArgs main_args(argc, argv);

在Windows平台下,它接收wWinMain函数传入的参数:实例句柄(HINSTANCE),这个实例能够通过函数GetModuleHandle(NULL)获取。

  1. CefMainArgs main_args(hInstance);

单一执行体(Single Executable)

当以单一执行体运行时,根据不同的进程类型,入口函数有差异。Windows、Linux平台支持单一执行体架构,Mac OS X平台则不行。

  1. int main(int argc, char* argv[]) {
  2. // Structure for passing command-line arguments.
  3. // The definition of this structure is platform-specific.
  4. CefMainArgs main_args(argc, argv);
  5. // Optional implementation of the CefApp interface.
  6. CefRefPtr<MyApp> app(new MyApp);
  7. // Execute the sub-process logic, if any. This will either return immediately for the browser
  8. // process or block until the sub-process should exit.
  9. int exit_code = CefExecuteProcess(main_args, app.get());
  10. if (exit_code >= 0) {
  11. // The sub-process terminated, exit now.
  12. return exit_code;
  13. }
  14. // Populate this structure to customize CEF behavior.
  15. CefSettings settings;
  16. // Initialize CEF in the main process.
  17. CefInitialize(main_args, settings, app.get());
  18. // Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
  19. CefRunMessageLoop();
  20. // Shut down CEF.
  21. CefShutdown();
  22. return 0;
  23. }

分离子进程执行体(Separate Sub-Process Executable)

当使用独立的子进程执行体时,你需要2个分开的可执行工程和2个分开的入口函数。

主程序的入口函数:

  1. // Program entry-point function.
  2. // 程序入口函数
  3. int main(int argc, char* argv[]) {
  4. // Structure for passing command-line arguments.
  5. // The definition of this structure is platform-specific.
  6. // 传递命令行参数的结构体。
  7. // 这个结构体的定义与平台相关。
  8. CefMainArgs main_args(argc, argv);
  9. // Optional implementation of the CefApp interface.
  10. // 可选择性地实现CefApp接口
  11. CefRefPtr<MyApp> app(new MyApp);
  12. // Populate this structure to customize CEF behavior.
  13. // 填充这个结构体,用于定制CEF的行为。
  14. CefSettings settings;
  15. // Specify the path for the sub-process executable.
  16. // 指定子进程的执行路径
  17. CefString(&settings.browser_subprocess_path).FromASCII(“/path/to/subprocess”);
  18. // Initialize CEF in the main process.
  19. // 在主进程中初始化CEF
  20. CefInitialize(main_args, settings, app.get());
  21. // Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
  22. // 执行消息循环,此时会堵塞,直到CefQuitMessageLoop()函数被调用。
  23. CefRunMessageLoop();
  24. // Shut down CEF.
  25. // 关闭CEF
  26. CefShutdown();
  27. return 0;
  28. }

子进程程序的入口函数:

  1. // Program entry-point function.
  2. // 程序入口函数
  3. int main(int argc, char* argv[]) {
  4. // Structure for passing command-line arguments.
  5. // The definition of this structure is platform-specific.
  6. // 传递命令行参数的结构体。
  7. // 这个结构体的定义与平台相关。
  8. CefMainArgs main_args(argc, argv);
  9. // Optional implementation of the CefApp interface.
  10. // 可选择性地实现CefApp接口
  11. CefRefPtr<MyApp> app(new MyApp);
  12. // Execute the sub-process logic. This will block until the sub-process should exit.
  13. // 执行子进程逻辑,此时会堵塞直到子进程退出。
  14. return CefExecuteProcess(main_args, app.get());
  15. }

集成消息循环(Message Loop Integration)

CEF可以不用它自己提供的消息循环,而与已经存在的程序中消息环境集成在一起,有两种方式可以做到:

  1. 周期性执行CefDoMessageLoopWork()函数,替代调用CefRunMessageLoop()。CefDoMessageLoopWork()的每一次调用,都将执行一次CEF消息循环的单次迭代。需要注意的是,此方法调用次数太少时,CEF消息循环会饿死,将极大的影响Browser的性能,调用次数太频繁又将影响CPU使用率。

  2. 设置CefSettings.multi_threaded_message_loop=true(Windows平台下有效),这个设置项将导致CEF在单独的线程上运行Browser的界面,而不是在主线程上,这种场景下CefDoMessageLoopWork()或者CefRunMessageLoop()都不需要调用,CefInitialze()、CefShutdown()仍然在主线程中调用。你需要提供主程序线程通信的机制(查看cefclient_win.cpp中提供的消息窗口实例)。在Windows平台下,你可以通过命令行参数--multi-threaded-message-loop测试上述消息模型。

CefSettings

CefSettings结构体允许定义全局的CEF配置,经常用到的配置项如下:

  • single_process 设置为true时,Browser和Renderer使用一个进程。此项也可以通过命令行参数“single-process”配置。查看本文中“进程”章节获取更多的信息。
  • browser_subprocess_path 设置用于启动子进程单独执行器的路径。参考本文中单进程执行体章节获取更多的信息。
  • cache_path 设置磁盘上用于存放缓存数据的位置。如果此项为空,某些功能将使用内存缓存,多数功能将使用临时的磁盘缓存。形如本地存储的HTML5数据库只能在设置了缓存路径才能跨session存储。
  • locale 此设置项将传递给Blink。如果此项为空,将使用默认值“en-US”。在Linux平台下此项被忽略,使用环境变量中的值,解析的依次顺序为:LANGUAE,LC_ALL,LC_MESSAGES和LANG。此项也可以通过命令行参数“lang”配置。
  • log_file 此项设置的文件夹和文件名将用于输出debug日志。如果此项为空,默认的日志文件名为debug.log,位于应用程序所在的目录。此项也可以通过命令参数“log-file”配置。
  • log_severity 此项设置日志级别。只有此等级、或者比此等级高的日志的才会被记录。此项可以通过命令行参数“log-severity”配置,可以设置的值为“verbose”,“info”,“warning”,“error”,“error-report”,“disable”。
  • resources_dir_path 此项设置资源文件夹的位置。如果此项为空,Windows平台下cef.pak、Linux平台下devtools_resourcs.pak、Mac OS X下的app bundle Resources目录必须位于组件目录。此项也可以通过命令行参数“resource-dir-path”配置。
  • locales_dir_path 此项设置locale文件夹位置。如果此项为空,locale文件夹必须位于组件目录,在Mac OS X平台下此项被忽略,pak文件从app bundle Resources目录。此项也可以通过命令行参数“locales-dir-path”配置。
  • remote_debugging_port 此项可以设置1024-65535之间的值,用于在指定端口开启远程调试。例如,如果设置的值为8080,远程调试的URL为http://localhost:8080。CEF或者Chrome浏览器能够调试CEF。此项也可以通过命令行参数“remote-debugging-port”配置。

CefBrowser和CefFrame

CefBrowser和CefFrame对象被用来发送命令给浏览器以及在回调函数里获取状态信息。每个CefBrowser对象包含一个主CefFrame对象,主CefFrame对象代表页面的顶层frame;同时每个CefBrowser对象可以包含零个或多个的CefFrame对象,分别代表不同的子Frame。例如,一个浏览器加载了两个iframe,则该CefBrowser对象拥有三个CefFrame对象(顶层frame和两个iframe)。

下面的代码在浏览器的主frame里加载一个URL:

  1. browser->GetMainFrame()->LoadURL(some_url);

下面的代码执行浏览器的回退操作:

  1. browser->GoBack();

下面的代码从主frame里获取HTML内容:

  1. // Implementation of the CefStringVisitor interface.
  2. class Visitor : public CefStringVisitor {
  3. public:
  4. Visitor() {}
  5. // Called asynchronously when the HTML contents are available.
  6. virtual void Visit(const CefString& string) OVERRIDE {
  7. // Do something with |string|...
  8. }
  9. IMPLEMENT_REFCOUNTING(Visitor);
  10. };
  11. browser->GetMainFrame()->GetSource(new Visitor());

CefBrowser和CefFrame对象在Browser进程和Render进程都有对等的代理对象。在Browser进程里,Host(宿主)行为控制可以通过CefBrowser::GetHost()方法控制。例如,浏览器窗口的原生句柄可以用下面的代码获取:

  1. // CefWindowHandle is defined as HWND on Windows, NSView* on Mac OS X
  2. // and GtkWidget* on Linux.
  3. CefWindowHandle window_handle = browser->GetHost()->GetWindowHandle();

其他方法包括历史导航,加载字符串和请求,发送编辑命令,提取text/html内容等。请参考支持函数相关的文档或者CefBrowser的头文件注释。

CefApp

CefApp接口提供了不同进程的可定制回调函数。毕竟重要的回调函数如下:

  • OnBeforeCommandLineProcessing 提供了以编程方式设置命令行参数的机会,更多细节,请参考Command Line Arguments一节。
  • OnRegisterCustomSchemes 提供了注册自定义schemes的机会,更多细节,请参考Request Handling一节。
  • GetBrowserProcessHandler 返回定制Browser进程的Handler,该Handler包括了诸如OnContextInitialized的回调。
  • GetRenderProcessHandler 返回定制Render进程的Handler,该Handler包含了JavaScript相关的一些回调以及消息处理的回调。 更多细节,请参考JavascriptIntegrationInter-Process Communication两节。

CefApp子类的例子:

  1. // MyApp implements CefApp and the process-specific interfaces.
  2. class MyApp : public CefApp,
  3. public CefBrowserProcessHandler,
  4. public CefRenderProcessHandler {
  5. public:
  6. MyApp() {}
  7. // CefApp methods. Important to return |this| for the handler callbacks.
  8. virtual void OnBeforeCommandLineProcessing(
  9. const CefString& process_type,
  10. CefRefPtr<CefCommandLine> command_line) {
  11. // Programmatically configure command-line arguments...
  12. }
  13. virtual void OnRegisterCustomSchemes(
  14. CefRefPtr<CefSchemeRegistrar> registrar) OVERRIDE {
  15. // Register custom schemes...
  16. }
  17. virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
  18. OVERRIDE { return this; }
  19. virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler()
  20. OVERRIDE { return this; }
  21. // CefBrowserProcessHandler methods.
  22. virtual void OnContextInitialized() OVERRIDE {
  23. // The browser process UI thread has been initialized...
  24. }
  25. virtual void OnRenderProcessThreadCreated(CefRefPtr<CefListValue> extra_info)
  26. OVERRIDE {
  27. // Send startup information to a new render process...
  28. }
  29. // CefRenderProcessHandler methods.
  30. virtual void OnRenderThreadCreated(CefRefPtr<CefListValue> extra_info)
  31. OVERRIDE {
  32. // The render process main thread has been initialized...
  33. // Receive startup information in the new render process...
  34. }
  35. virtual void OnWebKitInitialized(CefRefPtr<ClientApp> app) OVERRIDE {
  36. // WebKit has been initialized, register V8 extensions...
  37. }
  38. virtual void OnBrowserCreated(CefRefPtr<CefBrowser> browser) OVERRIDE {
  39. // Browser created in this render process...
  40. }
  41. virtual void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) OVERRIDE {
  42. // Browser destroyed in this render process...
  43. }
  44. virtual bool OnBeforeNavigation(CefRefPtr<CefBrowser> browser,
  45. CefRefPtr<CefFrame> frame,
  46. CefRefPtr<CefRequest> request,
  47. NavigationType navigation_type,
  48. bool is_redirect) OVERRIDE {
  49. // Allow or block different types of navigation...
  50. }
  51. virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
  52. CefRefPtr<CefFrame> frame,
  53. CefRefPtr<CefV8Context> context) OVERRIDE {
  54. // JavaScript context created, add V8 bindings here...
  55. }
  56. virtual void OnContextReleased(CefRefPtr<CefBrowser> browser,
  57. CefRefPtr<CefFrame> frame,
  58. CefRefPtr<CefV8Context> context) OVERRIDE {
  59. // JavaScript context released, release V8 references here...
  60. }
  61. virtual bool OnProcessMessageReceived(
  62. CefRefPtr<CefBrowser> browser,
  63. CefProcessId source_process,
  64. CefRefPtr<CefProcessMessage> message) OVERRIDE {
  65. // Handle IPC messages from the browser process...
  66. }
  67. IMPLEMENT_REFCOUNTING(MyApp);
  68. };

CefClient

CefClient提供访问Browser实例的回调接口。一个CefClient实现可以在任意数量的Browser进程中共享。以下为几个重要的回调:

  • 比如处理Browser的生命周期,右键菜单,对话框,通知显示, 拖曳事件,焦点事件,键盘事件等等。如果没有对某个特定的处理接口进行实现会造成什么影响,请查看cef_client.h文件中相关说明。
  • OnProcessMessageReceived在Browser收到Render进程的消息时被调用。更多细节,请参考Inter-Process Communication一节。

CefClient子类的例子:

  1. // MyHandler implements CefClient and a number of other interfaces.
  2. class MyHandler : public CefClient,
  3. public CefContextMenuHandler,
  4. public CefDisplayHandler,
  5. public CefDownloadHandler,
  6. public CefDragHandler,
  7. public CefGeolocationHandler,
  8. public CefKeyboardHandler,
  9. public CefLifeSpanHandler,
  10. public CefLoadHandler,
  11. public CefRequestHandler {
  12. public:
  13. MyHandler();
  14. // CefClient methods. Important to return |this| for the handler callbacks.
  15. virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() OVERRIDE {
  16. return this;
  17. }
  18. virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE {
  19. return this;
  20. }
  21. virtual CefRefPtr<CefDownloadHandler> GetDownloadHandler() OVERRIDE {
  22. return this;
  23. }
  24. virtual CefRefPtr<CefDragHandler> GetDragHandler() OVERRIDE {
  25. return this;
  26. }
  27. virtual CefRefPtr<CefGeolocationHandler> GetGeolocationHandler() OVERRIDE {
  28. return this;
  29. }
  30. virtual CefRefPtr<CefKeyboardHandler> GetKeyboardHandler() OVERRIDE {
  31. return this;
  32. }
  33. virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
  34. return this;
  35. }
  36. virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE {
  37. return this;
  38. }
  39. virtual CefRefPtr<CefRequestHandler> GetRequestHandler() OVERRIDE {
  40. return this;
  41. }
  42. virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
  43. CefProcessId source_process,
  44. CefRefPtr<CefProcessMessage> message)
  45. OVERRIDE {
  46. // Handle IPC messages from the render process...
  47. }
  48. // CefContextMenuHandler methods
  49. virtual void OnBeforeContextMenu(CefRefPtr<CefBrowser> browser,
  50. CefRefPtr<CefFrame> frame,
  51. CefRefPtr<CefContextMenuParams> params,
  52. CefRefPtr<CefMenuModel> model) OVERRIDE {
  53. // Customize the context menu...
  54. }
  55. virtual bool OnContextMenuCommand(CefRefPtr<CefBrowser> browser,
  56. CefRefPtr<CefFrame> frame,
  57. CefRefPtr<CefContextMenuParams> params,
  58. int command_id,
  59. EventFlags event_flags) OVERRIDE {
  60. // Handle a context menu command...
  61. }
  62. // CefDisplayHandler methods
  63. virtual void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
  64. bool isLoading,
  65. bool canGoBack,
  66. bool canGoForward) OVERRIDE {
  67. // Update UI for browser state...
  68. }
  69. virtual void OnAddressChange(CefRefPtr<CefBrowser> browser,
  70. CefRefPtr<CefFrame> frame,
  71. const CefString& url) OVERRIDE {
  72. // Update the URL in the address bar...
  73. }
  74. virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,
  75. const CefString& title) OVERRIDE {
  76. // Update the browser window title...
  77. }
  78. virtual bool OnConsoleMessage(CefRefPtr<CefBrowser> browser,
  79. const CefString& message,
  80. const CefString& source,
  81. int line) OVERRIDE {
  82. // Log a console message...
  83. }
  84. // CefDownloadHandler methods
  85. virtual void OnBeforeDownload(
  86. CefRefPtr<CefBrowser> browser,
  87. CefRefPtr<CefDownloadItem> download_item,
  88. const CefString& suggested_name,
  89. CefRefPtr<CefBeforeDownloadCallback> callback) OVERRIDE {
  90. // Specify a file path or cancel the download...
  91. }
  92. virtual void OnDownloadUpdated(
  93. CefRefPtr<CefBrowser> browser,
  94. CefRefPtr<CefDownloadItem> download_item,
  95. CefRefPtr<CefDownloadItemCallback> callback) OVERRIDE {
  96. // Update the download status...
  97. }
  98. // CefDragHandler methods
  99. virtual bool OnDragEnter(CefRefPtr<CefBrowser> browser,
  100. CefRefPtr<CefDragData> dragData,
  101. DragOperationsMask mask) OVERRIDE {
  102. // Allow or deny drag events...
  103. }
  104. // CefGeolocationHandler methods
  105. virtual void OnRequestGeolocationPermission(
  106. CefRefPtr<CefBrowser> browser,
  107. const CefString& requesting_url,
  108. int request_id,
  109. CefRefPtr<CefGeolocationCallback> callback) OVERRIDE {
  110. // Allow or deny geolocation API access...
  111. }
  112. // CefKeyboardHandler methods
  113. virtual bool OnPreKeyEvent(CefRefPtr<CefBrowser> browser,
  114. const CefKeyEvent& event,
  115. CefEventHandle os_event,
  116. bool* is_keyboard_shortcut) OVERRIDE {
  117. // Perform custom handling of key events...
  118. }
  119. // CefLifeSpanHandler methods
  120. virtual bool OnBeforePopup(CefRefPtr<CefBrowser> browser,
  121. CefRefPtr<CefFrame> frame,
  122. const CefString& target_url,
  123. const CefString& target_frame_name,
  124. const CefPopupFeatures& popupFeatures,
  125. CefWindowInfo& windowInfo,
  126. CefRefPtr<CefClient>& client,
  127. CefBrowserSettings& settings,
  128. bool* no_javascript_access) OVERRIDE {
  129. // Allow or block popup windows, customize popup window creation...
  130. }
  131. virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE {
  132. // Browser window created successfully...
  133. }
  134. virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE {
  135. // Allow or block browser window close...
  136. }
  137. virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE {
  138. // Browser window is closed, perform cleanup...
  139. }
  140. // CefLoadHandler methods
  141. virtual void OnLoadStart(CefRefPtr<CefBrowser> browser,
  142. CefRefPtr<CefFrame> frame) OVERRIDE {
  143. // A frame has started loading content...
  144. }
  145. virtual void OnLoadEnd(CefRefPtr<CefBrowser> browser,
  146. CefRefPtr<CefFrame> frame,
  147. int httpStatusCode) OVERRIDE {
  148. // A frame has finished loading content...
  149. }
  150. virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
  151. CefRefPtr<CefFrame> frame,
  152. ErrorCode errorCode,
  153. const CefString& errorText,
  154. const CefString& failedUrl) OVERRIDE {
  155. // A frame has failed to load content...
  156. }
  157. virtual void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser,
  158. TerminationStatus status) OVERRIDE {
  159. // A render process has crashed...
  160. }
  161. // CefRequestHandler methods
  162. virtual CefRefPtr<CefResourceHandler> GetResourceHandler(
  163. CefRefPtr<CefBrowser> browser,
  164. CefRefPtr<CefFrame> frame,
  165. CefRefPtr<CefRequest> request) OVERRIDE {
  166. // Optionally intercept resource requests...
  167. }
  168. virtual bool OnQuotaRequest(CefRefPtr<CefBrowser> browser,
  169. const CefString& origin_url,
  170. int64 new_size,
  171. CefRefPtr<CefQuotaCallback> callback) OVERRIDE {
  172. // Allow or block quota requests...
  173. }
  174. virtual void OnProtocolExecution(CefRefPtr<CefBrowser> browser,
  175. const CefString& url,
  176. bool& allow_os_execution) OVERRIDE {
  177. // Handle execution of external protocols...
  178. }
  179. IMPLEMENT_REFCOUNTING(MyHandler);
  180. };

Browser生命周期(Browser Life Span)

Browser生命周期从执行 CefBrowserHost::CreateBrowser() 或者 CefBrowserHost::CreateBrowserSync() 开始。可以在CefBrowserProcessHandler::OnContextInitialized() 回调或者特殊平台例如windows的WM_CREATE 中方便的执行业务逻辑。

  1. // Information about the window that will be created including parenting, size, etc.
  2. // The definition of this structure is platform-specific.
  3. // 定义的结构体与平台相关
  4. CefWindowInfo info;
  5. // On Windows for example...
  6. info.SetAsChild(parent_hwnd, client_rect);
  7. // Customize this structure to control browser behavior.
  8. CefBrowserSettings settings;
  9. // CefClient implementation.
  10. CefRefPtr<MyClient> client(new MyClient);
  11. // Create the browser asynchronously. Initially loads the Google URL.
  12. CefBrowserHost::CreateBrowser(info, client.get(), http://www.google.com”, settings);
  13. The CefLifeSpanHandler class provides the callbacks necessary for managing browser life span. Below is an extract of the relevant methods and members.
  14. CefLifeSpanHandler 类提供管理 Browser生命周期必需的回调。以下为相关方法和成员。
  15. class MyClient : public CefClient,
  16. public CefLifeSpanHandler,
  17. ... {
  18. // CefClient methods.
  19. virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
  20. return this;
  21. }
  22. // CefLifeSpanHandler methods.
  23. void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
  24. bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
  25. void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
  26. // Member accessors.
  27. CefRefPtr<CefBrowser> GetBrower() { return m_Browser; }
  28. bool IsClosing() { return m_bIsClosing; }
  29. private:
  30. CefRefPtr<CefBrowser> m_Browser;
  31. int m_BrowserId;
  32. int m_BrowserCount;
  33. bool m_bIsClosing;
  34. IMPLEMENT_REFCOUNTING(MyHandler);
  35. IMPLEMENT_LOCKING(MyHandler);
  36. };

当Browser对象创建后OnAfterCreated() 方法立即执行。宿主程序可以用这个方法来保持对Browser对象的引用。

  1. void MyClient::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
  2. // Must be executed on the UI thread.
  3. REQUIRE_UI_THREAD();
  4. // Protect data members from access on multiple threads.
  5. AutoLock lock_scope(this);
  6. if (!m_Browser.get()) {
  7. // Keep a reference to the main browser.
  8. m_Browser = browser;
  9. m_BrowserId = browser->GetIdentifier();
  10. }
  11. // Keep track of how many browsers currently exist.
  12. m_BrowserCount++;
  13. }

执行CefBrowserHost::CloseBrowser()销毁Browser对象。

  1. // Notify the browser window that we would like to close it. This will result in a call to
  2. // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
  3. browser->GetHost()->CloseBrowser(false);

Browser对象的关闭事件来源于他的父窗口的关闭方法(比如,在父窗口上点击X控钮。)。父窗口需要调用 CloseBrowser(false) 并且等待操作系统的第二个关闭事件来决定是否允许关闭。如果在JavaScript 'onbeforeunload'事件处理或者 DoClose()回调中取消了关闭操作,则操作系统的第二个关闭事件可能不会发送。注意一下面示例中对IsCloseing()的判断-它在第一个关闭事件中返回false,在第二个关闭事件中返回true(当 DoCloase 被调用后)。

Windows平台下,在父窗口的WndProc里处理WM_ClOSE消息:

  1. case WM_CLOSE:
  2. if (g_handler.get() && !g_handler->IsClosing()) {
  3. CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
  4. if (browser.get()) {
  5. // Notify the browser window that we would like to close it. This will result in a call to
  6. // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
  7. browser->GetHost()->CloseBrowser(false);
  8. // Cancel the close.
  9. return 0;
  10. }
  11. }
  12. // Allow the close.
  13. break;
  14. case WM_DESTROY:
  15. // Quitting CEF is handled in MyHandler::OnBeforeClose().
  16. return 0;
  17. }

Linux平台下,处理delete_event信号:

  1. gboolean delete_event(GtkWidget* widget, GdkEvent* event,
  2. GtkWindow* window) {
  3. if (g_handler.get() && !g_handler->IsClosing()) {
  4. CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
  5. if (browser.get()) {
  6. // Notify the browser window that we would like to close it. This will result in a call to
  7. // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
  8. browser->GetHost()->CloseBrowser(false);
  9. // Cancel the close.
  10. return TRUE;
  11. }
  12. }
  13. // Allow the close.
  14. return FALSE;
  15. }

MacOS X平台下,处理windowShouldClose选择器:

  1. // Called when the window is about to close. Perform the self-destruction
  2. // sequence by getting rid of the window. By returning YES, we allow the window
  3. // to be removed from the screen.
  4. - (BOOL)windowShouldClose:(id)window {
  5. if (g_handler.get() && !g_handler->IsClosing()) {
  6. CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
  7. if (browser.get()) {
  8. // Notify the browser window that we would like to close it. This will result in a call to
  9. // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
  10. browser->GetHost()->CloseBrowser(false);
  11. // Cancel the close.
  12. return NO;
  13. }
  14. }
  15. // Try to make the window go away.
  16. [window autorelease];
  17. // Clean ourselves up after clearing the stack of anything that might have the
  18. // window on it.
  19. [self performSelectorOnMainThread:@selector(cleanup:)
  20. withObject:window
  21. waitUntilDone:NO];
  22. // Allow the close.
  23. return YES;
  24. }

DoClose方法设置m_blsClosing 标志位为true,并返回false以再次发送操作系统的关闭事件。

  1. bool MyClient::DoClose(CefRefPtr<CefBrowser> browser) {
  2. // Must be executed on the UI thread.
  3. REQUIRE_UI_THREAD();
  4. // Protect data members from access on multiple threads.
  5. AutoLock lock_scope(this);
  6. // Closing the main window requires special handling. See the DoClose()
  7. // documentation in the CEF header for a detailed description of this
  8. // process.
  9. if (m_BrowserId == browser->GetIdentifier()) {
  10. // Notify the browser that the parent window is about to close.
  11. browser->GetHost()->ParentWindowWillClose();
  12. // Set a flag to indicate that the window close should be allowed.
  13. m_bIsClosing = true;
  14. }
  15. // Allow the close. For windowed browsers this will result in the OS close
  16. // event being sent.
  17. return false;
  18. }

当操作系统捕捉到第二次关闭事件,它才会允许父窗口真正关闭。该动作会先触发OnBeforeClose()回调,请在该回调里释放所有对浏览器对象的引用。

  1. void MyHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
  2. // Must be executed on the UI thread.
  3. REQUIRE_UI_THREAD();
  4. // Protect data members from access on multiple threads.
  5. AutoLock lock_scope(this);
  6. if (m_BrowserId == browser->GetIdentifier()) {
  7. // Free the browser pointer so that the browser can be destroyed.
  8. m_Browser = NULL;
  9. }
  10. if (--m_BrowserCount == 0) {
  11. // All browser windows have closed. Quit the application message loop.
  12. CefQuitMessageLoop();
  13. }
  14. }

完整的流程,请参考cefclient例子里对不同平台的处理。

离屏渲染(Off-Screen Rendering)

在离屏渲染模式下,CEF不会创建原生浏览器窗口。CEF为宿主程序提供无效的区域和像素缓存区,而宿主程序负责通知鼠标键盘以及焦点事件给CEF。离屏渲染目前不支持混合加速,所以性能上可能无法和非离屏渲染相比。离屏浏览器将收到和窗口浏览器同样的事件通知,例如前一节介绍的生命周期事件。下面介绍如何使用离屏渲染:

  • 实现CefRenderHandler接口。除非特别说明,所有的方法都需要覆写。
  • 调用CefWindowInfo::SetAsOffScreen(),将CefWindowInfo传递给CefBrowserHost::CreateBrowser()之前还可以选择设置CefWindowInfo::SetTransparentPainting()。如果没有父窗口被传递给SetAsOffScreen,则有些类似上下文菜单这样的功能将不可用。
  • CefRenderHandler::GetViewRect方法将被调用以获得所需要的可视区域。
  • CefRenderHandler::OnPaint() 方法将被调用以提供无效区域(脏区域)以及更新过的像素缓存。cefclient程序里使用OpenGL绘制缓存,但你可以使用任何别的绘制技术。
  • 可以调用CefBrowserHost::WasResized()方法改变浏览器大小。这将导致对GetViewRect()方法的调用,以获取新的浏览器大小,然后调用OnPaint()重新绘制。
  • 调用CefBrowserHost::SendXXX()方法通知浏览器的鼠标、键盘和焦点事件。
  • 调用CefBrowserHost::CloseBrowser()销毁浏览器。

使用命令行参数--off-screen-rendering-enabled运行cefclient,可以测试离屏渲染的效果。

投递任务(Posting Tasks)

任务(Task)可以通过CefPostTask在一个进程内的不同的线程之间投递。CefPostTask有一系列的重载方法,详细内容请参考cef_task.h头文件。任务将会在被投递线程的消息循环里异步执行。例如,为了在UI线程上执行MyObject::MyMethod方法,并传递两个参数,代码如下:

  1. CefPostTask(TID_UI, NewCefRunnableMethod(object, &MyObject::MyMethod, param1, param2));

为了在IO线程在执行MyFunction方法,同时传递两个参数,代码如下:

  1. CefPostTask(TID_IO, NewCefRunnableFunction(MyFunction, param1, param2));

参考cef_runnable.h头文件以了解更多关于NewCefRunnable模板方法的细节。

如果宿主程序需要保留一个运行循环的引用,则可以使用CefTaskRunner类。例如,获取UI线程的任务运行器(task runner),代码如下:

  1. CefRefPtr<CefTaskRunner> task_runner = CefTaskRunner::GetForThread(TID_UI);

进程间通信(Inter-Process Communication (IPC))

由于CEF3运行在多进程环境下,所以需要提供一个进程间通信机制。CefBrowser和CefFrame对象在Borwser和Render进程里都有代理对象。CefBrowser和CefFrame对象都有一个唯一ID值绑定,便于在两个进程间定位匹配的代理对象。

处理启动消息(Process Startup Messages)

为了给所有的Render进程提供一样的启动信息,请在Browser进程实现CefBrowserProcessHander::OnRenderProcessThreadCreated()方法。在这里传入的信息会在Render进程的CefRenderProcessHandler::OnRenderThreadCreated()方法里接受。

处理运行时消息(Process Runtime Messages)

在进程生命周期内,任何时候你都可以通过CefProcessMessage类传递进程间消息。这些信息和特定的CefBrowser实例绑定在一起,用户可以通过CefBrowser::SendProcessMessage()方法发送。进程间消息可以包含任意的状态信息,用户可以通过CefProcessMessage::GetArgumentList()获取。

  1. // Create the message object.
  2. CefRefPtr<CefProcessMessage> msg= CefProcessMessage::Create(“my_message”);
  3. // Retrieve the argument list object.
  4. CefRefPtr<CefListValue> args = msg>GetArgumentList();
  5. // Populate the argument values.
  6. args->SetString(0, my string”);
  7. args->SetInt(0, 10);
  8. // Send the process message to the render process.
  9. // Use PID_BROWSER instead when sending a message to the browser process.
  10. browser->SendProcessMessage(PID_RENDERER, msg);

一个从Browser进程发送到Render进程的消息将会在CefRenderProcessHandler::OnProcessMessageReceived()方法里被接收。一个从Render进程发送到Browser进程的消息将会在CefClient::OnProcessMessageReceived()方法里被接收。

  1. bool MyHandler::OnProcessMessageReceived(
  2. CefRefPtr<CefBrowser> browser,
  3. CefProcessId source_process,
  4. CefRefPtr<CefProcessMessage> message) {
  5. // Check the message name.
  6. const std::string& message_name = message->GetName();
  7. if (message_name == my_message”) {
  8. // Handle the message here...
  9. return true;
  10. }
  11. return false;
  12. }

我们可以调用CefFrame::GerIdentifier()获取CefFrame的ID,并通过进程间消息发送给另一个进程,然后在接收端通过CefBrowser::GetFrame()找到对应的CefFrame。通过这种方式可以将进程间消息和特定的CefFrame联系在一起。

  1. // Helper macros for splitting and combining the int64 frame ID value.
  2. #define MAKE_INT64(int_low, int_high) \
  3. ((int64) (((int) (int_low)) | ((int64) ((int) (int_high))) << 32))
  4. #define LOW_INT(int64_val) ((int) (int64_val))
  5. #define HIGH_INT(int64_val) ((int) (((int64) (int64_val) >> 32) & 0xFFFFFFFFL))
  6. // Sending the frame ID.
  7. const int64 frame_id = frame->GetIdentifier();
  8. args->SetInt(0, LOW_INT(frame_id));
  9. args->SetInt(1, HIGH_INT(frame_id));
  10. // Receiving the frame ID.
  11. const int64 frame_id = MAKE_INT64(args->GetInt(0), args->GetInt(1));
  12. CefRefPtr<CefFrame> frame = browser->GetFrame(frame_id);

异步JavaScript绑定(Asynchronous JavaScript Bindings)

JavaScript被集成在Render进程,但是需要频繁和Browser进程交互。 JavaScript API应该被设计成可使用闭包异步执行。

通用消息转发(Generic Message Router)

从1574版本开始,CEF提供了在Render进程执行的JavaScript和在Browser进程执行的C++代码之间同步通信的转发器。应用程序通过C++回调函数(OnBeforeBrowse, OnProcessMessageRecieved, OnContextCreated等)传递数据。Render进程支持通用的JavaScript回调函数注册机制,Browser进程则支持应用程序注册特定的Handler进行处理。

下面的代码示例在JavaScript端扩展window对象,添加cefQuery函数:

  1. // Create and send a new query.
  2. var request_id = window.cefQuery({
  3. request: 'my_request',
  4. persistent: false,
  5. onSuccess: function(response) {},
  6. onFailure: function(error_code, error_message) {}
  7. });
  8. // Optionally cancel the query.
  9. window.cefQueryCancel(request_id);

对应的C++ Handler代码如下:

  1. class Callback : public CefBase {
  2. public:
  3. ///
  4. // Notify the associated JavaScript onSuccess callback that the query has
  5. // completed successfully with the specified |response|.
  6. ///
  7. virtual void Success(const CefString& response) =0;
  8. ///
  9. // Notify the associated JavaScript onFailure callback that the query has
  10. // failed with the specified |error_code| and |error_message|.
  11. ///
  12. virtual void Failure(int error_code, const CefString& error_message) =0;
  13. };
  14. class Handler {
  15. public:
  16. ///
  17. // Executed when a new query is received. |query_id| uniquely identifies the
  18. // query for the life span of the router. Return true to handle the query
  19. // or false to propagate the query to other registered handlers, if any. If
  20. // no handlers return true from this method then the query will be
  21. // automatically canceled with an error code of -1 delivered to the
  22. // JavaScript onFailure callback. If this method returns true then a
  23. // Callback method must be executed either in this method or asynchronously
  24. // to complete the query.
  25. ///
  26. virtual bool OnQuery(CefRefPtr<CefBrowser> browser,
  27. CefRefPtr<CefFrame> frame,
  28. int64 query_id,
  29. const CefString& request,
  30. bool persistent,
  31. CefRefPtr<Callback> callback) {
  32. return false;
  33. }
  34. ///
  35. // Executed when a query has been canceled either explicitly using the
  36. // JavaScript cancel function or implicitly due to browser destruction,
  37. // navigation or renderer process termination. It will only be called for
  38. // the single handler that returned true from OnQuery for the same
  39. // |query_id|. No references to the associated Callback object should be
  40. // kept after this method is called, nor should any Callback methods be
  41. // executed.
  42. ///
  43. virtual void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
  44. CefRefPtr<CefFrame> frame,
  45. int64 query_id) {}
  46. };

完整的用法请参考wrapper/cef_message_router.h

自定义实现(Custom Implementation)

一个CEF应用程序也可以提供自己的异步JavaScript绑定。典型的实现如下:

  1. Render进程的JavaScript传递一个回调函数。
  1. // In JavaScript register the callback function.
  2. app.setMessageCallback('binding_test', function(name, args) {
  3. document.getElementById('result').value = "Response: "+args[0];
  4. });
  1. Render进程的C++端通过一个map持有JavaScript端注册的回调函数。
  1. // Map of message callbacks.
  2. typedef std::map<std::pair<std::string, int>,
  3. std::pair<CefRefPtr<CefV8Context>, CefRefPtr<CefV8Value> > >
  4. CallbackMap;
  5. CallbackMap callback_map_;
  6. // In the CefV8Handler::Execute implementation for “setMessageCallback”.
  7. if (arguments.size() == 2 && arguments[0]->IsString() &&
  8. arguments[1]->IsFunction()) {
  9. std::string message_name = arguments[0]->GetStringValue();
  10. CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
  11. int browser_id = context->GetBrowser()->GetIdentifier();
  12. callback_map_.insert(
  13. std::make_pair(std::make_pair(message_name, browser_id),
  14. std::make_pair(context, arguments[1])));
  15. }
  1. Render进程发送异步进程间通信到Browser进程。

  2. Browser进程接收到进程间消息,并处理。

  3. Browser进程处理完毕后,发送一个异步进程间消息给Render进程,返回结果。

  4. Render进程接收到进程间消息,则调用最开始保存的JavaScript注册的回调函数处理之。

  1. // Execute the registered JavaScript callback if any.
  2. if (!callback_map_.empty()) {
  3. const CefString& message_name = message->GetName();
  4. CallbackMap::const_iterator it = callback_map_.find(
  5. std::make_pair(message_name.ToString(),
  6. browser->GetIdentifier()));
  7. if (it != callback_map_.end()) {
  8. // Keep a local reference to the objects. The callback may remove itself
  9. // from the callback map.
  10. CefRefPtr<CefV8Context> context = it->second.first;
  11. CefRefPtr<CefV8Value> callback = it->second.second;
  12. // Enter the context.
  13. context->Enter();
  14. CefV8ValueList arguments;
  15. // First argument is the message name.
  16. arguments.push_back(CefV8Value::CreateString(message_name));
  17. // Second argument is the list of message arguments.
  18. CefRefPtr<CefListValue> list = message->GetArgumentList();
  19. CefRefPtr<CefV8Value> args = CefV8Value::CreateArray(list->GetSize());
  20. SetList(list, args); // Helper function to convert CefListValue to CefV8Value.
  21. arguments.push_back(args);
  22. // Execute the callback.
  23. CefRefPtr<CefV8Value> retval = callback->ExecuteFunction(NULL, arguments);
  24. if (retval.get()) {
  25. if (retval->IsBool())
  26. handled = retval->GetBoolValue();
  27. }
  28. // Exit the context.
  29. context->Exit();
  30. }
  31. }
  1. 在CefRenderProcessHandler::OnContextReleased()里释放JavaScript注册的回调函数以及其他V8资源。
  1. void MyHandler::OnContextReleased(CefRefPtr<CefBrowser> browser,
  2. CefRefPtr<CefFrame> frame,
  3. CefRefPtr<CefV8Context> context) {
  4. // Remove any JavaScript callbacks registered for the context that has been released.
  5. if (!callback_map_.empty()) {
  6. CallbackMap::iterator it = callback_map_.begin();
  7. for (; it != callback_map_.end();) {
  8. if (it->second.first->IsSame(context))
  9. callback_map_.erase(it++);
  10. else
  11. ++it;
  12. }
  13. }
  14. }

同步请求(Synchronous Requests)

某些特殊场景下,也许会需要在Browser进程和Render进程做进程间同步通信。这应该被尽可能避免,因为这会对Render进程的性能造成负面影响。然而如果你一定要做进程间同步通信,可以考虑使用XMLHttpRequest,XMLHttpRequest在等待Browser进程的网络响应的时候会等待。Browser进程可以通过自定义scheme Handler或者网络交互处理XMLHttpRequest。更多细节,请参考Network Layer一节。

网络层(Network Layer)

默认情况下,CEF3的网络请求会被宿主程序手工处理。然而CEF3也暴露了一系列网络相关的函数用以处理网络请求。

网络相关的回调函数可在不同线程被调用,因此要注意相关文档的说明,并对自己的数据进行线程安全保护。

自定义请求(Custom Requests)

通过CefFrame::LoadURL()方法可简单加载一个url:

  1. browser->GetMainFrame()->LoadURL(some_url);

如果希望发送更复杂的请求,则可以调用CefFrame::LoadRequest()方法。该方法接受一个CefRequest对象作为唯一的参数。

  1. // Create a CefRequest object.
  2. CefRefPtr<CefRequest> request = CefRequest::Create();
  3. // Set the request URL.
  4. request->SetURL(some_url);
  5. // Set the request method. Supported methods include GET, POST, HEAD, DELETE and PUT.
  6. request->SetMethod(“POST”);
  7. // Optionally specify custom headers.
  8. CefRequest::HeaderMap headerMap;
  9. headerMap.insert(
  10. std::make_pair("X-My-Header", "My Header Value"));
  11. request->SetHeaderMap(headerMap);
  12. // Optionally specify upload content.
  13. // The default “Content-Type” header value is "application/x-www-form-urlencoded".
  14. // Set “Content-Type” via the HeaderMap if a different value is desired.
  15. const std::string& upload_data = arg1=val1&arg2=val2”;
  16. CefRefPtr<CefPostData> postData = CefPostData::Create();
  17. CefRefPtr<CefPostDataElement> element = CefPostDataElement::Create();
  18. element->SetToBytes(upload_data.size(), upload_data.c_str());
  19. postData->AddElement(element);
  20. request->SetPostData(postData);

浏览器无关请求(Browser-Independent Requests)

应用程序可以通过CefURLRequest类发送和浏览器无关的网络请求。并实现CefURLRequestClient接口处理响应。CefURLRequest可以在Browser和Render进程被使用。

  1. class MyRequestClient : public CefURLRequestClient {
  2. public:
  3. MyRequestClient()
  4. : upload_total_(0),
  5. download_total_(0) {}
  6. virtual void OnRequestComplete(CefRefPtr<CefURLRequest> request) OVERRIDE {
  7. CefURLRequest::Status status = request->GetRequestStatus();
  8. CefURLRequest::ErrorCode error_code = request->GetRequestError();
  9. CefRefPtr<CefResponse> response = request->GetResponse();
  10. // Do something with the response...
  11. }
  12. virtual void OnUploadProgress(CefRefPtr<CefURLRequest> request,
  13. uint64 current,
  14. uint64 total) OVERRIDE {
  15. upload_total_ = total;
  16. }
  17. virtual void OnDownloadProgress(CefRefPtr<CefURLRequest> request,
  18. uint64 current,
  19. uint64 total) OVERRIDE {
  20. download_total_ = total;
  21. }
  22. virtual void OnDownloadData(CefRefPtr<CefURLRequest> request,
  23. const void* data,
  24. size_t data_length) OVERRIDE {
  25. download_data_ += std::string(static_cast<const char*>(data), data_length);
  26. }
  27. private:
  28. uint64 upload_total_;
  29. uint64 download_total_;
  30. std::string download_data_;
  31. private:
  32. IMPLEMENT_REFCOUNTING(MyRequestClient);
  33. };

下面的代码发送一个请求:

  1. // Set up the CefRequest object.
  2. CefRefPtr<CefRequest> request = CefRequest::Create();
  3. // Populate |request| as shown above...
  4. // Create the client instance.
  5. CefRefPtr<MyRequestClient> client = new MyRequestClient();
  6. // Start the request. MyRequestClient callbacks will be executed asynchronously.
  7. CefRefPtr<CefURLRequest> url_request = CefURLRequest::Create(request, client.get());
  8. // To cancel the request: url_request->Cancel();

可以通过CefRequest::SetFlags定制请求的行为,这些标志位包括:

  • UR_FLAG_SKIP_CACHE 如果设置了该标志位,则处理请求响应时,缓存将被跳过。
  • UR_FLAG_ALLOW_CACHED_CREDENTIALS 如果设置了该标志位,则可能会发送cookie并在响应端被保存。同时UR_FLAG_ALLOW_CACHED_CREDENTIALS标志位也必须被设置。
  • UR_FLAG_REPORT_UPLOAD_PROGRESS 如果设置了该标志位,则当请求拥有请求体时,上载进度事件将会被触发。
  • UR_FLAG_REPORT_LOAD_TIMING 如果设置了该标志位,则时间信息会被收集。
  • UR_FLAG_REPORT_RAW_HEADERS 如果设置了该标志位,则头部会被发送,并且接收端会被记录。
  • UR_FLAG_NO_DOWNLOAD_DATA 如果设置了该标志位,则CefURLRequestClient::OnDownloadData方法不会被调用。
  • UR_FLAG_NO_RETRY_ON_5XX 如果设置了该标志位,则5xx重定向错误会被交给相关Observer去处理,而不是自动重试。这个功能目前只能在Browser进程的请求端使用。

例如,为了跳过缓存并不报告下载数据,代码如下:

  1. request->SetFlags(UR_FLAG_SKIP_CACHE | UR_FLAG_NO_DOWNLOAD_DATA);

请求响应(Request Handling)

CEF3 支持两种方式处理网络请求。一种是实现scheme Handler,这种方式允许为特定的(sheme+domain)请求注册特定的请求响应。另一种是请求拦截,允许处理任意的网络请求。

注册自定义scheme(有别于HTTP,HTTPS等)可以让CEF按希望的方式处理请求。例如,如果你希望特定的shceme被当策划那个HTTP一样处理,则应该注册一个standard的scheme。如果你的自定义shceme可被跨域执行,则应该考虑使用使用HTTP scheme代替自定义scheme以避免潜在问题。如果你希望使用自定义scheme,实现CefApp::OnRegisterCustomSchemes回调。

  1. void MyApp::OnRegisterCustomSchemes(CefRefPtr<CefSchemeRegistrar> registrar) {
  2. // Register "client" as a standard scheme.
  3. registrar->AddCustomScheme("client", true, false, false);
  4. }

Scheme响应(Scheme Handler)

通过CefRegisterSchemeHandlerFactory方法注册一个scheme响应,最好在CefBrowserProcessHandler::OnContextInitialized()方法里调用。例如,你可以注册一个"client://myapp/"的请求:

  1. CefRegisterSchemeHandlerFactory("client", myapp”, new MySchemeHandlerFactory());

scheme Handler类可以被用在内置shcme(HTTP,HTTPS等),也可以被用在自定义scheme上。当使用内置shceme,选择一个对你的应用程序来说唯一的域名。实现CefSchemeHandlerFactory和CefResoureHandler类去处理请求并返回响应数据。可以参考cefclient/sheme_test.h的例子。

  1. // Implementation of the factory for creating client request handlers.
  2. class MySchemeHandlerFactory : public CefSchemeHandlerFactory {
  3. public:
  4. virtual CefRefPtr<CefResourceHandler> Create(CefRefPtr<CefBrowser> browser,
  5. CefRefPtr<CefFrame> frame,
  6. const CefString& scheme_name,
  7. CefRefPtr<CefRequest> request)
  8. OVERRIDE {
  9. // Return a new resource handler instance to handle the request.
  10. return new MyResourceHandler();
  11. }
  12. IMPLEMENT_REFCOUNTING(MySchemeHandlerFactory);
  13. };
  14. // Implementation of the resource handler for client requests.
  15. class MyResourceHandler : public CefResourceHandler {
  16. public:
  17. MyResourceHandler() {}
  18. virtual bool ProcessRequest(CefRefPtr<CefRequest> request,
  19. CefRefPtr<CefCallback> callback)
  20. OVERRIDE {
  21. // Evaluate |request| to determine proper handling...
  22. // Execute |callback| once header information is available.
  23. // Return true to handle the request.
  24. return true;
  25. }
  26. virtual void GetResponseHeaders(CefRefPtr<CefResponse> response,
  27. int64& response_length,
  28. CefString& redirectUrl) OVERRIDE {
  29. // Populate the response headers.
  30. response->SetMimeType("text/html");
  31. response->SetStatus(200);
  32. // Specify the resulting response length.
  33. response_length = ...;
  34. }
  35. virtual void Cancel() OVERRIDE {
  36. // Cancel the response...
  37. }
  38. virtual bool ReadResponse(void* data_out,
  39. int bytes_to_read,
  40. int& bytes_read,
  41. CefRefPtr<CefCallback> callback)
  42. OVERRIDE {
  43. // Read up to |bytes_to_read| data into |data_out| and set |bytes_read|.
  44. // If data isn't immediately available set bytes_read=0 and execute
  45. // |callback| asynchronously.
  46. // Return true to continue the request or false to complete the request.
  47. return …;
  48. }
  49. private:
  50. IMPLEMENT_REFCOUNTING(MyResourceHandler);
  51. };

如果响应数据类型是已知的,则CefStreamResourceHandler类提供了CefResourceHandler类的默认实现。

  1. // CefStreamResourceHandler is part of the libcef_dll_wrapper project.
  2. #include include/wrapper/cef_stream_resource_handler.h
  3. const std::string& html_content = “<html><body>Hello!</body></html>”;
  4. // Create a stream reader for |html_content|.
  5. CefRefPtr<CefStreamReader> stream =
  6. CefStreamReader::CreateForData(
  7. static_cast<void*>(const_cast<char*>(html_content.c_str())),
  8. html_content.size());
  9. // Constructor for HTTP status code 200 and no custom response headers.
  10. // There’s also a version of the constructor for custom status code and response headers.
  11. return new CefStreamResourceHandler("text/html", stream);

请求拦截(Request Interception)

CefRequestHandler::GetResourceHandler()方法支持拦截任意请求。参考client_handler.cpp。

  1. CefRefPtr<CefResourceHandler> MyHandler::GetResourceHandler(
  2. CefRefPtr<CefBrowser> browser,
  3. CefRefPtr<CefFrame> frame,
  4. CefRefPtr<CefRequest> request) {
  5. // Evaluate |request| to determine proper handling...
  6. if (...)
  7. return new MyResourceHandler();
  8. // Return NULL for default handling of the request.
  9. return NULL;
  10. }

其他回调(Other Callbacks)

CefRequestHander接口还提供了其他回调函数以定制其他网络相关事件。包括授权、cookie处理、外部协议处理、证书错误等。

Proxy Resolution

CEF3使用类似Google Chrome一样的方式,通过命令行参数传递代理配置。

  1. --proxy-server=host:port
  2. Specify the HTTP/SOCKS4/SOCKS5 proxy server to use for requests. An individual proxy
  3. server is specified using the format:
  4. [<proxy-scheme>://]<proxy-host>[:<proxy-port>]
  5. Where <proxy-scheme> is the protocol of the proxy server, and is one of:
  6. "http", "socks", "socks4", "socks5".
  7. If the <proxy-scheme> is omitted, it defaults to "http". Also note that "socks" is equivalent to
  8. "socks5".
  9. Examples:
  10. --proxy-server="foopy:99"
  11. Use the HTTP proxy "foopy:99" to load all URLs.
  12. --proxy-server="socks://foobar:1080"
  13. Use the SOCKS v5 proxy "foobar:1080" to load all URLs.
  14. --proxy-server="sock4://foobar:1080"
  15. Use the SOCKS v4 proxy "foobar:1080" to load all URLs.
  16. --proxy-server="socks5://foobar:66"
  17. Use the SOCKS v5 proxy "foobar:66" to load all URLs.
  18. It is also possible to specify a separate proxy server for different URL types, by prefixing
  19. the proxy server specifier with a URL specifier:
  20. Example:
  21. --proxy-server="https=proxy1:80;http=socks4://baz:1080"
  22. Load https://* URLs using the HTTP proxy "proxy1:80". And load http://*
  23. URLs using the SOCKS v4 proxy "baz:1080".
  24. --no-proxy-server
  25. Disables the proxy server.
  26. --proxy-auto-detect
  27. Autodetect proxy configuration.
  28. --proxy-pac-url=URL
  29. Specify proxy autoconfiguration URL.

如果代理请求授权,CefRequestHandler::GetAuthCredentials()回调会被调用。如果isProxy参数为true,则需要返回用户名和密码。

  1. bool MyHandler::GetAuthCredentials(
  2. CefRefPtr<CefBrowser> browser,
  3. CefRefPtr<CefFrame> frame,
  4. bool isProxy,
  5. const CefString& host,
  6. int port,
  7. const CefString& realm,
  8. const CefString& scheme,
  9. CefRefPtr<CefAuthCallback> callback) {
  10. if (isProxy) {
  11. // Provide credentials for the proxy server connection.
  12. callback->Continue("myuser", "mypass");
  13. return true;
  14. }
  15. return false;
  16. }

网络内容加载可能会因为代理而有延迟。为了更好的用户体验,可以考虑让你的应用程序先显示一个闪屏,等内容加载好了再通过meta refresh显示真实网页。可以指定--no-proxy-server禁用代理并做相关测试。代理延迟也可以通过chrome浏览器重现,方式是使用命令行传参:chrome -url=....

CEF General Usage(CEF3预览)的更多相关文章

  1. MWeb 1.6 发布!Dark Mode、全文搜寻、发布到Wordpress、Evernote 等支持更新、编辑/预览视图模式等

    Dark Mode 使用 View - Dark Mode 或快捷键 CMD + Option + L 开启或关闭 Dark Mode.可以在设置中设置 Dark Mode 状态下编辑器所使用的样式, ...

  2. [下载] VS 2013 Update 4 & 社群版 (Visual Studio Community) & VS 2015 Preview预览版

    这是我的备份,原文请看http://www.dotblogs.com.tw/mis2000lab/archive/2014/11/13/vs2013_update4_community_vs2015_ ...

  3. Org mode无法生成LaTeX公式预览图片

    最近需要在Cygwin平台下的Emacs Org mode中生成LaTeX数学公式的预览图片,从而得到图文并貌的笔记与任务管理文档.但当我执行org-toggle-latex-fragment命令后却 ...

  4. 基于jQuery页面窗口拖动预览效果

    今天给大家分享一款基于Query页面窗口拖动预览效果.这是一款基于jQuery+HTML5实现的模拟页面窗口显示拖动窗口预览特效.这款实例适用浏览器:IE8.360.FireFox.Chrome.Sa ...

  5. Word/Excel 在线预览

    前言 近日项目中做到一个功能,需要上传附件后能够在线预览.之前也没做过这类似的,于是乎就查找了相关资料,.net实现Office文件预览大概有这几种方式: ① 使用Microsoft的Office组件 ...

  6. 预览github里面的网页或dome

    1.问题所在: 之前把项目提交到github都可以在路径前面加上http://htmlpreview.github.io/?来预览demo,最近发现这种方式预览的时候加载不出来css,js(原因不详) ...

  7. IE8/9 本地预览上传图片

    本地预览的意思是,在选择图片之后先不上传到服务器,而是由一个<img>标签来预览本地的图片,非 IE8/9 浏览器可以从<input type="file"/&g ...

  8. JS图片上传预览插件制作(兼容到IE6)

    其实,图片预览功能非常地常见.很意外,之前遇到上传图片的时候都不需要预览,也一直没有去实现过.现在手上的项目又需要有图片预览功能,所以就动手做了一个小插件.在此分享一下思路. 一.实现图片预览的一些方 ...

  9. [干货来袭]MSSQL Server on Linux预览版安装教程(先帮大家踩坑)

    前言 昨天晚上微软爸爸开了全国开发者大会,会上的内容,我就不多说了,园子里面很多.. 我们唐总裁在今年曾今透漏过SQL Server love Linux,果不其然,这次开发者大会上就推出了MSSQL ...

随机推荐

  1. Hadoop4.2HDFS测试报告之一

    1.1   测试方案 1.1.1 测试目标 为了验证本地文件系统和HDFS存储能力对比,将1G文件组存储进各个文件系统,记录存储任务消耗的时间. l   测试HDFS的高可用性和高稳定性 l   测试 ...

  2. [转载]C语言头文件的作用

    最近在工作当中遇到了一点小问题,关于C语言头文件的应用问题,主要还是关于全局变量的定义和声明问题.学 习C语言已经有好几年了,工作使用也近半年了,但是对于这部分的东西的确还没有深入的思考过.概念上还是 ...

  3. JAVA 基础--开发环境IDEA 搭建

    1.下载IDEA  (500M+) 2.激活. 在网站http://idea.lanyus.com/中获取注册码,填入Activation code中: 然后点击Activate即可. 3.创建工程前 ...

  4. SpringDataJpa错误

    在运行项目的时候出现的错误如下: would dispatch back to the current handler URL [/save] again. Check your ViewResolv ...

  5. Java学习笔记1---JVM、JRE、JDK

    jdk包含jre,jre包含jvm. 用java语言进行开发时,必须先装jdk: 只运行java程序,不进行开发时,可以只装jre. JVM 即Java Virtual machine,Java虚拟机 ...

  6. PHP define() 定义常量

    PHP define()函数定义了运行时的常量, 具体语法如下所示: (PHP 4, PHP 5, PHP 7) define — Defines a named constant bool defi ...

  7. CF750E 线段树+矩阵乘矩阵加

    题目描述 A string tt is called nice if a string "2017" occurs in tt as a subsequence but a str ...

  8. VMware RHEL6.3 开启网络连接

    确认/etc/sysconfig/network是否存在,如果不存在,service network 命令使用不了.新建: NETWORKING=yes HOSTNAME=RHEL6. GATEWAY ...

  9. pl/sql的控制结构,分支、循环、控制

    一.pl/sql的进阶--控制结构在任何计算机语言(c,java,pascal)都有各种控制语句(条件语句,循环结构,顺序控制结构...),在pl/sql中也存在这样的控制结构.在本部分学习完成后,希 ...

  10. 九度oj 题目1513:二进制中1的个数

    题目描述: 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 输入: 输入可能包含多个测试样例. 对于每个输入文件,第一行输入一个整数T,代表测试样例的数量.对于每个测试样例输入为一个 ...