使用CEF(二)— 基于VS2019编写一个简单CEF样例
在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进行修改配置和代码编写,并在这个过程中介绍vs使用过程中和C++项目的结合。源码见文章末尾Github链接。
前提
你已经阅读过《使用CEF(1)— 起步》,你可以在这些地方读到:知乎链接、cnblogs。或,你知道如何获得libcef的库以及libcef_dll_wrapper静态库。
文件准备
接下来,本人将以Debug的模式下完成代码的开发工作。在Release下是同样的步骤,但是需要注意的是你所选择的目标是Debug或是Release都需要和libcef库以及libcef_dll_wrapper完全一致。
- 现在,你需要libcef库文件相关文件,它来自于:
- 你需要使用libcef_dll_wrapper静态库文件,它来自于你编译出来的静态库:
- 你需要libcef与wrapper的include文件,它来自于:
接下来我们创建一个名为cef的文件夹,并且把上述提到的文件夹和文件放到该目录下:
cef
│ libcef_dll_wrapper.lib
│ libcef_dll_wrapper.pdb
│
├─Debug
│ │ ......
│ │ libcef.dll
│ │ libcef.lib
│ │ libEGL.dll
│ │ libGLESv2.dll
│ │ ......
│ │
│ └─swiftshader
│ libEGL.dll
│ libGLESv2.dll
│
└─include
│ cef_accessibility_handler.h
│ cef_api_hash.h
│ cef_app.h
│ cef_audio_handler.h
| .....
基础文件创建完成后,我们开始编写一个简单的基于CEF的程序吧!
项目创建
创建一个Windows桌面应用程序
创建一个名为simple-cef的项目
创建完成后,我们删除所有模板生成的代码,得到一个完全空白的应用程序项目:
依赖添加
头文件添加
众所周知,C/C++头文件作为声明定义,对于编译过程有着举足轻重的位置。当我们引入CEF编译我们的项目时候,首先需要include正确位置的头文件,才能实现编译(狭义的编译,不包括链接)。我们首先把上述做好的cef文件夹放到项目所在目录下,也就是说我们把cef的inlucde头文件以及静态库文件全都加到了项目中:
然后,在VS中,我们通过如下的方式为我们的项目引入CEF的头文件:
右键项目 — properties — C/C++ — General — Additional Include Directories
PS:如果你发现没有C/C++分类,是因为你没有创建任何的源代码文件,官方FAQ。所以我们在Source Files目录下先创建一个main.cpp,然后继续上述的配置。
PS:这里本人使用了$(ProjectDir)
,它是一个VS宏变量,返回项目所在目录(即,vcxproj所在目录),且目录末尾带反斜杠\
。从上面的Evaluated value里面展示的经过实际计算得到的值,可以验证我们配置是否正确。这里正确的返回了我们放在项目目录下的cef文件夹。
这里只需要添加到cef文件夹这一层级,是因为cef/include里面的头文件在include的时候,采用了对应的"include/xxx.h",即需要从引入目录中找到include文件夹,里面查找xxx.h头文件。当我们指定到了cef层级后,就能够使得编译器正确处理cef头文件中include的位置。
这里以$(ProjectDir)cef/include/cef_broweser.h这个头文件举例:
当编译器发现里面的#include预编译命令后,会从头文件目录中去查找,即希望从上述配置的\((ProjectDir)cef/**以及默认目录下查找,默认的项目目录应该是找不到了,但是可以在**\)(ProjectDir)cef/目录下找到include/cef_base.h等文件,因为$(ProjectDir)cef/include/cef_base.h确实是正确的文件路径。因此,上述额外的include文件夹只需要指定到cef层级即可。
库文件添加
完成头文件的添加后,我们还需要添加链接目标,即cef的静态库。添加方式为:
properties — Linker — Input— Additional Dependencies
同样使用宏变量来指定对应的lib静态库:libcef_dll_wrapper.lib、libcef.lib、cef_sandbox.lib。
通过上述的库文件添加,我们就完成了编译(狭义,头文件查找)——链接(库文件链接)这两个步骤的配置了,接下来就是进一步,开始我们的代码编写之路。
代码编写与说明
CEF的整体架构以及CefApp以及CefClient的概念可以参考该仓库里面的文档,或者是阅读官方文档。接下来将使用cefsimple代码进行解释说明,并适当增加一些小的细节。
simple_app
simple_app.h
#ifndef SIMPLE_APP_H
#define SIMPLE_APP_H
#pragma once
#include "include/cef_app.h"
// Implement application-level callbacks for the browser process.
class SimpleApp : public CefApp, public CefBrowserProcessHandler {
public:
SimpleApp();
// CefApp methods:
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
OVERRIDE {
return this;
}
// CefBrowserProcessHandler methods:
virtual void OnContextInitialized() OVERRIDE;
private:
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(SimpleApp);
};
#endif
这里引入的时候,如果发现VS提示,#include "include/cef_app.h"
无效,首先检查上述的对项目的配置是否正确!上述项目Properties中配置的平台是x64,VS中也请选择一致的平台。而且在本Demo是无法使用32位的,因为我们下载的静态库是x64位的。
simple_app.cpp
在simple_app的实现中,主要需要提供3个部分的代码实现:
- CefWindowDelegate
- CefBrowserViewDelegate
- SimpleApp
CefWindowDelegate与CefBrowserViewDelegate
Cef窗体代理以及Cef浏览器视图代理,他们是CEF提供的一套图形视图框架。这一套图形接口目前在Windows和Linux上支持了,所以在Windows和Linux我们完全可以不用选择原生的窗体框架(例如在Windows上的WinForm和Linux上的QT之类的),而是直接使用CEF提供的图形视图框架。而CEF的图形视图框架的内部实现原理我们暂时不需要知道,可以把它们想象成一些窗体和控件对象,它们需要在SimpleApp中的实现用到,所以也写在了simple_app.cpp中。相关代码如下:
// SimpleBrowserViewDelegate
// 继承CefBrowserViewDelegate,即CEF浏览器视图代理。
// 该代理由CEF屏蔽细节,只暴露出视图控件指定的接口回调供我们实现即可
class SimpleBrowserViewDelegate : public CefBrowserViewDelegate
{
public:
SimpleBrowserViewDelegate()
{
}
bool OnPopupBrowserViewCreated(CefRefPtr<CefBrowserView> browser_view,
CefRefPtr<CefBrowserView> popup_browser_view,
bool is_devtools) OVERRIDE
{
// Create a new top-level Window for the popup. It will show itself after
// creation.
CefWindow::CreateTopLevelWindow(
new SimpleWindowDelegate(popup_browser_view));
// We created the Window.
return true;
}
private:
IMPLEMENT_REFCOUNTING(SimpleBrowserViewDelegate);
DISALLOW_COPY_AND_ASSIGN(SimpleBrowserViewDelegate);
};
// SimpleWindowDelegate
// 继承CefWindowDelegate,即CEF窗口代理。
// 该代理由CEF屏蔽细节,只暴露窗口一些接口回调供我们实现即可。
class SimpleWindowDelegate : public CefWindowDelegate
{
public:
explicit SimpleWindowDelegate(CefRefPtr<CefBrowserView> browser_view)
: browser_view_(browser_view)
{
}
// 窗体创建时
void OnWindowCreated(CefRefPtr<CefWindow> window) OVERRIDE
{
// Add the browser view and show the window.
window->AddChildView(browser_view_);
window->Show();
// Give keyboard focus to the browser view.
browser_view_->RequestFocus();
}
// 窗体销毁时
void OnWindowDestroyed(CefRefPtr<CefWindow> window) OVERRIDE
{
browser_view_ = nullptr;
}
// 窗体是否可以关闭
bool CanClose(CefRefPtr<CefWindow> window) OVERRIDE
{
// Allow the window to close if the browser says it's OK.
CefRefPtr<CefBrowser> browser = browser_view_->GetBrowser();
if (browser)
return browser->GetHost()->TryCloseBrowser();
return true;
}
// 获取窗体展示的最佳尺寸
CefSize GetPreferredSize(CefRefPtr<CefView> view) OVERRIDE
{
return CefSize(800, 600);
}
private:
CefRefPtr<CefBrowserView> browser_view_;
IMPLEMENT_REFCOUNTING(SimpleWindowDelegate);
DISALLOW_COPY_AND_ASSIGN(SimpleWindowDelegate);
};
SimpleApp
SimpleApp::SimpleApp()
{
}
void SimpleApp::OnContextInitialized()
{
CEF_REQUIRE_UI_THREAD();
CefRefPtr<CefCommandLine> command_line =
CefCommandLine::GetGlobalCommandLine();
const bool enable_chrome_runtime =
command_line->HasSwitch("enable-chrome-runtime");
#if defined(OS_WIN) || defined(OS_LINUX)
// Create the browser using the Views framework if "--use-views" is specified
// via the command-line. Otherwise, create the browser using the native
// platform framework. The Views framework is currently only supported on
// Windows and Linux.
const bool use_views = command_line->HasSwitch("use-views");
#else
const bool use_views = false;
#endif
// SimpleHandler implements browser-level callbacks.
CefRefPtr<SimpleClient> handler(new SimpleClient(use_views));
// Specify CEF browser settings here.
CefBrowserSettings browser_settings;
std::string url;
// Check if a "--url=" value was provided via the command-line. If so, use
// that instead of the default URL.
url = command_line->GetSwitchValue("url");
if (url.empty())
url = "https://www.cnblogs.com/w4ngzhen/";
if (use_views && !enable_chrome_runtime)
{
// Create the BrowserView.
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
handler, url, browser_settings, nullptr, nullptr,
new SimpleBrowserViewDelegate());
// Create the Window. It will show itself after creation.
CefWindow::CreateTopLevelWindow(new SimpleWindowDelegate(browser_view));
}
else
{
// Information used when creating the native window.
CefWindowInfo window_info;
#if defined(OS_WIN)
// On Windows we need to specify certain flags that will be passed to
// CreateWindowEx().
window_info.SetAsPopup(NULL, "simple-cef by w4ngzhen");
#endif
// Create the first browser window.
CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
nullptr, nullptr);
}
}
simple_client
simple_client.h
#ifndef SIMPLE_CLIENT_H
#define SIMPLE_CLIENT_H
#include "include/cef_client.h"
#include <list>
class SimpleClient : public CefClient,
public CefDisplayHandler,
public CefLifeSpanHandler,
public CefLoadHandler
{
public:
explicit SimpleClient(bool use_views);
~SimpleClient();
static SimpleClient* GetInstance();
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE
{ return this; }
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE
{ return this; }
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE { return this; }
// CefDisplayHandler的实现声明:
virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,
const CefString& title) OVERRIDE;
// CefLifeSpanHandler的实现声明:
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
// CefLoadHandler的实现声明:
virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) OVERRIDE;
void CloseAllBrowsers(bool force_close); // 请求将所有的已经存在的浏览器窗体进行关闭
bool IsClosing() const { return is_closing_; }
private:
// 平台特定的标题修改
// 当我们没有CEF的GUI视图框架的时候,就需要特定平台的标题修改实现
// 例如,Windows中需要我们获取窗体句柄,调用Windows的API完成对该窗体的标题修改
void PlatformTitleChange(CefRefPtr<CefBrowser> browser,
const CefString& title);
const bool use_views_; // 是否使用了CEF的GUI视图框架
// List of existing browser windows. Only accessed on the CEF UI thread.
typedef std::list<CefRefPtr<CefBrowser>> BrowserList;
BrowserList browser_list_;
bool is_closing_;
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(SimpleClient);
};
#endif
simple_client.cpp以及simple_client_os_win.cpp
这里我们提供了两份源代码,第一份是所有平台的通用实现,而第二份源码从名称可以看出跟特定的操作系统平台有关,这里就是Windows,为什么会有两份源码我们下文会逐步了解。
首先看simple_client.cpp的源代码:
#include "simple_client.h"
#include <sstream>
#include <string>
#include "include/base/cef_bind.h"
#include "include/cef_app.h"
#include "include/cef_parser.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
namespace
{
SimpleClient* g_instance = nullptr;
// Returns a data: URI with the specified contents.
std::string GetDataURI(const std::string& data, const std::string& mime_type)
{
return "data:" + mime_type + ";base64," +
CefURIEncode(CefBase64Encode(data.data(), data.size()), false)
.ToString();
}
} // namespace
SimpleClient::SimpleClient(bool use_views)
: use_views_(use_views), is_closing_(false)
{
DCHECK(!g_instance);
g_instance = this;
}
SimpleClient::~SimpleClient()
{
g_instance = nullptr;
}
// static
SimpleClient* SimpleClient::GetInstance()
{
return g_instance;
}
void SimpleClient::OnTitleChange(CefRefPtr<CefBrowser> browser,
const CefString& title)
{
CEF_REQUIRE_UI_THREAD();
if (use_views_)
{
// 如果使用CEF的GUI视图框架,那么修改窗体的标题通过调用该视图框架的API完成
CefRefPtr<CefBrowserView> browser_view =
CefBrowserView::GetForBrowser(browser);
if (browser_view)
{
CefRefPtr<CefWindow> window = browser_view->GetWindow();
if (window)
window->SetTitle(title);
}
}
else
{
// 否则使用特定平台窗体标题修改API
// 详情见simple_client_os_win.cpp
PlatformTitleChange(browser, title);
}
}
void SimpleClient::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
// Add to the list of existing browsers.
browser_list_.push_back(browser);
}
bool SimpleClient::DoClose(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
// Closing the main window requires special handling. See the DoClose()
// documentation in the CEF header for a detailed destription of this
// process.
if (browser_list_.size() == 1)
{
// Set a flag to indicate that the window close should be allowed.
is_closing_ = true;
}
// Allow the close. For windowed browsers this will result in the OS close
// event being sent.
return false;
}
void SimpleClient::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
// Remove from the list of existing browsers.
BrowserList::iterator bit = browser_list_.begin();
for (; bit != browser_list_.end(); ++bit)
{
if ((*bit)->IsSame(browser))
{
browser_list_.erase(bit);
break;
}
}
if (browser_list_.empty())
{
// All browser windows have closed. Quit the application message loop.
CefQuitMessageLoop();
}
}
void SimpleClient::OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl)
{
CEF_REQUIRE_UI_THREAD();
// Don't display an error for downloaded files.
if (errorCode == ERR_ABORTED)
return;
// Display a load error message using a data: URI.
std::stringstream ss;
ss << "<html><body bgcolor=\"white\">"
"<h2>Failed to load URL "
<< std::string(failedUrl) << " with error " << std::string(errorText)
<< " (" << errorCode << ").</h2></body></html>";
frame->LoadURL(GetDataURI(ss.str(), "text/html"));
}
void SimpleClient::CloseAllBrowsers(bool force_close)
{
if (!CefCurrentlyOn(TID_UI))
{
// Execute on the UI thread.
CefPostTask(TID_UI, base::Bind(&SimpleClient::CloseAllBrowsers, this,
force_close));
return;
}
if (browser_list_.empty())
return;
BrowserList::const_iterator it = browser_list_.begin();
for (; it != browser_list_.end(); ++it)
(*it)->GetHost()->CloseBrowser(force_close);
}
上述代码有重要部分为函数SimpleClient::OnTitleChange
的实现。在该实现代码中,通过判断变量use_views_
来决定是否使用CEF提供的视图框架,也就有了下面两种情况:
- 使用了CEF提供的视图框架:在这种情况下,窗体的标题改变直接使用CEF视图框架提供的API完成修改;
- 未使用CEF提供的视图框架:在这种情况下,我们一定用了原生的窗体框架或者是第三方的(QT或者GTK+),那么就需要调用相关原生窗体的API或者第三方的API来完成窗体标题的修改。
由于存在上面的情况2,才有了下面的simple_client_os_win.cpp的代码。(PS:上面的代码并没有实现头文件里面的PlatformTitleChange声明哟,只是调用了而已)
// simple_client_os_win.cpp代码
#include "simple_client.h"
#include <windows.h>
#include <string>
#include "include/cef_browser.h"
void SimpleClient::PlatformTitleChange(CefRefPtr<CefBrowser> browser,
const CefString& title) {
// 通过GetHost()来获取CEF浏览器对象的宿主对象(这里就是Windows原生窗体)
// 再获取对应的窗体句柄
// 通过#include <windows.h>得到的WindowsAPI完成标题修改
CefWindowHandle hwnd = browser->GetHost()->GetWindowHandle();
if (hwnd)
SetWindowText(hwnd, std::wstring(title).c_str());
}
这段代码实际上跟特定的平台有关,这里就是Windows平台。
- 通过GetHost()来获取CEF浏览器对象的宿主对象(这里就是Windows原生窗体);
- 再获取对应的窗体句柄;
- 通过#include <windows.h>得到的WindowsAPI完成标题修改。
入口代码main.cpp
编写完成上述的CEF应用模块后,我们最后编写入口代码。
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include <windows.h>
#include "include/cef_command_line.h"
#include "include/cef_sandbox_win.h"
#include "simple_app.h"
// When generating projects with CMake the CEF_USE_SANDBOX value will be defined
// automatically if using the required compiler version. Pass -DUSE_SANDBOX=OFF
// to the CMake command-line to disable use of the sandbox.
// Uncomment this line to manually enable sandbox support.
// #define CEF_USE_SANDBOX 1
#if defined(CEF_USE_SANDBOX)
// The cef_sandbox.lib static library may not link successfully with all VS
// versions.
#pragma comment(lib, "cef_sandbox.lib")
#endif
// 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 = nullptr;
#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, nullptr, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
// Parse command-line arguments for use in this method.
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(::GetCommandLineW());
// Specify CEF global settings here.
CefSettings settings;
if (command_line->HasSwitch("enable-chrome-runtime")) {
// Enable experimental Chrome runtime. See issue #2969 for details.
settings.chrome_runtime = true;
}
#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;
}
编译与运行
上述代码完成后,我们的代码结构如下:
我们右键项目使用build指令进行尝试编译,如果不出意外会看到这些内容:
error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MTd_StaticDebug' doesn't match value 'MDd_DynamicDebug'
译为中文大意为:未检测到运行时库:MTd_StaticDebug
无法匹配MDd_DynamicDebug
,MTd是什么?MDd又是什么?关键字:MD、MDd、MT以及MTd。读者可以参考这篇文章深入了解:VS运行时 /MD、/MDd 和 /MT、/MTd之间的区别。简单一点讲,我们编译出来的libcef_dll_wrapper.lib库的某个标志与我们当前编译的程序的某个标志不一致:一个是MTd一个是MDd。那么这个标志在哪儿设置呢?我们可以右键项目工程——properties——C/C++——Code Generation(代码生成)——Runtime Library中看到。
在我们的simple项目中,VS在创建项目的时候默认使用了MDd,那么libcef_dll_wrapper.lib又是使用的什么呢?在《使用CEF(1)— 起步》文章中编译libcef_dll_wrapper.lib的项目目录下使用的是MTd。下图是再回看当时的项目使用的运行库类型:
当然,具体情况也要具体判断。例如Debug与Release的不同,又或者是当时确实是使用MD(d)进行编译的,总之需要一一对应起来。这里我们修改我们的simple项目的RuntimeLibrary为对应的MTd,再次进行编译。不出意外,你会看到如下的编译成功的输出:
Rebuild started...
1>------ Rebuild All started: Project: simple-cef, Configuration: Debug x64 ------
1>main.cpp
1>simple_app.cpp
1>simple_client.cpp
1>simple_client_os_win.cpp
1>Generating Code...
1>simple-cef.vcxproj -> D:\Projects\cef-projects\simple-cef\x64\Debug\simple-cef.exe
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
于是,我们运行生成出来的exe,不出意外会有弹框报错。
---------------------------
simple-cef.exe - 系统错误
---------------------------
由于找不到 libcef.dll,无法继续执行代码。重新安装程序可能会解决此问题。
---------------------------
确定
---------------------------
检查目录下发现,确实只有个孤单的可执行程序,并没有那些依赖库。此时我们需要将所有的依赖文件全部复制到运行目录下,主要有以下几个部分需要拷贝:
- Resources
把Resources文件夹里面的所有文件和子文件夹复制到运行目录下。
- CEF依赖库文件
将上图中除了两个lib库文件之外的组件拷贝到运行目录下。
此时,我们的编译出来的运行目录如下:
我们再次尝试运行该simple-cef,终于能够成功打开,然而再次不出意外的话,会看到一个白屏的浏览器窗口。首先会看到标题,然后转为对应的空白:
运行问题:Check failed: fallback_available == base::win::GetVersion() > base::win::Version::WIN8 (1 vs. 0)
上述白屏后,还会在运行目录下会看到一个名为debug.log
的文件,打开检查内容。
// debug.log
[0124/113454.346:INFO:content_main_runner_impl.cc(976)] Chrome is running in full browser mode.
[0124/113454.488:FATAL:dwrite_font_proxy_init_impl_win.cc(91)] Check failed: fallback_available == base::win::GetVersion() > base::win::Version::WIN8 (1 vs. 0)
[0124/113454.545:FATAL:dwrite_font_proxy_init_impl_win.cc(91)] Check failed: fallback_available == base::win::GetVersion() > base::win::Version::WIN8 (1 vs. 0)
该错误的关键字:CEF base::win::GetVersion() > base::win::Version::WIN8。这里能够得到一个CEF官方论坛的解答:CEF Forum Check failed: fallback_available (magpcss.org)。简单来说,浏览器程序无法加载manifest文件从而无法处理操作系统的版本问题。
解决方案
- 创建manifest文件放在项目根目录下
在项目根目录下创建一个manifest文件:simple-cef.manifest
<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates application support for Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- 10.0 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>
- 为项目添加上述manifest
打开项目的属性,找到Manifest Tool —— Input and Output —— Additional Manifest Files,选择项目根目录下的simple-cef.manifest。
保存后,我们再次构建项目并运行我们的simple-cef.exe,终于看到了期待已久的页面:
写在结尾
在不断的踩坑下,我们终于得到了一个网络页面,不过这并不意味着我们的使用CEF之旅就结束了,恰恰相反,通过这个Demo,我们接触到了更多的东西,有CefApp、CefClient类,有CefBrowserProcessHandler等等,这些类是干什么的?CefWindowDelegate、CefBrowserViewDelegate这里些CEF框架提供的窗体GUI代理又是怎样的概念?CEF跨平台的实现策略又是怎样的呢?问题只增不减,本人也会就着这些问题继续探索并给出总结。
源代码
w4ngzhen/simple-cef (github.com)
PS:在改源码中,没有将上述的cef相关库以及include文件放在源码库中,因为静态库超过了大小。请读者自行编译并按照指定的方式添加。