0%

CEF编译与Windows桌面应用开发

  1. 编译示例测试
  2. 编译MD静态库
    1. 正确且简单的做法
    2. 为啥要这么做?
    3. 编译
    4. 测试
  3. 需要拷贝的文件
  4. Hello World
    1. 显示一个网页窗口
    2. 优化:将主程序抽象为App类,窗口由其管理控制
      1. App类
      2. WindowDelegate类
      3. main.cpp
      4. 最终效果

参考:CEF 桌面软件开发实战 - 刘晓伦liulun - 掘金小册 (juejin.cn)

相关链接

编译示例测试

编译环境:

  • VS2022(v143)
  • CMake 3.24

CMakeList.txt拖入CMake,创建输出目录后,即可直接Configure + Generate(遇到一个找不到Doxygen的错误,没管)

最后点击“Open Project”,用VS2022打开工程

右键cefsimple解决方案,生成,启动新实例,即可检验是否编译生成成功

image-20230304121326327

直接执行默认打开google的首页,可以在simple_app.cc文件中修改默认链接地址

image-20230304121610216

打开后界面如下

image-20230304121650236

生成后的exe保存到了构建输出目录的tests\cefsimple\Debug文件夹下(解决方案配置选择Release则在tests\cefsimple\Release下),这里面包含了一个完整的CEF软件,打开cefsample.exe,可以感受一下启动并渲染网页的速度(吊打Electron)。

image-20230304122117891

编译MD静态库

原本想着把库编译成dll就可以减小libcel.dll的体积,后来发现根本没法减少,天真了哈哈哈。

正确且简单的做法

chromiumembedded / cef / wiki / LinkingDifferentRunTimeLibraries — Bitbucket

在CMake构建阶段,把CEF_RUNTIME_LIBRARY_FLAG改成/MD即可。

image-20230304170752792

然后把USE_SANDBOX勾掉

image-20230304170727003

构建,生成,打开VS。然后在批生成里面,把libcef_dll_wrapper的两个配置都生成一下,分别得到Debug和Release的MD的.lib文件,完成。

下面都不对!!

为啥要这么做?

现有工程用的MD,就这么简单。

根本没法减少libcef.dll的体积,那里面有个chrome,减个屁。

在源码生成的工程中,解决方案都默认采用了MT的运行库,采用MT的好处在于:打包生成的dll中包含了所有软件运行时需要的环境支撑,不再需要引入诸如MSVCRxxx.dll等VC环境,看着相对简洁且易于拷贝,适用于独立应用。其缺点在于,该部分dll体积就要大得多(默认使用的libcef.dll在Release模式下达到了180M以上)。

使用MD运行库的好处在于,CEF生成的dll仅包含了自身的函数接口,其余的系统函数调用仍需要依赖MSVCRxxx.dllVCRUNTIME.dll等dll,这种时候编译的dll体积就会小很多,适用于嵌入到已有的工程中

由于想在MFC应用中嵌入CEF,因此选择将CEF编译为动态库。

如果需要构建使用MT运行库的CEF应用,可以完全参考解决方案cefsimple的工程配置以及其中的cefsimple_win.cc文件,包括头文件引入目录、lib链接、预定义等。

编译

只需要将CEF_USE_SANDBOX删掉即可,因为沙盒用的是MT,不这么干会报错。

如何将cef静态库(/MT)编译为动态库(/MD)cef库峰峰小筑的博客-CSDN博客

下面开始编译

  1. 右键libcef_dll_wrapper属性
    1. 将配置类型改为.dll,点击应用
    2. 在“高级”里面,将目标文件扩展名改为.dll
    3. 将MFC的使用改成“在共享DLL中使用MFC”
    4. 在C/C++,代码生成,将运行库改成MD
    5. 在C/C++,将预处理器定义里面的CEF_USE_SANDBOX删掉
    6. 在链接器-输入-附加依赖项,将cefsimple该位置处的所有内容拷贝过来,并且删掉两处:libcef_dll_wrapper.libcef_sandbox.lib(若在后续开发中需要打开互联网上的网页,则建议手动编译一个带sandbox的dll,或者使用MT。这里是因为只打算加载自己的页面,以及sandbox不太好编译,故直接放弃)
  2. 配置完成后,点击 生成-批生成,将libcef_dll_wrapperDebug和Release勾上,即可生成dll

为什么没有lib?因为没有选择导出函数,导出函数需要在函数定义处加上__declspec(dllexport),这里暂时不这么做,后续需要时考虑按需导出。

测试

。。。

image-20230304163713880

需要拷贝的文件

编译完成后,源码工程中除了编译成果,还有一些文件需要直接拷贝至运行目录才能保证cef正确运行:

  • Resources文件夹

image-20230304172451789

  • Debug和Release文件夹

image-20230304172517560

最后得到应用程序的大体结构:

image-20230304172628029

Hello World

项目配置:在自定工程创建cef文件夹,将源码目录下的include文件夹拷贝到该文件夹下;创建cef/lib/Debugcef/lib/Release,拷贝相应的.lib文件,如下所示:

image-20230304184343685

