Qt Remote Object realizes inter process communication

Keywords: C++ Qt ipc

1 Overview

Qt Remote Object, called qtro for short, is a new module officially launched after Qt5.9, which is specially used for inter process communication (IPC). Qtro is essentially a point-to-point communication network. Each process accesses the qtro network through QRemoteObjectNode. The function providing node (which can be understood as a server) needs to use QRemoteObjectHost to register a QObject derived class that provides actual functions into the qtro network, and then other programs using the function connect to the Host through their respective QRemoteObjectNode, and then acquire a Replica of the function object. After the Replica is initialized, the program can use the signals, slots and attributes in the Replica as if the function class were local. Qtro is divided into two types of Replica: dynamic Replica and static Replica.

2 static and dynamic comparison

Static Replica:

advantage

It has a clear definition and is more suitable for use in C + + (because there are header files generated by repc).
Support the definition of complex structures such as POD.
More efficient. Because the structure definitions have been defined in C + +, there is no need for dynamic transmission and construction, which saves the cost.

inferiority

The Source side and the Replica side must strictly use the same version of the rep file, even if only one line of comments is added in the rep file, otherwise it will not be connected.

Dynamic Replica

advantage

Because the Client side does not need rep files, the Server side can modify them at any time, which avoids the disadvantages of static mode.

inferiority

Complex structure definitions such as POD are not supported.
It can only be used after initialization, which increases the additional complexity of programming and the additional overhead of building connections. This is determined by the dynamic feature.

pod types are simply understood as float, int, string, class, struct and other types.

3 static Replica

Server

1 create rep file

rep file is a kind of DSL (Domain Specific Language), which is specially used to define QtRO interface. When compiling, the file will first be processed by the program repc.exe to generate the corresponding header file and source file.
commoninterface.rep

class CommonInterface
{
    SIGNAL(sigMessage(QString msg))   //The server sends a message to the client
    SLOT(void onMessage(QString msg)) //The server receives messages from the client
}

2 compilation

Add QtRO module

QT += remoteobjects

Add rep file

REPC_SOURCE += \
    ./Reps/CommonInterface.rep

Compile, and then find the generated rep header file in the output directory of the program

3 implementation function class

Create a class, inherit from the automatically generated class, and implement all virtual functions in it.
CommonInterface.h

#ifndef COMMONINTERFACE_H
#define COMMONINTERFACE_H

#include "rep_CommonInterface_source.h"

class CommonInterface:public CommonInterfaceSource
{
    Q_OBJECT
public:
    explicit CommonInterface(QObject * parent = nullptr);

    //receive messages
    void onMessage(QString msg) override;
    //send message
    void sendMsg(QString msg);
signals:
    void sigReceiveMsg(QString);
};

#endif // COMMONINTERFACE_H

CommonInterface.cpp

#include "CommonInterface.h"

CommonInterface::CommonInterface(QObject *parent):
    CommonInterfaceSource(parent)
{

}

void CommonInterface::onMessage(QString msg)
{
    emit sigReceiveMsg(msg);
}

void CommonInterface::sendMsg(QString msg)
{
    sigMessage(msg);
}

4. Implement the main logic of the Server

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "CommonInterface.h"
#include <QRemoteObjectHost>
#include <QDateTime>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    void init();
private slots:
    void onReceiveMsg(QString msg);
    void sendBtnClicked();
    void lineRetrunPressed();
private:
    Ui::Widget *ui;
    CommonInterface* m_pInterface=nullptr;
    QRemoteObjectHost* m_pHost=nullptr;
};
#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "ui_Widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    init();
}

Widget::~Widget()
{
    delete ui;
}

