在前文《使用CEF(四)— 在QT中集成CEF(1):基本集成》中,我们使用VS+QT的插件搭建了一个基于QT+CEF的项目。时过境迁,笔者目前用的最多的就是CLion+CMake搭建C/C++项目,并且CLion提供了对C/C++强大的开发环境。此外,也想将CMake搭建QT项目作为一次实践,故由此文。

基础环境

  • QT 5.14.2
  • CEF 105.3.33以及对应版本wrapper(特别注意,wrapper以动态库(MD)版本进行编译。为了方便更多的开发者了解如何编译,我做了一个视频,视频是MT版本,请读者自行修改配置。)
  • CMake 3.24-rc5
  • VS2019

工程搭建

创建QtCefCMakeDemo文件夹,将基础环境提到的CEF的wrapper编译产物(libcef_dll_wrapper)+CEF相关库文件(libcef)、资源文件(*.pak)放置于QtCefCMakeDemo/CefFiles中:

QtCefCMakeDemo
└─ CefFiles
├─bin
│ ├─Debug
│ │ │ ...
│ │ │ libcef.dll
│ │ │ libcef.lib
│ │ │ libcef_dll_wrapper.lib
│ │ │ ...
│ │ │
│ │ └─swiftshader
│ │ ...
│ │
│ └─Release
│ │ ...
│ │ libcef.dll
│ │ libcef.lib
│ │ libcef_dll_wrapper.lib
│ │ ...
│ │
│ └─swiftshader
│ ...

├─include
│ 各种.h头文件
│ ...
└─Resources
│ cef.pak
│ ..
└─locales
...
zh-CN.pak
zh-TW.pak

并且在QtCefCMakeDemo目录下创建一个src目录,用以存放cpp代码。将咱们在《在QT中集成CEF(1)》中编写的相关代码存放于该目录下(QtCefDemo/QtCefDemo at main · w4ngzhen/QtCefDemo (github.com)):

QtCefCMakeDemo
├─ CefFiles
└─ src
app.manifest
main.cpp
qtcefwindow.cpp
qtcefwindow.h
qtcefwindow.qrc
qtcefwindow.ui
simple_app.cpp
simple_app.h
simple_handler.cpp
simple_handler.h
stdafx.cpp
stdafx.h
... ...

请注意,这份代码已经已经有些许过时了,该份代码是基于cef_binary_87.1.13版本,而我们本文是基于cef_binary_105.3.33。所以使用新的cef、cef wrapper,但使用旧的应用层代码,势必会有问题。但是我们目前先不处理,后文会逐一列举并修改。

CMakeLists.txt

使用CMake来搭建QT+CEF项目,最核心的就是CMakeLists.txt文件内容:

CMAKE_MINIMUM_REQUIRED(VERSION 3.5)

PROJECT(QtCefCMakeDemo LANGUAGES CXX)
SET(CMAKE_BUILD_TYPE DEBUG)
SET(CMAKE_CXX_STANDARD 11)
SET(CMAKE_CXX_STANDARD_REQUIRED ON)
SET(CMAKE_INCLUDE_CURRENT_DIR ON) # 【QT】CMAKE_PREFIX_PATH 实际值为本地安装的QT中的对应编译环境的目录
SET(CMAKE_PREFIX_PATH "D:\\Programs\\Qt\\Qt5.14.2\\5.14.2\\msvc2017_64")
# 配置了上述后,可以通过find_package来查找QT相关的cmake文件 # 【QT】UIC、MOC、RCC启用
# 引入的QT模块则会对.ui文件、.qtc文件以及QT中的元信息机制自动进行处理
SET(CMAKE_AUTOUIC ON)
SET(CMAKE_AUTOMOC ON)
SET(CMAKE_AUTORCC ON) # 【QT】通过FIND_PACKAGE,CMake会查找QT相关模块cmake文件,
# 这些cmake文件自动处理了头文件的查找等,
# 不需要像配置CEF的头文件查找一样来配置QT的头文件引入
FIND_PACKAGE(Qt5 COMPONENTS Widgets REQUIRED)
# 【CEF】CEF相关头文件的引入
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/CefFiles")
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/CefFiles/include") # 添加项目所有的文件:
# 头文件、源文件、ui文件、qrc资源文件
# 特别的,在Windows下VS下,还需要manifest文件,并且该文件在cmake3.4以后就能够自动是被并被引入
ADD_EXECUTABLE(qt-cef
WIN32
src/qtcefwindow.h
src/simple_app.h
src/simple_handler.h
src/main.cpp
src/qtcefwindow.cpp
src/simple_app.cpp
src/simple_handler.cpp
src/qtcefwindow.ui
src/qtcefwindow.qrc
src/app.manifest
) # QT库链接
TARGET_LINK_LIBRARIES(qt-cef
PRIVATE
# 【QT】QT库链接
Qt5::Widgets
# 【CEF】cef相关库链接
"${CMAKE_SOURCE_DIR}/CefFiles/bin/Debug/libcef.lib"
"${CMAKE_SOURCE_DIR}/CefFiles/bin/Debug/libcef_dll_wrapper.lib"
)