引用目录就写../cef,库目录写../cef/lib/$(Configuration)/*.lib

显示一个网页窗口

用最少的代码,调用CEF,显示一个网页窗口(但有很多不合理的地方以及运行时会出BUG)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <include/cef_app.h>

int main()
{
// 开启高DPI支持(Win7及之后版本)
CefEnableHighDPISupport();

// 获取命令行参数
CefMainArgs mainArgs;

// CEF 应用包括多个子进程(渲染进程/GPU进程等),这些进程共享一个可执行程序
// 子进程也会调用这个函数,执行到这一步返回值不为-1,则就退出了
int exitCode = CefExecuteProcess(mainArgs, nullptr, nullptr);
if (exitCode >= 0) return exitCode;

// 应用配置
CefSettings settings;
settings.no_sandbox = true; // 无沙盒

// 初始化
CefInitialize(mainArgs, settings, nullptr, nullptr);

// 创建浏览器窗口
CefWindowInfo winInfo;
CefBrowserSettings browserSettings;
browserSettings.windowless_frame_rate = 60; // 最低帧率
CefBrowserHost::CreateBrowser(winInfo, nullptr, "http://baidu.com", browserSettings,
nullptr, nullptr);

// 开启消息循环
CefRunMessageLoop();
CefShutdown();
return 0;
}

优化:将主程序抽象为App类,窗口由其管理控制

App类

作为整个应用程序的入口,用于管理一个或多个窗口

App.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma once
#include <include/cef_app.h>

class App : public CefApp, public CefBrowserProcessHandler
{
public:
App() = default;

// 返回进程控制句柄(自身)。重载CefApp
CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; }

// 上下文初始化完成后触发
void OnContextInitialized() override;

private:
// 该宏为Cef智能指针提供帮助
IMPLEMENT_REFCOUNTING(App);
};

App.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "App.h"
#include <include/wrapper/cef_helpers.h>
#include <include/views/cef_browser_view.h>
#include <include/views/cef_window.h>
#include "WindowDelegate.h"

void App::OnContextInitialized()
{
// 确保在主线程中调用
CEF_REQUIRE_UI_THREAD();

// 创建浏览器视图
auto url = "http://baidu.com";
CefBrowserSettings settings;
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
nullptr, url, settings, nullptr, nullptr, nullptr);

// 创建代理,代理浏览器视图,并将代理交给窗口
CefWindow::CreateTopLevelWindow(new WindowDelegate(browser_view));
}

WindowDelegate类

用于代理浏览窗口视图,可以响应窗口的创建、销毁、尺寸变化、鼠标键盘点击等事件。

WindowDelegate.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once
#include <include/views/cef_window.h>
#include <include/views/cef_browser_view.h>

class WindowDelegate :public CefWindowDelegate
{
public:
explicit WindowDelegate(CefRefPtr<CefBrowserView> browser_view)
: _browser_view(browser_view) { }

// 不允许拷贝
WindowDelegate(const WindowDelegate&) = delete;
WindowDelegate& operator=(const WindowDelegate&) = delete;

void OnWindowCreated(CefRefPtr<CefWindow> window) override;
void OnWindowDestroyed(CefRefPtr<CefWindow> window) override;
CefRect GetInitialBounds(CefRefPtr<CefWindow> window) override;

private:
CefRefPtr<CefBrowserView> _browser_view;

IMPLEMENT_REFCOUNTING(WindowDelegate);
};

WindowDelegate.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "WindowDelegate.h"
#include <include/cef_app.h>
#include <include/views/cef_display.h>

void WindowDelegate::OnWindowCreated(CefRefPtr<CefWindow> window)
{
// 当拿到窗口后,将浏览器视图设置上去
window->AddChildView(_browser_view);
window->Show();

// 聚焦
_browser_view->RequestFocus();

// 设置标题
window->SetTitle(L"Hello World");
}

void WindowDelegate::OnWindowDestroyed(CefRefPtr<CefWindow> window)
{
_browser_view = nullptr;
CefQuitMessageLoop();
}

CefRect WindowDelegate::GetInitialBounds(CefRefPtr<CefWindow> window)
{
// 获取显示器信息
CefRefPtr<CefDisplay> display = CefDisplay::GetPrimaryDisplay();
CefRect rect = display->GetBounds();

// 设置窗口大小和位置
int width = 1366;
int height = 768;

rect.x = static_cast<int>((rect.width - width) / 2);
rect.y = static_cast<int>((rect.height - height) / 2);
rect.width = width;
rect.height = height;

return rect;
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <include/cef_app.h>
#include "App.h"

int main()
{
// 开启高DPI支持(Win7及之后版本)
CefEnableHighDPISupport();

// 获取命令行参数
CefMainArgs mainArgs;

// CEF 应用包括多个子进程(渲染进程/GPU进程等),这些进程共享一个可执行程序
// 子进程也会调用这个函数,执行到这一步返回值不为-1,则就退出了
int exitCode = CefExecuteProcess(mainArgs, nullptr, nullptr);
if (exitCode >= 0) return exitCode;

// 应用配置
CefSettings settings;
settings.no_sandbox = true; // 无沙盒

// 创建应用 初始化
CefRefPtr<App> app(new App());
CefInitialize(mainArgs, settings, app.get(), nullptr);

// 开启消息循环
CefRunMessageLoop();
CefShutdown();
return 0;
}

最终效果

image-20230306153057609