void Widget::init()
{
    setWindowTitle("This is Server");
    ui->inputLine->setFocus();

    m_pHost = new QRemoteObjectHost(this);
    m_pHost->setHostUrl(QUrl("local:interfaces"));
    m_pInterface = new CommonInterface(this);
    m_pHost->enableRemoting(m_pInterface);
    connect(m_pInterface,&CommonInterface::sigReceiveMsg,this,&Widget::onReceiveMsg);

    connect(ui->sendBtn,&QPushButton::clicked,this,&Widget::sendBtnClicked);
    connect(ui->inputLine,&QLineEdit::returnPressed,this,&Widget::lineRetrunPressed);
}

void Widget::onReceiveMsg(QString msg)
{
    QDateTime curDateTime=QDateTime::currentDateTime();
    QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");

    ui->textEdit->append(temp+QString(" Client: ") + msg);
}

void Widget::sendBtnClicked()
{
    QString msg = ui->inputLine->text();
    if(!msg.isEmpty()){
        m_pInterface->sendMsg(msg);
    }
    QDateTime curDateTime=QDateTime::currentDateTime();
    QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");

    ui->textEdit->append(temp+QString(" Server: ") + msg);
    ui->inputLine->clear();
}

void Widget::lineRetrunPressed()
{
    sendBtnClicked();
}

be careful

m_pHost->setHostUrl(QUrl("local:interfaces"));

The string format is "local:xxxx", where xxxx must be a unique string. It conflicts with other programs, otherwise it will not be connected.
The server is OK here.

client

1 create rep folder

Clients share a rep file, and then configure the por file

QT += remoteobjects

REPC_REPLICA += \
    ./Rep/CommonInterface.rep

Note that the addition method here is different from that on the server side. The server side is REPC_SOURCE.

2 compilation

Find the corresponding generated rep header file

Different from the server side, the client side does not need to re implement the function class, but only needs to connect to the server side in the main window.

3. Implement the main logic of the Client

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QRemoteObjectNode>
#include "rep_CommonInterface_replica.h"
#include <QDateTime>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    void init();
private slots:
    void onReceiveMsg(QString msg);
    void sendBtnClicked();
    void lineRetrunPressed();

private:
    Ui::Widget *ui;
    QRemoteObjectNode * m_pRemoteNode = nullptr;
    CommonInterfaceReplica * m_pInterface = nullptr;
};
#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "ui_Widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    init();
}

Widget::~Widget()
{
    delete ui;
}
void Widget::init()
{
    setWindowTitle("This is Client");
    ui->inputLine->setFocus();

    m_pRemoteNode = new QRemoteObjectNode(this);
    m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
    m_pInterface = m_pRemoteNode->acquire<CommonInterfaceReplica>();

    connect(m_pInterface,&CommonInterfaceReplica::sigMessage,
            this,&Widget::onReceiveMsg);

    connect(ui->sendBtn,&QPushButton::clicked,this,&Widget::sendBtnClicked);
    connect(ui->inputLine,&QLineEdit::returnPressed,this,&Widget::lineRetrunPressed);
}

void Widget::onReceiveMsg(QString msg)
{
    QDateTime curDateTime=QDateTime::currentDateTime();
    QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");

    ui->textEdit->append(temp+QString(" Server: ") + msg);
}

void Widget::sendBtnClicked()
{
    QString msg = ui->inputLine->text();
    if(!msg.isEmpty()){
        m_pInterface->onMessage(msg); //Call the slot to send a message to the server
    }
    QDateTime curDateTime=QDateTime::currentDateTime();
    QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");

    ui->textEdit->append(temp+QString(" Client: ") + msg);
    ui->inputLine->clear();
}

void Widget::lineRetrunPressed()
{
    sendBtnClicked();
}

be careful

m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
m_pInterface = m_pRemoteNode->acquire<CommonInterfaceReplica>();

The connection address here must be consistent with that of the server, and then through acquire(); Connect to the server side.
The client is OK here.

Running screenshot

3 dynamic Replica

Change the above example to dynamic Replica.

Server side changes

m_pHost->enableRemoting(m_pInterface,QStringLiteral("Interfaces1"));