CMake的基础配置请各位读者自行了解。关于QT的配置,我都在CMakeLists.txt中以【QT】标识出;关于CEF的配置部分,我都在配置文件中以【CEF】标识出。

异常处理

此时,我们尝试编译整个项目的时候,会发现有一些编译/链接的错误,相关的错误大多数来源于CEF的头文件升级,接下来我将一一列举并处理。

error C3646: “OVERRIDE”: 未知重写说明符

出现点:simple_app.h、simple_handler.h

原因以及解决方案:实际上在87版本中这个OVERRIDE是一个宏,指代的就是关键字:override,不过在105版本中已经不存在了,所以手动修改为c++标准关键词即可。所以解决方案就是将所有出现OVERRIDE的地方改为关键词override

error C2039: "Bind": 不是 "base" 的成员

出现点:simple_handler.cpp

原因以及解决方案:cef团队移除了该API(Remove deprecated base::Bind APIs (see issue #3140)),而是要求使用BindOnce,且该BindOnce所在定义的头文件由原来的#include "include/base/cef_bind.h"变为#include "include/base/cef_callback.h"。所以解决方案就是将头文件include/base/cef_bind.h改为引入include/base/cef_callback.h,且将base::Bind改为base::BindOnce。

warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失

出现点:只要不是UTF-8 with BOM的文件,都可能出现这个警告

原因以及解决方案:CLion 默认使用 UTF-8 编码,MSVC 除非明确指定否则就使用 UTF-8 with BOM 或者当前代码页(详情可以参考这篇博文:解决 CLion + MSVC 下的字符编码问题)),所以在CMakeLists.txt中,在ADD_EXECUTABLE之前加上

# 解决warning C4819
ADD_COMPILE_OPTIONS("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
ADD_COMPILE_OPTIONS("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

error C2664: “void CefWindowInfo::SetAsChild(HWND,const CefRect &)”: 无法将参数 2 从“RECT”转换为“const CefRect &”

出现点:qtcefwindow.cpp

原因以及解决方案:void CefWindowInfo::SetAsChild(HWND parent, const CefRect &windowBounds),第二个参数类型由原来的windef.h中定义的RECT结构体,调整为CefRect类,即:

-    RECT win_rect;
QRect rect = this->geometry();
- win_rect.left = rect.left();
- win_rect.right = rect.right();
- win_rect.top = rect.top();
- win_rect.bottom = rect.bottom();
+// CEF引入CefRect,而不是windef.h中的RECT
+ CefRect win_rect(
+ rect.left(),
+ rect.top(),
+ rect.left() + rect.width() * devicePixelRatio(),
+ rect.top() + rect.height() * devicePixelRatio());

libcef_dll_wrapper.lib(libcef_dll_wrapper.obj) : error LNK2038: 检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项: 值“0”不匹配值“2”(mocs_compilation.cpp.obj 中)

出现点:链接阶段错误

原因以及解决方案:针对该问题,首先通过网上搜寻的博文了解到是:当前工程是Debug版本,而引用的库文件时Release版本。排查libcef_dll_wrapper.lib,确实使用的Debug版本。从报错了解到与mocs_compilation.cpp.obj_ITERATOR_DEBUG_LEVEL不一致。但是,这个mocs_compilation.cpp.obj是通过咱们项目生成的,是QT的MetaObject元对象机制下,MOC参与代码生成、编译输出的,其自动生成的代码在cmake-build-debug目录下的qt-cef_autogen中:

该cpp编译单元编译后的产物在项目根目录/cmake-build-debug/CMakeFiles/qt-cef.dir/qt-cef_autogen下:

使用VS的工具( 适用于开发人员的命令行 shell 和提示 - Visual Studio (Windows) | Microsoft Docs)中的dumpbin.exe工具(DUMPBIN 参考 | Microsoft Docs),可以查看库文件的_ITERATOR_DEBUG_LEVEL值。操作方式为:

  1. 找到VS开发者工具,方式有几种,主要有:1、从 Windows 菜单中启动;2、从文件菜单启动
  2. 启动后进入命令行,执行命令:
dumpbin /directives "库文件路径"

mocs_compilation.cpp.obj的_ITERATOR_DEBUG_LEVEL值

libcef_dll_wrapper.lib中一些obj的_ITERATOR_DEBUG_LEVEL值:

可以看出,两份库代码确实是不一样的。由于libcef_dll_wrapper.lib我们已经完成了编译,这里我们不考虑重新编译该lib库,而是通过配置CMake,让生成的mocs_compilation.cpp.obj等obj的_ITERATOR_DEBUG_LEVEL值为0,来匹配libcef_dll_wrapper.lib。所以,解决方案就是在CMakeLists.txt中,添加配置(c++ - How to add _ITERATOR_DEBUG_LEVEL to CMake? - Stack Overflow):

# 解决warning C4819,需要在ADD_EXECUTABLE前加上
ADD_COMPILE_OPTIONS("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
ADD_COMPILE_OPTIONS("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
+# 控制项目所有编译单元_ITERATOR_DEBUG_LEVEL的值,
+# 这里设置为和libcef_dll_wrapper.lib中的obj一致。
+ADD_COMPILE_DEFINITIONS($<$<CONFIG:Debug>:_ITERATOR_DEBUG_LEVEL=0>) # 【QT】CMAKE_PREFIX_PATH 实际值为本地安装的QT中的对应编译环境的目录
SET(CMAKE_PREFIX_PATH "D:\\Programs\\Qt\\Qt5.14.2\\5.14.2\\msvc2017_64")

不出意外,此时我们已经处理了所有的编译和链接过程中的问题。控制台会显示:

====================[ Build | qt-cef | Debug ]==================================
D:\Programs\ToolBoxApp\apps\CLion\ch-0\222.3739.54\bin\cmake\win\bin\cmake.exe --build D:\Projects\cpp-projects\qt-projects\qt-cef\QtCefCMakeDemo\cmake-build-debug --target qt-cef -j 12
[1/8] Automatic MOC and UIC for target qt-cef
[2/8] Building CXX object CMakeFiles\qt-cef.dir\qt-cef_autogen\UVLADIE3JM\qrc_qtcefwindow.cpp.obj
[3/8] Building CXX object CMakeFiles\qt-cef.dir\src\simple_app.cpp.obj
[4/8] Building CXX object CMakeFiles\qt-cef.dir\src\simple_handler.cpp.obj
[5/8] Building CXX object CMakeFiles\qt-cef.dir\qt-cef_autogen\mocs_compilation.cpp.obj
[6/8] Building CXX object CMakeFiles\qt-cef.dir\src\qtcefwindow.cpp.obj
[7/8] Building CXX object CMakeFiles\qt-cef.dir\src\main.cpp.obj
[8/8] Linking CXX executable qt-cef.exe Build finished

但是在运行的过程中理论山还会出现两个问题:

Process finished with exit code -1073740791 (0xC0000409)

出现这个问题的时候,使用CLion的Debug模式进行,会看到错误调用栈:

经过问题排查,主要原因点:

在qtcefwindow构造函数中调用CefBrowserHost::CreateBrowserAPI,会传入初始要打开的页面地址,然而QString.toStdString得到string有问题(后续排查具体原因)。解决方案就是直接使用std::string变量即可:

     // 以下是将 SimpleHandler 与窗体进行关联的代码
CefWindowInfo cef_wnd_info;
- QString str_url = "https://www.cnblogs.com/w4ngzhen";
+ std::string str_url = "https://www.cnblogs.com/w4ngzhen";
QRect rect = this->geometry();
CefRect win_rect(
rect.left(),
@@ -25,7 +25,7 @@ QtCefWindow::QtCefWindow(QWidget* parent)
simple_handler_ = CefRefPtr<SimpleHandler>(new SimpleHandler());
CefBrowserHost::CreateBrowser(cef_wnd_info,
simple_handler_,
- str_url.toStdString(),
+ str_url,
cef_browser_settings,
nullptr,

"Invalid COM thread model change" 或 运行后异常退出报错Exception 0x80000003 encountered at address 0x7ffbc43e9f3c

解决掉上述问题以后,笔者的环境下还会出现两种类似的问题:

  1. "Invalid COM thread model change"(实际上有些同学机器上,这个问题先于上面的字符串问题)
  2. 运行后异常退出报错Exception 0x80000003 encountered at address 0x7ffbc43e9f3c

这两种情况都是一个解决方案。问题点在于,QT的事件循环在多个进程(浏览器进程、渲染进程)均被初始化。实际上只需要在浏览器进程即可。解决方案就是将main.cpp中的init_for_cef提取到最前:

-    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);  // 解决高DPI下,界面比例问题
- QApplication a(argc, argv);
+ // 将init_qt_cef提取到QApplication初始化之前
+ // 对于CEF多进程架构模型
+ // 因为【渲染进程】启动后,init_qt_cef中执行的CefExecuteProcess会阻塞住,
+ // 如果在此之前启动了QT的事件循环,那么会导致QT出现异常
+ // 所以,我们将init_qt_cef提前到QApplication初始化之前,
+ // 保证无论是浏览器进程还是渲染进程启动,都会进入init_qt_cef,但渲染进程会在里面阻塞,
+ // 不会进入后续的QT应用初始化
const int result = init_qt_cef(argc, argv);
if (result >= 0)
{
return result;
} + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // 解决高DPI下,界面比例问题
+ QApplication a(argc, argv);
+
QtCefWindow w;
w.show();
a.exec();

对于CEF多进程架构模型,因为渲染进程启动后,init_qt_cef中执行的CefExecuteProcess会阻塞住,如果在此之前启动了QT的事件循环,那么会导致QT出现异常。 所以,我们将init_qt_cef提前到QApplication初始化之前,保证无论是浏览器进程还是渲染进程启动后,都会进入init_qt_cef,但渲染进程会在里面阻塞,不会进入后续的QT应用初始化。

效果演示与代码库

与本文相关的代码已经提交至Github,且按照整个文章的编写流程进行提交:

w4ngzhen/QtCefCmakeDemo (github.com)

使用CEF(五)— 在QT中集成CEF(2)基于CLion+CMake搭建环境的更多相关文章

  1. 使用CEF(四)— 在QT中集成CEF(1):基本集成

    QT作为C++下著名的跨平台软件开发框架,实现了一套代码可以在所有的操作系统.平台和屏幕类型上部署.我们前几篇文章讲解了如何构建一款基于CEF的简单的样例,但这些样例的GUI都是使用的原生的或者是控件 ...

  2. Qt中使用CEF(Windows下)

    最近项目中要在Qt中使用CEF(Chromium Embedded Framework),在这里总结下其中的几个要点. 下载合适的CEF版本 关于CEF的简介我们这里就不做介绍了,下载CEF可以有2种 ...

  3. 解决Duilib集成CEF浏览器在Win10无法向客户区拖拽文件

    在Duilib中集成CEF浏览器项目实际开发中,遇到一个问题. 一个需求从资源管理器(桌面)拖拽文件到客户端,窗口捕获WM_DROPFILES消息然后进行消息处理,但客户区是集成的CEF浏览器,浏览器 ...

  4. qt5集成libcurl实现tftp和ftp的方法一:搭建环境(五篇文章)

    最近使用QT5做一个软件,要求实现tftp和ftp文件传输,使用QT5开发好UI界面等功能,突然发现QT5不直接提供tftp和ftp支持,无奈之下只好找第三方库来间接实现,根据网友的介绍,libcur ...

  5. 了解多层交换中的CEF FIB CAM TCAM

    来源:http://blog.51cto.com/redant/314151 多层交换是指交换机使用硬件来交换和路由数据包,通过硬件来支持4-7层的交换.交换机执行硬件交换,第3层引擎(路由处理器)须 ...

  6. 在Application中集成Microsoft Translator服务之开发前准备

    第一步:准备一个微软账号 要使用Microsoft Translator API需要在Microsoft Azure Marketplace(https://datamarket.azure.com/ ...

  7. Qt中使用ActiveX(3篇)

    由于最近需要使用ActiveX,一般来说可以使用微软提供的MFC或者ATL框架来开发,由于我个人对这部分内容不是很熟悉,好在Qt也提供对于ActiveX的支持.本文主要记录个人学习ActiveX的一些 ...

  8. 在VS2103环境中集成Doxygen工具

    自己已将学习了两三次了吧,差不多这次该总结一下: Doxygen是一种开源跨平台的,以类似JavaDoc风格描述的文档系统,完全支持C.C++.Java.Objective-C和IDL语言,部分支持P ...

  9. 【Spring】关于Boot应用中集成Spring Security你必须了解的那些事

    Spring Security Spring Security是Spring社区的一个顶级项目,也是Spring Boot官方推荐使用的Security框架.除了常规的Authentication和A ...

  10. Qt中设置widget背景颜色/图片的注意事项(使用样式表 setStyleSheet())

    在Qt中设置widget背景颜色或者图片方法很多种:重写paintEvent() , 调色板QPalette , 样式表setStyleSheet等等. 但是各种方法都有其注意事项,如果不注意则很容易 ...

随机推荐

  1. 18. 默认堆/创建堆--《Windows核心编程》

    Windows 提供了以下三种机制来对内存进行操控虚拟内存:最适合用来管理大量对象数组或者大型数据结构内存映射文件:最适合用来管理大型数据流(通常是文件),以及在同一机器上运行的多个进程之间的共享数据 ...

  2. 【CSS】如何复原被隐藏的滚动条?记一个看似简单的样式问题所引发的一系列思考

    壹 ❀ 引 故事的起因是这样的,某一个同事在封装了一个TableList组件,用于做表格视图渲染,但出于研发经验考虑上,他可能觉得表格若出滚动条可能会引发某些不可预估的小问题(毕竟一个基础组件会被用于 ...

  3. JS Leetcode 33. 搜索旋转排序数组题解,图解旋转数组中的二分法

    壹 ❀ 引 本来今天(2021.4.7)的每日一题是81. 搜索旋转排序数组 II,但今天工作很忙,下班人基本累个半死,题目别说按照二分法的思路做不出来,连题解看了会都没法沉下心去看,不过得到的信息是 ...

  4. pico命令

    pico命令 pico是一个简单易用.以显示导向为主的文字编辑程序,具有pine电子邮件编写器的风格.在现代Linux系统上,nano即pico的GNU版本是默认安装的,在使用上和pico一模一样. ...

  5. java集成华为云obs上传下载实战

    说明 最近项目上需要开发一个服务去和华为云OBS集成获取一些业务上的文件,此处记录一下简单的java集成obs的入门,希望对大家快速入门有所帮助:) 实现效果 上传对象 下载到本地 操作步骤 1.开通 ...

  6. 使用GDI时如何确定是否有内存泄漏

    在创建GDI对象时,比如创建笔,画刷等对象时,在调用完之后忘记删除对象了,会造成内存泄漏 我们可以通过任务管理器来快速的查看 启动任务管理器(右键单击Windows任务栏以选择任务管理器) 在Wind ...

  7. git回退至指定版本,并更新远程仓库

    1. git log   查到commit记录 2.复制 commit 后面的id 3. git reset --hard  commit 后面的id   // 回退 4. 强制更新远程仓库  git ...

  8. 项目实战:Qt+iMax6生命探测仪(探测障碍物、静止目标、动态目标、生命目标、探测半径、探测前方雷达显示、动态目标轨迹显示、探测热力图、探测过程存储与回放)

    若该文为原创文章,转载请注明原文出处本文章博客地址:https://blog.csdn.net/qq21497936/article/details/110994486长期持续带来更多项目与技术分享, ...

  9. 迁移到 Gradle 7.x 使用 Version Catalogs 管理依赖

    一.根目录下 build.gradle 变更 变更前: buildscript { ext.kotlin_version = '1.5.0' repository { repository { mav ...

  10. 【Azure 存储服务】使用REST API操作Azure Storage Table,删除数据(Delete Entity)

    问题描述 使用Azure Storage Table的REST API,实现根据过滤条件删除满足条件的数据,调用方法为  Delete Entity (Azure Storage) 问题实现 第一步: ...