| 
 | 
	
 
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 
 
 |   
 
 
 
 |