The second parameter name must be passed in.

Client side change

The rep file is not required. It is directly removed from the pro file

#REPC_REPLICA += \
#    ./Rep/CommonInterface.rep

Then, when obtaining Replica, you need to use a dynamic version

QRemoteObjectDynamicReplica  * m_pInterface = nullptr;
m_pInterface = m_pRemoteNode->acquireDynamic("Interfaces1");//Dynamic acquisition

In addition, there is a key point that needs to be changed, because without the rep file, the program doesn't know what the connected Server looks like when it starts. Therefore, the internal principle of dynamic Replica is to first obtain the meta information of the Source side after establishing a connection, and then dynamically build attributes, signals and slots on the Replica side. After these structures are completed, Replica will send an initialized signal, and then the Replica can be really used.
Only when Replica sends the initialized signal can it have the Source side meta information (attributes, signals and slots) and be used.

 	setWindowTitle("This is Client");
    ui->inputLine->setFocus();

    m_pRemoteNode = new QRemoteObjectNode(this);
    m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
    m_pInterface=m_pRemoteNode->acquireDynamic("Interfaces1");//Dynamic acquisition

    //Only when Replica is initialized can it be used. Otherwise, connect is invalid
    connect(m_pInterface, &QRemoteObjectDynamicReplica::initialized, this, &Widget::onInitConnect);

It'll be all changed by now

Client complete code

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QRemoteObjectNode>
#include "rep_CommonInterface_replica.h"
#include <QDateTime>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    void init();
private slots:
    void onReceiveMsg(QString msg);
    void sendBtnClicked();
    void lineRetrunPressed();
    void onInitConnect();
signals:
    void sigSendMsg(QString);
private:
    Ui::Widget *ui;
    QRemoteObjectNode * m_pRemoteNode = nullptr;
    QRemoteObjectDynamicReplica  * m_pInterface = nullptr;
};
#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "ui_Widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    init();
}

Widget::~Widget()
{
    delete ui;
}
void Widget::init()
{
    setWindowTitle("This is Client");
    ui->inputLine->setFocus();

    m_pRemoteNode = new QRemoteObjectNode(this);
    m_pRemoteNode->connectToNode(QUrl("local:interfaces"));
    m_pInterface=m_pRemoteNode->acquireDynamic("Interfaces1");//Dynamic acquisition

    //Only when Replica is initialized can it be used. Otherwise, connect is invalid
    connect(m_pInterface, &QRemoteObjectDynamicReplica::initialized, this, &Widget::onInitConnect);
}

void Widget::onReceiveMsg(QString msg)
{
    QDateTime curDateTime=QDateTime::currentDateTime();
    QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");

    ui->textEdit->append(temp+QString(" Server: ") + msg);
}

void Widget::sendBtnClicked()
{
    QString msg = ui->inputLine->text();
    if(!msg.isEmpty()){
        emit sigSendMsg(msg);
    }
    QDateTime curDateTime=QDateTime::currentDateTime();
    QString temp=curDateTime.toString("yyyy-MM-dd hh:mm:ss");

    ui->textEdit->append(temp+QString(" Client: ") + msg);
    ui->inputLine->clear();
}

void Widget::lineRetrunPressed()
{
    sendBtnClicked();
}

void Widget::onInitConnect()
{
    connect(m_pInterface,SIGNAL(sigMessage(QString)),this,SLOT(onReceiveMsg(QString)));
    connect(this,SIGNAL(sigSendMsg(QString)),m_pInterface,SLOT(onMessage(QString)));

    connect(ui->sendBtn,&QPushButton::clicked,this,&Widget::sendBtnClicked);
    connect(ui->inputLine,&QLineEdit::returnPressed,this,&Widget::lineRetrunPressed);
}

Running screenshot

Posted by Marqis on Tue, 19 Oct 2021 12:07:59 -0700