目 录
第1章 下载
1.1 下载
下载网址为:http://www.magpcss.net/cef_downloads/,显示如下图所示:
图1.1
下载后的文件名如下所示:
cef_binary_3.2785.1466.g80e473e_windows32.tar.tar cef_binary_3.2785.1466.g80e473e_windows64.tar.tar |
1.2 合并
解压上一节的两个压缩包,并使用 Beyond Compare 进行二进制比较,如下图所示:
图1.2
可见:只有 Debug、Release、Resources 三个文件夹中的某些文件存在差异。所以,可将32位的Debug、Release、Resources重命名为Debug32、Release32、Resources32;64位的Debug、Release、Resources重命名为Debug64、Release64、Resources64。然后,将两个文件夹合并。
笔者将两个文件夹合并到了W:libCEFv3.2785.1466unzip,其目录结构如下图所示:
图1.3
第2章 cmake
2.1 编译简介
这里的编译主要指的是 libcef_dll_wrapper 的编译。它是libcef.dll的包装工程,客户端程序通过它可以间接的访问libcef.dll。
libCEF较早的版本(如3.2171.1979),包含有VC++项目文件(*.sln),可直接打开进行编译。目前的版本不再包含这些*.sln文件,使用VC++编译前可使用cmake程序生成*.sln文件。
2.2 下载cmake
cmake的下载网址为:https://cmake.org/download/。显示如下图所示:
图2.1
说明如下:
cmake-3.6.1.zip cmake的源代码
cmake-3.6.1-win32-x86.msi 安装包(32 位)
cmake-3.6.1-win32-x86.zip 直接运行(32 位)
cmake-3.6.1-win64-x64.msi 安装包(64 位)
cmake-3.6.1-win64-x64.zip 直接运行(64 位)
这里,下载 cmake-3.6.1-win32-x86.zip 即可。
2.3 运行cmake
解压cmake-3.6.1-win32-x86.zip,运行cmake-gui.exe,将显示如下界面:
图2.2
"Where is the source code:"右边的文本框内请输入图1.3中CMakeLists.txt所在的目录,即W:libCEFv3.2785.1466unzip。
"Where to build the binaries:"右边的文本框内请输入一个目录,cmake将在这个目录下生成VC++项目文件(*.sln;*.vcproj;*.vcxproj)。这个目录可以和"Where is the source code:"的设置相同。
单击上图的"Configure"按钮,将弹出上图右下角的界面。在这个界面里选择VC++编译器,如:使用vc2010编译64位程序,请选择"Visual Studio 10 2010 Win64"。这里选择Visual Studio 10 2010,然后单击"Finish"按钮。
单击上图的"Generate"按钮,cmake将在W:/libCEF/v3.2785.1466/make/vc2010里生成vc2010的项目文件cef.sln、ALL_BUILD.vcxproj……如下图所示:
图2.3
使用Visual Studio 2010打开上图的cef.sln,可以发现有五个项目。如下图所示:
图2.4
libcef_dll_wrapper 对libcef.dll的包装,是编译的重点
cefclient 一个调用libcef的示例程序,较复杂
cefsimple 一个调用libcef的示例程序,较简单
ZERO_CHECK 再次运行cmake程序,检查哪些文件被修改了
ALL_BUILD 编译它,则ZERO_CHECK、libcef_dll_wrapper、
cefclient、cefsimple将依次被编译
第3章 编译进阶
本章将说明如何在不使用cmake的情况下编译libcef_dll_wrapper。
3.1 创建项目
创建VC++静态库项目libcef_dll_wrapper。以VC++2010为例,创建项目时首先选择"Win32 Project",如下图所示:
图3.1
接下来的设置如下图所示:即创建一个静态库,不使用预编译头文件。
图3.2
3.2 添加源文件
将图1.3文件夹libcef_dll及其子文件夹下的所有源文件(*.cc)添加到上一节创建的VC++项目libcef_dll_wrapper里。
3.2.1 下载vcHelper
手动添加所有的源文件比较繁琐,可借助工具vcHelper来完成此项工作。其下载方法为:
1、进入百度网盘 http://pan.baidu.com/s/1gd7XDkf
2、进入目录 publicToolsvcHelper
3、下载vcHelper的最新版本。
3.2.2 使用vcHelper
运行vcHelper,进入"源文件树"页面,如下图所示:
图3.3
接下来的操作步骤为:
1、拖放文件夹libcef_dll至"生成文件树"内的文本框内,文件夹libcef_dll的全路径名(上图中的W:libCEFv3.2785.1466unziplibcef_dll)即被自动添加到该文本框内。当然,也可手工输入或粘贴这个全路径名;
2、单击"生成文件树"按钮。根据上图可知:vcHelper找到了263个文件;
3、拖放VC++项目libcef_dll_wrapper所在文件夹(W:libCEFv3.2785.1466libcef_dll_wrapper)至"替换文件树"内的文本框内。vcHelper将在该文件夹内查找所有的VC++工程文件(*.vcproj或*.vcxproj),并将其全路径名添加到相应的文本框内;
4、单击"替换文件树"按钮,文件树(263个文件)将被添加到VC++工程文件里。
打开项目libcef_dll_wrapper,可以看到源文件树。如下图所示:
图3.4
3.3 配置VC++项目
3.3.1 不使用预编译头文件
具体的配置如下图所示:
图3.5
3.3.2 增加宏定义
增加宏定义:USING_CEF_SHARED=1和NOMINMAX,如下图所示:
图3.6
USING_CEF_SHARED 有两个作用:
1、调用libcef.dll里的函数时,会使用__declspec(dllimport),详见includeinternalcef_export.h文件;
2、编译时防止有效代码被屏蔽,如文件libcef_dllcpptocviewsrowser_view_delegate_cpptoc.h里有如下代码:
#ifndef USING_CEF_SHARED #pragma message("Warning: "__FILE__" may be accessed wrapper-side only") #else // USING_CEF_SHARED class CefBrowserViewDelegateCppToC : public CefCppToC<CefBrowserViewDelegateCppToC, CefBrowserViewDelegate, cef_browser_view_delegate_t> { public: CefBrowserViewDelegateCppToC(); }; #endif |
不定义宏USING_CEF_SHARED,则有效代码(蓝色部分)将被屏蔽掉。
NOMINMAX的作用:取消宏min、max的定义,防止它们与std::min、std::max冲突。以下内容节选自windef.h
#ifndef NOMINMAX
#ifndef max #define max(a,b) (((a) > (b)) ? (a) : (b)) #endif
#ifndef min #define min(a,b) (((a) < (b)) ? (a) : (b)) #endif
#endif /* NOMINMAX */ |
亦即:定义宏NOMINMAX之后,宏min、max将不会被定义。
3.3.3 设置头文件查找目录
编译libcef_dll_wrapper时,需要用到W:libCEFv3.2785.1466unzipinclude目录下的头文件。为此,需要设置W:libCEFv3.2785.1466unzip为头文件查找目录。具体设置如下图所示:
图3.7
上图中的../../../unzip是W:libCEFv3.2785.1466unzip相对于文件W:libCEFv3.2785.1466libcef_dll_wrappermake-libTvc2010libcef_dll_wrapperT.vcxproj的相对路径。可用vcHelper获得这个相对路径,如下图所示:
图3.8
输入"基准目录/文件"和"绝对路径",单击按钮">>"即可获得相对路径。
3.4 其它
3.4.1 使用VC++2008编译
libcef_dll_wrapper默认情况下至少需要VC++2010才能编译通过。使用VC++2008编译时,会提示无法找到stdint.h,为此特做如下修改:
在include目录下新建文件stdint.h,其内容如下:
#pragma once #if _MSC_VER >= 1600 //VC++2010 ~ VC++2015 #include <stdint.h> #else typedef signed char int8_t; typedef short int16_t; typedef int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef signed char int_least8_t; typedef short int_least16_t; typedef int int_least32_t; typedef unsigned char uint_least8_t; typedef unsigned short uint_least16_t; typedef unsigned int uint_least32_t; typedef char int_fast8_t; typedef int int_fast16_t; typedef int int_fast32_t; typedef unsigned char uint_fast8_t; typedef unsigned int uint_fast16_t; typedef unsigned int uint_fast32_t; #ifdef _WIN64 typedef __int64 intptr_t; #else /* _WIN64 */ typedef int intptr_t; #endif /* _WIN64 */ #endif |
亦即:VC++2010及其以上版本直接包含自带的stdint.h头文件,否则就自定义int8_t、int16_t……
接下来,修改源代码中的#include <stdint.h>为#include "../../include/stdint.h"。注意双引号内的相对路径不要弄错。
3.4.2 修改#include语句
如图3.7所示,使用libCEF的客户端项目,必须设置libCEF的头文件查找目录,否则将无法正常编译。
以W:libCEFv3.2785.1466unziplibcef_dllwrapperlibcef_dll_wrapper.cc为例进行说明。该文件包含如下代码:
#include "include/cef_app.h" |
它其实想包含的是W:libCEFv3.2785.1466unzipincludecef_app.h。为此,需要设置W:libCEFv3.2785.1466unzip为头文件查找目录,这样编译器在编译时就能顺利找到文件include/cef_app.h了。
现在,如果把#include语句修改一下,如下所示:
#include "../../include/cef_app.h" |
编译W:libCEFv3.2785.1466unziplibcef_dllwrapperlibcef_dll_wrapper.cc时,编译器会使用头文件W:libCEFv3.2785.1466unziplibcef_dllwrapper....includecef_app.h,即W:libCEFv3.2785.1466unzipincludecef_app.h。这样,不用设置头文件查找目录,编译器也能找到头文件cef_app.h了。
也就是说:将#include语句中的包含路径全部更改为相对路径,就不再需要设置头文件查找目录了。
#include语句那么多,手工逐条修改将是一项浩大的工程。可借助vcHelper完成此项工作。如下图所示:
图3.9
具体操作步骤如下:
1、设置"源文件目录"和"引用目录"为W:libCEFv3.2785.1466unzip;
2、勾中"修改#include""语句"复选框,不要勾中"修改#include<>语句"复选框,不要勾中"修改#include里的扩展名"
3、单击"扫描"按钮;
4、单击"自动修改"按钮,vcHelper将自动修改#include""语句,并将无法修改的语句显示到右侧的列表中;
5、右侧列表中,单击"数量"列标题,整个列表将按数量进行排序。数量2表示包含文件(ef_callback.h或resource.h)有两个。此时鼠标左键双击该行,将显示如下界面:
图3.10
单击按钮"UltraEdit",vcHelper将调用UltraEdit,并打开文件urlrequest_test.cc,然后跳转至第10行。如下图所示:
图3.11
根据上图可知,urlrequest_test.cc需要的是base目录下的cef_callback.h。因此应该选择图3.10中的第一条修改建议。鼠标左键双击此修改建议,然后粘贴到上图的第10行,即可完成此行语句的修改。
注意:如果vcHelper无法正常调用UltraEdit,请在vcHelper的Config.txt里配置UltraEdit的全路径名,如下图所示:
图3.12
第4章 入门示例
4.1 简介
本章参考了开源项目CEF3SimpleSample,其下载网址为:
https://github.com/acristoffers/CEF3SimpleSample
CEF3SimpleSample是一个SDK程序,本章对其进行精简,并使用了MFC。最终实现了如下图所示的效果——运行后将显示新浪网的首页。
图4.1
4.2 创建项目
创建一个MFC的对话框程序mfcCEF。创建项目时的配置请见下图:
图4.2
4.3 编码
4.3.1 修改stdafx.h
在stdafx.h的最后增加如下代码
#define STR(x) #x #define STR2(x) STR(x) #define INCLUDE(f) STR2(PATH(f)) //包含 libCEF 的头文件 //下面的宏定义,##之前是 libCEF include 目录相对于本文件的相对路径 #define PATH(f) ../../libCEF/v3.2785.1466/unzip/include/##f #define USING_CEF_SHARED 1 #include INCLUDE(cef_app.h) #include INCLUDE(cef_client.h) #undef PATH
#include "cefSimple.h"
//下面的宏定义是 libCEF 库文件相对于 vc 项目文件(*.vcproj/*.vcxproj)的相对路径 #define PATH "../../libCEF/v3.2785.1466/" //连接库文件 libcef.lib #ifdef _WIN64 #ifdef _DEBUG #define PATH1 "unzip/Debug64/" #else #define PATH1 "unzip/Release64/" #endif #else #ifdef _DEBUG #define PATH1 "unzip/Debug32/" #else #define PATH1 "unzip/Release32/" #endif #endif #pragma comment(lib,PATH PATH1 "libcef.lib") #undef PATH1 //连接库文件 libcef_dll_wrapper.lib #define PATH1 "libcef_dll_wrapper/bin/" #if _MSC_VER==1500 //VC++9.0(VC2008) #define PATH2 "vc2008" #elif _MSC_VER==1600 //VC++10.0(VC2010) #define PATH2 "vc2010" #elif _MSC_VER==1700 //VC++11.0(VC2012) #define PATH2 "vc2012" #elif _MSC_VER==1800 //VC++12.0(VC2013) #define PATH2 "vc2013" #elif _MSC_VER==1900 //VC++14.0(VC2015) #define PATH2 "vc2015" #else #error 未知的 VC++ 编译器 #endif #ifdef _WIN64 #define PATH3 "-x64" #else #define PATH3 "-Win32" #endif #ifdef _DEBUG #ifdef _UNICODE #define PATH4 "-DU/" #else #define PATH4 "-DA/" #endif #else #ifdef _UNICODE #define PATH4 "-RU/" #else #define PATH4 "-RA/" #endif #endif #ifdef _MT #ifdef _DLL //使用多线程 DLL 版的 C 函数库 #define PATH5 "libcef_dll_wrapperD.lib" #else //使用多线程版的 C 函数库 #define PATH5 "libcef_dll_wrapperT.lib" #endif #else //使用单线程版的 C 函数库 #define PATH5 "libcef_dll_wrapperS.lib" #endif #pragma comment(lib,PATH PATH1 PATH2 PATH3 PATH4 PATH5) #undef PATH1 #undef PATH2 #undef PATH3 #undef PATH4 #undef PATH5 #undef PATH
#undef STR #undef STR2 #undef INCLUDE |
说明:
1、#define USING_CEF_SHARED 1 表示导入libcef.dll的导出函数;
2、VC++编译预处理器对#include INCLUDE(cef_app.h)的预处理:
首先变为 #include STR2(PATH(cef_app.h))
然后变为 #include STR(../../libCEF/v3.2785.1466/unzip/include/cef_app.h)
最后变为 #include "../../libCEF/v3.2785.1466/unzip/include/cef_app.h"
"#define PATH(f) ../../libCEF/v3.2785.1466/unzip/include/##f"中的../../libCEF/v3.2785.1466/unzip/include/是libCEF include 目录(W:libCEFv3.2785.1466unzipinclude)相对于本文件(W:VCmfcCEFstdafx.h)的相对目录。请根据实际情况修改这个相对路径。
注意:若要使用相对路径包含libCEF头文件,请按照3.4.2节的说明把libCEF源代码里的#include语句都修改掉。
3、如果使用vc2010编译器,编译Debug版,则#pragma comment(lib,PATH PATH1 "libcef.lib")将被展开为:#pragma comment(lib,"../../libCEF/v3.2785.1466/" "unzip/Debug32/" "libcef.lib")也就是#pragma comment(lib,"../../libCEF/v3.2785.1466/unzip/Debug32/libcef.lib")。
../../libCEF/v3.2785.1466/unzip/Debug32/libcef.lib是库文件(W:libCEFv3.2785.1466unzipDebug32libcef.lib)相对于vc项目文件(W:VCmfcCEFmfcCEF.vcxproj)的相对路径。请根据实际情况修改"#define PATH "../../libCEF/v3.2785.1466/""中的相对路径。
4、libcef_dll_wrapper库文件
笔者的电脑上libcef_dll_wrapper库文件的路径为:
W:libCEFv3.2785.1466libcef_dll_wrapperinvc2010-Win32-RU
vc2010还有可能是vc2012/vc2013/vc2015
Win32还有可能是x64
RU(Release Unicode)还有可能是RA(Release Ansi)、DA(Debug Ansi)、DU(Debug Unicode)
库文件名有两种:
libcef_dll_wrapperT.lib 使用的C函数库是多线程版
libcef_dll_wrapperD.lib 使用的C函数库是多线程DLL版
如果客户端程序Use MFC in a Static Library,就会自动连接libcef_dll_wrapperT.lib;如果客户端程序Use MFC in a Shared DLL,就会自动连接libcef_dll_wrapperD.lib。
4.3.2 增加cefSimple.h
给项目增加头文件cefSimple.h,其内容如下所示:
#pragma once
class SimpleApp : public CefApp { private: IMPLEMENT_REFCOUNTING(SimpleApp); };
class SimpleHandler : public CefClient , public CefLifeSpanHandler { public: SimpleHandler() { m_hWndBrowser = NULL; } public://CefClient methods: virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE { return this; } public://CefLifeSpanHandler methods: virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE { m_hWndBrowser = browser->GetHost()->GetWindowHandle(); } public: HWND m_hWndBrowser; IMPLEMENT_REFCOUNTING(SimpleHandler); }; |
4.3.3 修改mfcCEF.h
修改后的内容如下
#pragma once
#ifndef __AFXWIN_H__ #error "在包含此文件之前包含"stdafx.h"以生成 PCH 文件" #endif
#include "resource.h"
class CmfcCEFApp : public CWinApp { public: CmfcCEFApp(); public: virtual BOOL InitInstance(); DECLARE_MESSAGE_MAP() };
extern CmfcCEFApp theApp; |
4.3.4 修改mfcCEF.cpp
修改后的内容如下
#include "stdafx.h" #include "mfcCEF.h" #include "mfcCEFDlg.h"
#ifdef _DEBUG #define new DEBUG_NEW #endif
BEGIN_MESSAGE_MAP(CmfcCEFApp, CWinApp) END_MESSAGE_MAP()
CmfcCEFApp::CmfcCEFApp() { }
CmfcCEFApp theApp;
BOOL CmfcCEFApp::InitInstance() { CWinApp::InitInstance(); _tsetlocale(LC_ALL,_T("")); {//CEF 初始化 CefMainArgs ma(m_hInstance); CefRefPtr<SimpleApp> app(new SimpleApp()); int nCEP = CefExecuteProcess(ma,app.get(),NULL); if(nCEP >= 0) { exit(nCEP); } CefSettings settings; CefString(&settings.locale) = L"zh-CN"; settings.no_sandbox = 1; settings.multi_threaded_message_loop = 1; CefInitialize(ma, settings, app.get(),NULL); } { CmfcCEFDlg dlg; m_pMainWnd = &dlg; dlg.DoModal(); } CefShutdown(); //退出 CEF return FALSE; } |
4.3.5 修改mfcCEFDlg.h
修改后的内容如下
#pragma once
class CmfcCEFDlg : public CDialog { public: CmfcCEFDlg(CWnd* pParent = NULL); enum { IDD = IDD_MFCCEF_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); protected: virtual BOOL OnInitDialog(); afx_msg void OnSize(UINT nType, int cx, int cy); DECLARE_MESSAGE_MAP() protected: CefRefPtr<SimpleHandler> m_Handler; }; |
4.3.6 修改mfcCEFDlg.cpp
修改后的内容如下
#include "stdafx.h" #include "mfcCEF.h" #include "mfcCEFDlg.h"
#ifdef _DEBUG #define new DEBUG_NEW #endif
CmfcCEFDlg::CmfcCEFDlg(CWnd* pParent /*=NULL*/) : CDialog(CmfcCEFDlg::IDD, pParent) ,m_Handler(new SimpleHandler()) { }
void CmfcCEFDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); }
BEGIN_MESSAGE_MAP(CmfcCEFDlg, CDialog) ON_WM_SIZE() END_MESSAGE_MAP()
BOOL CmfcCEFDlg::OnInitDialog() { CDialog::OnInitDialog(); CefWindowInfo wi; RECT rc; GetClientRect(&rc); wi.SetAsChild(m_hWnd,rc); CefBrowserSettings bs; CefBrowserHost::CreateBrowser(wi,m_Handler.get() ,L"www.sina.com",bs,NULL); return TRUE; }
void CmfcCEFDlg::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); HWND hWnd = m_Handler->m_hWndBrowser; if(hWnd) { RECT rc; GetClientRect(&rc); ::MoveWindow(hWnd,0,0,rc.right,rc.bottom,FALSE); Invalidate(); } } |
4.4 运行
编译mfcCEF成功后,先不要急着运行程序。因为mfcCEF.exe运行时需要一些文件,需要把它们复制到mfcCEF.exe所在目录。
假如mfcCEF.exe是32位的Release版,则
1、复制图1.3中Release32文件夹内的所有文件到mfcCEF.exe所在目录。注意:*.lib有259M,运行时不需要这些文件,所以就不用复制了。
2、复制图1.3中Resources32文件夹内的所有文件到mfcCEF.exe所在目录。
文件复制完成后,mfcCEF.exe所在目录如下图所示:
图4.3
现在,就可以运行mfcCEF.exe了。
4.5 程序运行逻辑
运行mfcCEF,代码的执行顺序如下:
1、执行 CmfcCEFApp::InitInstance,调用 CefInitialize 初始化 CEF;
2、CmfcCEFApp::InitInstance 里的 dlg.DoModal() 显示程序主界面;
3、程序主界面被创建时,执行 CmfcCEFDlg::OnInitDialog,调用 CefBrowserHost::CreateBrowser 创建浏览器窗口;
4、浏览器窗口创建完毕后,执行 SimpleHandler::OnAfterCreated,获得浏览器窗口的句柄 m_hWndBrowser;
5、浏览器窗口是程序主界面窗口的子窗口。调整主界面大小时,将执行 CmfcCEFDlg::OnSize。OnSize 函数里移动浏览器窗口(句柄为 m_hWndBrowser)使其占满主界面窗口的客户区;
6、用户退出主界面,CmfcCEFApp::InitInstance 里的 dlg.DoModal() 将返回。同时对象 dlg 将被析构,其成员变量m_Handler 也将被析构。CefRefPtr<SimpleHandler> 使用了计数功能,m_Handler 析构时计数值减一后等于零,new SimpleHandler() 创建的对象将被自动 delete;
7、CmfcCEFApp::InitInstance 里的 CefShutdown() 被执行,退出 CEF;
8、CmfcCEFApp::InitInstance 返回 FALSE,整个程序不进入消息循环,而是结束。
下面说明一下对象new SimpleApp()的生命周期:
1、CefRefPtr<SimpleApp> app(new SimpleApp()) 后对象的计数值为 1;
2、CefInitialize(ma, settings, app.get(),NULL) 后对象的计数值为2;
3、app 析构后对象的计数值为 1;
4、CefShutdown() 后,app 的计数值为 0,对象自动被 delete。