QT + WebEngine
WebEngine 架构:
能看到 WebEngine 有一个核心模块是基于 Chromium 构造的,通过使用 Chromium 的Blink渲染引擎和V8 JavaScript引擎来处理和渲染Web内容,并将这些底层技术封装为一系列高级的C++类和接口,以便开发者可以将Web内容嵌入到Qt应用程序中。
- WebEngine Widgets 模块:主要是针对于桌面应用,提供了用于将Web内容嵌入到Qt应用程序中的窗口和小部件, 可以用于各种应用程序来加载web内容。
主要架构如上图所示,主要提供了一些组件:
- QWebEnginePage:一个代表Web页面的类,它包含了页面的内容、状态和功能。它可以执行JavaScript代码、处理表单、捕获页面截图等。
- QWebEngineView:一个用于展示Web页面的小部件,它是QWebEnginePage的可视化容器。它可以加载URL、导航历史、缩放页面等。
- QWebEngineProfile:一个表示浏览器配置文件的类,它包含了缓存、Cookie和安全策略等设置。它可以用于实现多用户浏览器或隐私浏览功能。
- QWebEngineSettings:一个表示浏览器设置的类,它包含了字体、颜色、JavaScript和插件等选项。它可以用于调整页面的渲染效果和功能。
- QWebEngineScript:执行 JavaScript 脚本的类,用于向网页中注入并执行自定义的 JavaScript 脚本来添加新功能或修改现有的功能。
- QWebEngineHistory:管理浏览历史的类,它提供了对浏览历史记录的访问和操作,其中每个历史记录项都由 QWebEngineHistoryItem 类表示。
- WebEngineAction:表示对网页的操作,由WebEngineView::action()方法返回,包含相关的一些信息;
一个QWebEngineView 实例有一个 QWebEnginePage. QWebEnginePage有一个QWebEngineHistory和一些用于操作页面的QAction对象。每一个QWebEnginePage都属于一个QWebEngineProfile, QWebEngineProfile有一个页面设置的QWebEngineSettings, 一个在页面上运行脚本的QWebEngineScriptCollection, 一个访问HTTP cookies的QWebEngineCookieStore。
-
WebEngine 模块:主要是针对用于QML。
Qt WebEngine 除了没有单独访问的web引擎页面,包含的其他组件与Qt WebEngine Widgets相同,功能也是一致的。 -
WebEngine Process:这是可执行文件,对应的是一个独立的用于渲染网页和执行JavaScript进程,用于增强安全性与内存隔离。
网上有文章说“每个QWebEngineView或WebEngineView实例都会启动一个QtWebEngineProcess.exe进程”,在View对象过多的情况下会导致内存占用;
但是在官方文档里没有找到相关描述,这个问题暂时未能确定; -
WebEngine 对 AMD 显卡支持性不好,在某些A系显卡下会显示不出来甚至闪退; 后续优化计划目前还没在网上搜到;另外嵌入的网页无法播放视频,要播放视频的话需要下载QT源码自己设置编译参数集成视频播放库;
-
对显卡有依赖,基于openGL,看网上说如果在显卡和显卡驱动不一致或是旧版版本下还有openGL,程序有可能出异常
QT 与 网页通信
- 通过 WebEnginePage 与网页通信
通过 WebEngine 嵌入网页的方式与网页端的JS交互很容易,QWebEnginePage 自带了一个函数为 runJavaScript ,这个函数可以直接通知 JS 执行指定的函数,
void QWebEnginePage::runJavaScript(const QString &scriptSource, const std::function<void (const QVariant &)> &resultCallback)
例如这个接口,第一个参数就是要调用的接口以及参数拼成的字符串,第二个参数是对应的回调。
这种方式用起来很方便,但是是单向的
- QWebChannel 双向通信
QT提供了一种通道机制,可以让 JS 与 QT 通过对象建立连接, 然后进行通信;
QWebChannel 提供了在 QT 和 HTML/JS 之间点对点的通信能力,通过向 HTML/JS 端的 QWebChannel 发布 QObject 的 派生对象,进而实现在 HTML/JS 端可以同步读取来自 Qt 端的 公共插槽 和 QObject 的 属性值 和 方法。在整个通信过程中,无需任何手动序列化传入的参数。所有 Qt 端的数据更新,都会自动且异步的触发 signal 并更新到 client 端。这个机制一般被称为对象模型,QObject 是这个模型的核心,而模型实现的基础就是靠的 QT 的 signal 和 slot 也就是信号槽机制。
这个机制主要分以下几步:
- QT端 创建 HTML/JS 网页
- JS端 创建 channel 对象,通过 channel 取到通信对象 QObject;
- QT端 创建 channel 对象与通信用的 QObject 对象;
- QT端 channel 与 网页对应的 QWebEngineView 绑定;
- QT端 将 channel 与 QObject 对象绑定;
- QT端/JS端 修改QObject属性,另一端对应的监听函数响应;
WebEngine 网页示例
源码见 QTWebtest_WebEngine
在示例代码里,我写了一个中间类名为 JS2QTBridge, 里面定义了两个字符串 m_jscontent 与 m_content;
通过 channel 将这个对象对 JS 与 QT 双向绑定,网页/QT界面上显示内容发生改变时给 QT/JS 发送对应信号,实现效果如:
其中在QT上点击按钮通知网页画图是用 runJavaScript 方法实现;
在网页点击饼图在QT弹出提示是通过调用 bridge 对象的函数实现;
两边输入框修改另一侧的显示区域一同变化是通过对 bridge 对象的成员变量与界面控件信号槽绑定实现;
QT + cef
CEF3使用多个进程运行。处理窗口创建、绘制和网络访问的主要进程称为浏览器进程,通常与宿主应用程序的进程相同,大多数应用程序的逻辑将在浏览器进程中运行。使用Blink引擎渲染HTML和JavaScript在单独的渲染进程中,相关的应用程序逻辑(如JavaScript绑定和DOM访问)将在渲染进程中运行。默认进程模型将为每个唯一源地址(scheme+domain)运行一个新的渲染进程。其他进程将根据需要生成,例如处理Flash等插件的插件进程和处理加速合成的GPU进程。另外浏览器进程中会进行窗口绘制,不是绘制HTML内容,而是承载网页内容的那个窗体,渲染进程则不会用来创建窗体。
<!-- 编译CEF参考 cef.md – >
cef解决方案中有多个工程:
- ALL_BUILD与ZERO_CHECK:是cmake自动生成的辅助工程。
- cef_gtest与ceftests:都是测试模块,这里不关心。
- cefclient:一个包含CEF各种API演示的浏览器程序Demo。
- cefsimple:一个简单的CEF浏览器程序Demo。
- libcef_dll_wrapper:对libcef库的C++代码封装。上述cefclient与cefsimple都会用它。
可以移植 cefsimple 来实现一个简单的浏览器程序:
- simple_app.h、simple_app.cc、simple_handler.h、simple_handler.cc 这四个文件拷到QT工程中;
- simple_handler.h中的 PlatformTitleChange 定义注释掉;simple_handler.cc 的 OnTitleChange 对这个函数的调用也注释掉;
- CEF.lib等相关库 以及 libcef_dll_wrapper.lib 拷贝到工程中,链接到QT工程里;
- CEF的include文件夹拷贝到QT工程位置并引入;
- CEF的 Resources 中的五项拷贝到QT编译出来的EXE的环境里;这些是网页渲染引擎需要的资源,不打包过去程序运行时会报错:icu_util.cc(224)] Invalid file descriptor to ICU data received.
QT 与 CEF 嵌入的网页通信有两种方式;
1. 使用 QWebChannel
这种方法与上面使用 webengine 时类似, 在QT 与 JS中声明channel, 通过将这个channel对象来作为中间通信的桥梁;
这种方式有一个优点是,可以通过 websocket 来让 Channel 监听指定的IP和端口来实现远程通信,而不仅仅局限于QT与它启动的web子进程;
2. 通过 CefRenderProcessHandler 的方法回调传递
-
virtual void OnWebKitInitialized() {}
当 webkit 初始化完毕之后。在这个函数中,可以通过 CefRegisterExtension() 函数来注册JavaScript与C++代码之间的“映射”关系,官方管这种方式叫做扩展(Extension) -
virtual void OnContextCreated(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) {}
JavaScript 上下文被创建以后,在这个函数中,可以为JavaScript中的 window 对象绑定属性和方法,官方将这种方式叫做 窗口绑定(window binding)
3. CEF网页通信示例
源码见 QTWebtest_CEF
在示例代码里,我写了一个网页,将这个网页用CEF展示在了QT窗口中,然后另外打开了一个QT对话框;
通过 channel 来在网页与对话框之间发送信息,展示效果如下:
CEF使用的一些问题:
不支持视频,需要自编译源码;原生的CEF是纯C代码,libcef_dll_wrapper才是在CEF的基础上封装的C++ API;
很大,我使用的版本的 libcef.dll 164MB, libcef_dll_wrapper.lib 33MB ,还有一些相关的加一起超200MB;
MD/MT属性支持的不好,CEF工程默认使用的是MT,其中用到的一些依赖库如cef_sandbox.lib都是MT编译的,使用MD编译会报错,要是改成MD需要自己下载相关源码编译;
多进程架构,WebEngine应该是一个view一个进程,如果只有一个view然后路径切换不会增加新的进程,CEF应该是每打开一次网页就会创建一个渲染进程;