客户端内嵌页 基于Chromium Embedded Framework (CEF)
Chromium Embedded Framework (CEF)是个基于Google Chromium项目的开源Web browser控件,支持Windows, Linux, Mac平台。除了提供C/C++接口外,也有其他语言的移植版。–百度百科
前言:
由于我们项目的客户端比较老了,用户的电脑的默认的ie内核版本也比较低(老用户很多,环境比较复杂)。导致现在内嵌页的前端开发困难,很多现行的web标准不能使用。为了解决这个问题,打算用cef嵌入客户端,这样就不用考虑ie内核的问题了,能使用哪些web特性只要看使用的cef版本是否支持就可以了。
cef 源码可以谷歌的官方下载:cef 官方 下载
cef github 源码: github 源码下载
cef 已经编好的库: 编好的链接库(旧)
编译好的库(新)
因为自己下载源码,自己编译有点复杂,时间也比较久,所以这里先用官方给我们编好的链接库。等以后有时间了再用自己编源码的方式。
我使用的是 cef_binary_1.1180.832_windows.zip 可以用vs2005。 刚好我们客户的代码也是vs2005的。
解压编译
下载好 cef_binary_1.1180.832_windows.zip 后进行解压:
cefclient 是示例程序。 lib 里面的 libcef.lib 是已经编好的库,在客户端代码工程需要引入。libcef.lib是用c写的,所以为了c++方便使用,libcef_dell_wrapper 是c++ 对 libcef 封装, 源码在 libcef_dll目录下
用vs2005打开 cefclient2005.sln
工程目录中,cefclient->include 和 libcef_dll_wrapper->include 是libcef.lib 库的引用头文件,就是解压根目录下的include 中的文件。我们客户端的工程中也引入这些头文件。
在编 cefclient 的时候,遇到了 atlthunk.lib 找不到的错误。弄了好久没解决,后面索性在工程属性中Linker->input 中把这个依赖库去掉。不影响编译。libcef_dll_wrapper 编译基本没遇到问题。
嵌入客户端工程
先研究下 cefclient 示例程序中的代码,不是很复杂。基本流程如下:
// 详细的代码在 cefclient_win.cpp
// 这里只是一些片段
// Program entry point function.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Retrieve the current working directory.
if (_getcwd(szWorkingDir, MAX_PATH) == NULL)
szWorkingDir[0] = 0;
// Parse command line arguments. The passed in values are ignored on Windows.
// 初始化命令行参数
AppInitCommandLine(0, NULL);
CefSettings settings;
CefRefPtr<CefApp> app;
// Populate the settings based on command line arguments.
// 内嵌页的一些配置设置,详细内容要看CefSettings类
AppGetSettings(settings, app);
// Initialize CEF.
// cef 开始进行初始
// 这里在我们自己程序运行的时候,如果根目录没有把示例程序根目录的locales拷到客户端跟目录。
// 初始化就会崩溃,而且没有任何提示和报错(坑爹的google o(╥﹏╥)o 找了好久才发现)。
// 具体下面的注意点会提到。
CefInitialize(settings, app);
........
// 其他自己逻辑代码.......
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_CEFCLIENT, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization
if (!InitInstance (hInstance, nCmdShow))
return FALSE;
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CEFCLIENT));
// Register the find event message.
uFindMsg = RegisterWindowMessage(FINDMSGSTRING);
int result = 0;
// settings.multi_threaded_message_loop 是 true 还是 false 的处理 会影响到
// 关闭 CefShutdown() 函数,如果是 settings.multi_threaded_message_loop = true
// 单处理不是 CefRunMessageLoop() , 在准备退出并调用 CefShutdown 就会崩溃。
if (!settings.multi_threaded_message_loop) {
// Run the CEF message loop. This function will block until the application
// recieves a WM_QUIT message.
CefRunMessageLoop();
} else {
MSG msg;
// Run the application message loop.
while (GetMessage(&msg, NULL, 0, 0)) {
// Allow processing of find dialog messages.
if (hFindDlg && IsDialogMessage(hFindDlg, &msg))
continue;
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
result = static_cast<int>(msg.wParam);
}
// Shut down CEF.
/ cefShutdown() 崩溃下面会提到。
CefShutdown();
return result;
}
// 创建内嵌页
// 详细的代码在 cefclient_win.cpp
// Create the single static handler class instance
g_handler = new ClientHandler();
g_handler->SetMainHwnd(hWnd);
// Create the child windows used for navigation
RECT rect;
GetClientRect(hWnd, &rect);
CefWindowInfo info;
CefBrowserSettings settings;
// Populate the settings based on command line arguments.
AppGetBrowserSettings(settings);
// Initialize window info to the defaults for a child window
info.SetAsChild(hWnd, rect);
// Creat the new child browser window
int nRet = CefBrowser::CreateBrowser(info,
static_cast<CefRefPtr<CefClient> >(g_handler),
g_handler->GetStartupURL(), settings);
根据自己项目的不同,不一定要和示例程序完全一样,不过核心的步骤一定要有:
// Parse command line arguments. The passed in values are ignored on Windows.
AppInitCommandLine(0, NULL);
CefSettings settings;
CefRefPtr<CefApp> app;
// Populate the settings based on command line arguments.
AppGetSettings(settings, app);
// 初始化
// Initialize CEF.
CefInitialize(settings, app);
g_handler = new ClientHandler();
g_handler->SetMainHwnd(hWnd);
// 创建
int nRet = CefBrowser::CreateBrowser(info,
static_cast<CefRefPtr<CefClient> >(g_handler),
g_handler->GetStartupURL(), settings);
// 关闭
// Shut down CEF.
CefShutdown();
客户端工程引入
- 拷贝解压根目录中的 include 目录到客户端工程目录, 并配置好引入目录(属性->c/c++ ->General ->Additionnal Include Directionories)。
- 将解压目录 lib->[Debug|Release] ->libcef.lib , 根目录 [Debug|Release] -> lib -> libcef_dll_wrapper.lib 两个链接库放到客户端工程链接库目录,并配置好引入目录和库配置(属性-> Linker -> Additional Library Directories 和 属性 -> Linker -> input -> Additional Dependencies)。
-
参照示例程序写好内嵌页后就可以编译客户端了, 编译过程中可能会出现一些问题, 下面是我遇到的。
-4503 各种 这些类似的警告和报错。参照示例 cefclient 的 project 属性 -> c/c++ -> Advanced -> Disable Specific Warnings 里面的配置,拷贝到客户端的对应配置。可能还会有一些其他的报错,可以google 下自己找。
-还有一些 error C2220: warning treated as error - no ‘object’ file generated 类似的错误。有可能是Runtime Library 配置的问题。例如我们客户的: 属性 -> c/c++ -> Coode Generation -> Runtime Library 的配置是 Multi-threaded Debug (/MTd), 但 libcef_dll_wrapper 中的配置是Multi-threaded Debug DLL (/MDd)。 把libcef_dll_wrapper 中的配置改成和客户端的一样然后重新编译 libcef_dll_wrapper .lib 。
-可能还会有其他错误。可以对比客户端和示例项目cefclient的工程配置有哪些不一样的进行查验。比如cefclient中的预定义,有几个可能客户端中没有进行定义。
注意点:
客户端编好后,需要把cef_binary_1.1180.832_windows.zip解压后根目录下 Debug或 Release 目录下 libGLESv2.dll , libEGL.dll, libcef.dll, icudt.dll 和 locales 目录都拷到客户端程序的根目录, 如果如果不放心就把所有的文件都拷过去。 我一开始没拷locales, 导致了程序在执行初始化CefInitialize(settings, app); 的时候就崩溃。 然后不是源码编译libcef.lib不能进行DUBG调试,找网上找了好久。
setting.multi_threaded_message_loop是true 还是 false。要根据自己的程序而定。我们的客户端是要配置成true的。所以不需要调用CefRunMessageLoop()处理消息。如果配置setting.multi_threaded_loop 不对,就有可能在调用CefgShutdown()的时候崩溃。