firemail
标题:
QT集成CEF08-多进程通信
[打印本页]
作者:
Qter
时间:
2023-2-3 16:40
标题:
QT集成CEF08-多进程通信
https://blog.csdn.net/paopao_wu/article/details/121795134
通过上一个章节理解了CEF3 的多进程,即一个主进程,一般主进程是Browser进程,其他的分别是渲染进程(Renderer),GPU加速进程(GPU),插件进程(NPAPI或者PPAPI)。
Browser进程:负责窗口管理,界面绘制和网络交互。
Renderer进程:负责JavaScript的执行与DOM节点维护
NPAPI插件进程:按需创建,每种类型的插件只会有一个进程,每个插件进程可以被多个Render进程共享;
GPU进程:按需创建,最多只有一个,当且仅当GPU硬件加速打开的时候才会被创建,主要用于对3D加速调用的实现。
多进程的好处很多,在浏览器中最主要的好处是当一个页面或者插件崩溃或假死,不会给其他页面带来影响。各个进程之间还可以通过发送消息来进行通信。这里借用网络上的一张图说明:
方框代表进程,连接线代表IPC进程间通信(Inter-Process Communication)
CEF3的进程之间可以通过IPC进行通信。Browser和Renderer进程可以通过发送异步消息进行双向通信。这里我们主要讨论的是 Browser进程和Renderer进程之间的通信。
在网络上找到了Chromium 的browser进程和Renderer进程的模型图:
Browser进程
Renderer 进程
在CEF3中,每个进程都会运行多个线程
Browser进程中包含如下主要的线程:
TID_UI 线程是Browser进程的主线程。如果应用程序在调用CefInitialize()时,传递CefSettings.multi_threaded_message_loop=false,这个线程也是应用程序的主线程。 因为要与QT继承,所以我们传递的是 CefSettings.multi_threaded_message_loop=true
TID_IO 线程主要负责处理IPC消息以及网络通信。
TID_FILE 线程负责与文件系统交互。
CEF中browser进程和Renderer 进程是通过管道通信的。CEF做了一层封装所有行为都以接口的形式对外提供,通过继承接口,重写接口函数,处理不同的消息。
CefBrowser和CefFrame对象在Borwser和Renderer 进程里都会存在,都有一个唯一ID值绑定,便于在两个进程间定位匹配。也就是说在我们的示例工程:QyCefVS和QyRender 这两个项目中都能使用CefBrowser 和 CefFrame ,但它们是在不同的进程中运行的,通过相互发送消息进行通信。
1. 消息发送与接收
现在我们需要搞清楚以下几个问题:
消息的发送者
发送什么样的消息
怎么发送消息
消息的接受者
怎么接收消息
这里我设计了这样的一个场景: Renderer进程的 JavaScript 引擎V8 Context上下文被创建以后,向Browser进程发送一个消息,发送这个消息的目的是让 Browser进程创建一个窗口并显示出来,所以消息包含三个参数: 窗口标题,窗口宽度,窗口高度。Browser进程收到消息后解析出消息内容,然后发射一个QT 信号给 主窗体,主窗体中的槽函数连接这个信号,在槽函数中创建窗体并显示出来。
这个场景并没有什么实际意义,但是通过它我们可以了解进程间是如何通信的。
消息的发送者:Render进程,在 QyRender这个项目中。 QyAppRenderer类实现了CefApp和CefRenderProcessHandler接口,CefRenderProcessHandler 中定义了 OnContextCreated和 OnProcessMessageReceived, 我们需要重写这两个方法。消息就在 OnContextCreated中发出,OnProcessMessageReceived 是用来接收消息的,这里做简单日志输出。这个例子中暂时还用不到。
发送什么样的消息:CefProcessMessage 表示一个消息,通过它的静态方法CefProcessMessage::Create 可以创建一个消息
怎么发送消息: 参考文档 中说:
在进程生命周期内,任何时候你都可以通过CefProcessMessage类传递进程间消息。这些信息和特定的CefBrowser实例绑定在一起,用户可以通过CefBrowser::SendProcessMessage()方法发送。进程间消息可以包含任意的状态信息,用户可以通过CefProcessMessage::GetArgumentList()获取。
但是在我使用的版本:cef_binary_91.1.22+gc67b5dd+chromium-91.0.4472.124_windows32 中,CefBrowser 类并没有 SendProcessMessage 方法,这个方法被定义到了CefFrame 类上。所以我这里是使用 CefFrame 来发送消息的。
消息的接收者: Browser进程,在QyCefVS项目中,SimpleHandler 实现了CefClient, 在 CefClient 中定义了 OnProcessMessageReceived 方法,可以用它来接收消息
怎么接收消息: 在SimpleHandler 类中重写 OnProcessMessageReceived 方法。
2. 简单日志和中文处理
因为这是个多进程应用,调试比较麻烦,所以这里通过在程序中输出日志到文件的方式来Debug。这里使用了QT的 QDebug来输出到文件中。
QT 中 QDebug的输出默认是输出到控制台上的,可以使用 qInstallMessageHandler 函数来定制 QDebug的输出。
QT中输出中文也比较麻烦,经常会出现乱码,所以也要配置一下,让我们能够在日志文件中正常的显示中文信息。
2.1 日志输出
在QyCefVS项目和 QyRender项目的源码目录中创建一个头文件:log.h
// log.h
#pragma once
#include <QtCore/qlogging.h>
#include <QFile>
#include <QDateTime>
#include <QMutex>
#include <QTextStream>
#include <QtCore/QCoreApplication>
void outputMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
static QMutex mutex;
mutex.lock();
QString text;
switch (type) {
case QtDebugMsg:
text = QString("debug:");
break;
case QtWarningMsg:
text = QString("warning:");
break;
}
QString context_info = QString("File
%1) Line
%2)").arg(QString(context.file)).arg(context.line);
QString current_date_time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
QString current_date = QString("(%1)").arg(current_date_time);
QString message = QString("%1 %2 %3 %4").arg(text).arg(context_info).arg(msg).arg(current_date);
// 在QyRender项目中,输出为 QyRenderLog.txt
//QFile file(QCoreApplication::applicationDirPath().append("\\QyRenderLog.txt"));
// 在QQyCefVS项目中,输出为 QyCefVSLog.txt
QFile file(QCoreApplication::applicationDirPath().append("\\QyCefVSLog.txt"));
file.open(QIODevice::WriteOnly | QIODevice::Append);
QTextStream text_stream(&file);
text_stream << message << "\r\n";
file.flush();
file.close();
mutex.unlock();
}
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
注意在不同的项目中,这里输出的日志文件名字不一样。
在两个项目的main函数中引入头文件,调用 qInstallMessageHandler:
QyRender项目 main函数:
//... 省略
#include "log.h"
//... 省略
int main(int argc, char* argv[])
{
QCoreApplication a(argc, argv);
qInstallMessageHandler(outputMessage);
//... 省略
}
1
2
3
4
5
6
7
8
9
QyCefVS项目 main函数:
//... 省略
#include "log.h"
//... 省略
int main(int argc, char* argv[])
{
//... 省略
QApplication a(argc, argv);
qInstallMessageHandler(outputMessage); // 日志
//... 省略
}
1
2
3
4
5
6
7
8
9
10
2.2 中文乱码处理
为 vs 2019 安装一个 FileEncoding 插件,使用它能方便更改源代码所使用的字符集。
然后修改源代码文件的编码格式为 “UTF-8(BOM)”:
在需要输出中文的代码文件的第一行添加一个预编译指令:
#pragma execution_character_set("UTF-8")
1
这样就能使用 qDebug直接输出中文了。
3. 实现步骤
这里只给出关键代码,其它代码省略掉了。
3.1 Renderer进程发送消息
QyRender项目的 QyAppRenderer类实现,重点是实现 OnContextCreated 方法中的发送消息
//QyAppRenderer.h
#include "include/cef_app.h"
class QyAppRenderer :public CefApp, public CefRenderProcessHandler {
public:
QyAppRenderer();
//重写CefApp 中的GetRenderProcessHandler方法
CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE {
return this;
}
//实现 CefRenderProcessHandler 接口中的方法
void OnBrowserCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDictionaryValue> extra_info) OVERRIDE;
// 在这里发送消息
void QyAppRenderer::OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) OVERRIDE;
// 接收消息,暂时没哟用到
bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message);
private:
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(QyAppRenderer);
};
//QyAppRenderer.cpp
#pragma execution_character_set("UTF-8")
#include "QyAppRenderer.h"
#include <QDebug>
void QyAppRenderer::OnBrowserCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDictionaryValue> extra_info) {
qDebug() << "=====OnBrowserCreated=======";
}
// 在这里发送消息
void QyAppRenderer::OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) {
// 发送消息给 Browser进程
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("showSubWindow");
CefRefPtr<CefListValue> args = msg->GetArgumentList();
CefString title("我是一个子窗口");
int width = 400, height = 300; //窗体的宽和高
args->SetSize(2); //两个参数
args->SetString(0, title);
args->SetInt(1, width);
args->SetInt(2, height);
qDebug() << "=====发送消息给Browser进程=======";
// 发送消息给Browser进程
frame->SendProcessMessage(PID_BROWSER, msg);
}
// 收到其它进程发送过来的消息,这里仅仅打印了收到的消息
bool QyAppRenderer::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) {
qDebug() << "收到进程:" << source_process << "的消息, 消息名称:"
<< QString::fromStdString(message.get()->GetName());
return true;
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
3.2 Browser进程接收消息
QyCefVS项目中 ,SimpleHandler 中接收Renderer进程发送过来的消息然后发射QT 信号,所以它需要继承 QObject,并添加 Q_OBJECT 宏,定义一个信号,名字为:onReceiveRendererProccessMessasge 并实现 CefClient中的OnProcessMessageReceived 方法:
//simple_handler.h
#include "include/cef_client.h"
#include <list>
#include "include/wrapper/cef_helpers.h"
#include "QObject"
class SimpleHandler :public QObject, public CefClient
, public CefLifeSpanHandler
, public CefKeyboardHandler {
Q_OBJECT
public:
// ... 省略
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message);
signals:
void onReceiveRendererProccessMessasge(QString title, int width, int height);
// ... 省略
};
//simple_handler.cc
// ... 省略
bool SimpleHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) {
qDebug() << "收到:" << source_process << "进程消息: " << QString::fromStdString(message.get()->GetName());
CefString title = message.get()->GetArgumentList().get()->GetString(0); //第一个参数 标题
int width = message.get()->GetArgumentList().get()->GetInt(1); // 第二个参数宽度
int height = message.get()->GetArgumentList().get()->GetInt(2);// 第三个参数高度
//发送信号到 主窗口,让主窗口创建一个子窗口
emit onReceiveRendererProccessMessasge(QString::fromStdString(title), width, height);
return true;
}
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
3.3 主进程(Browser进程) 连接信号
MainWindow 中先定义一个槽函数void onReceiveRendererProccessMessasge(QString title, int width, int height);
在SimpleHandler 对象创建完毕之后,关联信号和槽。而SimpleHandler 对象是在 原来的 :void MainWindow::createBrowserWindow() 中创建的,所以在这个函数中做信号连接。
// main_window.h
#include <QtWidgets/QMainWindow>
#include "ui_mainwindow.h"
#include "cef/simple_app.h"
class MainWindow : public QMainWindow
{
// 省略...
private slots:
// 省略...
void onReceiveRendererProccessMessasge(QString title, int width, int height);
// 省略...
};
// main_window.cpp
void MainWindow::createBrowserWindow() {
// 省略...
// 连接信号和槽
connect(handler.get(), &SimpleHandler:
nReceiveRendererProccessMessasge, this, &MainWindow:
nReceiveRendererProccessMessasge);
}
// 槽函数,用来创建一个子窗口
void MainWindow:
nReceiveRendererProccessMessasge(QString title, int width, int height)
{
QDialog* subWin = new QDialog(this);
subWin->setWindowTitle(title);
subWin->setFixedWidth(width);
subWin->setFixedHeight(height);
subWin->show();
}
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
3.4 编译运行
先编译QyRender项目,在编译QyCefVS项目,或者直接编译整个解决方案后运行:
项目运行后会发现主窗口出现以后,稍微等待一小会,子窗口出现了,这说明已经成功了。再看看可执行程序目录下多了两个日志文件:
代码请访问 GitHub qt_cef_08分支
————————————————
版权声明:本文为CSDN博主「paopao_wu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/paopao_wu/article/details/121795134
欢迎光临 firemail (http://firemail.wang:8088/)
Powered by Discuz! X3