使用CEF(二)— 基于VS2019编写一个简单CEF样例

使用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的头文件:

右键项目propertiesC/C++GeneralAdditional 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的静态库。添加方式为:

propertiesLinkerInputAdditional 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平台。

  1. 通过GetHost()来获取CEF浏览器对象的宿主对象(这里就是Windows原生窗体);
  2. 再获取对应的窗体句柄;
  3. 通过#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文件从而无法处理操作系统的版本问题。

解决方案

  1. 创建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>
  1. 为项目添加上述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文件放在源码库中,因为静态库超过了大小。请读者自行编译并按照指定的方式添加。

posted @ 2021-01-15 10:11  w4ngzhen  阅读(2235)  评论(0编辑  收藏  举报