Discuz! Board

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1143|回复: 0
打印 上一主题 下一主题

QT集成CEF08-多进程通信

[复制链接]

1228

主题

1998

帖子

7596

积分

认证用户组

Rank: 5Rank: 5

积分
7596
跳转到指定楼层
楼主
发表于 2023-2-3 16:40:42 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
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

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|firemail ( 粤ICP备15085507号-1 )

GMT+8, 2024-5-16 04:24 , Processed in 0.055936 second(s), 19 queries .

Powered by Discuz! X3

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表