Qt and Python create Qt Remote Objec client / server respectively to communicate with each other

Posted by spider_man on Tue, 18 Jan 2022 07:16:23 +0100

This content is original, please indicate the source for reprint.

environment
Ubuntu18.04 or Window 10
python 3.8.5
pyqt 5.9.2

Qt Remote Object, referred to as QtRO for short, can be used to communicate between different processes using signal slots.
No more, just do it.
I will use Qt and Python to create client and server respectively, and connect in turn.

The article is a little long. If you don't have the patience to see the guy paper, you can download the code directly. I uploaded it to Baidu online disk.
Link: https://pan.baidu.com/s/1uEetEpMG-aEfzfccBCTEwQ
Extraction code: m0ss

Step 1: create a path

Because I intend to write QT and python server and client respectively, there will be several files, so I will build several more folders to separate them.
The Reps folder will be placed Rep files are set up in a separate folder because Qt servers and clients must reference the same rep file, which is more convenient to mention outside the project.
The Python folder contains two py files for the client and server respectively.
The other two folders are Qt's server and client projects.

Step 2: create Qt server

First, let's write a commoninterface The rep file is placed in the Reps folder, where a signal and a slot function are written.
For other types of functions, please refer to Qt's official documents: https://doc.qt.io/qt-5/qtremoteobjects-repc.html

#include <QtCore>

class CommonInterface
{
    SIGNAL(sigMessage(QString msg));
    SLOT(void onMessage(QString msg));
};

Then we create a Qt project of qml, named RemoteObjectServer.
We open remoteobjectserver Pro file, add the Remote module first, and then introduce commoninterface Rep file.

QT += remoteobjects
REPC_SOURCE += \
    ../Reps/CommonInterface.rep

OK, then we build and run the project. After running successfully, we will get a rep in the build directory_ CommonInterface_ source. H header file, which is automatically generated by Qt.
We copy this header file to the server project, which is convenient for reference.

Now let's create a signal slot class for the server to receive and send signals.
Create a c++ class and enter the class name CommonInterface, so we get the file CommonInterface H and CommonInterface CPP, start writing code.
commoninterface. In H:

#ifndef COMMONINTERFACE_H
#define COMMONINTERFACE_H

#include <QObject>
#include "rep_CommonInterface_source.h"

class CommonInterface : public CommonInterfaceSource
{
    Q_OBJECT
public:
    CommonInterface(QObject * parent = nullptr);
    virtual void onMessage(QString msg);
    void sendMsg(const QString &msg);
signals:
    void sigReceiveMsg(const QString &msg);
};

#endif // COMMONINTERFACE_H

commoninterface. In CPP:

#include "commoninterface.h"


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

}

/**
 * @brief CommonInterface::onMessage
 * @param msg
 * Receive messages from clients
 */
void CommonInterface::onMessage(QString msg)
{
    emit sigReceiveMsg(msg);
}

/**
 * @brief CommonInterface::sendMsg
 * @param msg
 * Send message to client
 */
void CommonInterface::sendMsg(const QString &msg)
{
    emit sigMessage(msg);
}

Isn't it familiar? sigMessage and onMessage are the signals and slots we wrote in the rep file. If you are interested, go to the rep automatically generated by Qt_ CommonInterface_ source. H files, you can also see their shadow.

In fact, at this stage, we can receive and send messages in Qt, but I'd better finish the whole project.
Let's create a new class to communicate with qml. Widgets are also OK, but I prefer qml.
Create a c++ class and enter the class name MyQmlFunction, so we get the file MyQmlFunction H and MyQmlFunction CPP, start writing code.
MyQmlFunction. In H:

#ifndef MYQMLFUNCTION_H
#define MYQMLFUNCTION_H

#include <QObject>
#include <QDebug>
#include <QRemoteObjectHost>
#include "commoninterface.h"

class MyQmlFunction : public QObject
{
    Q_OBJECT
public:
    explicit MyQmlFunction(QObject *parent = nullptr);
    Q_INVOKABLE void sendToClient(QString information);

private:
    CommonInterface *remoteObj = nullptr;
    QRemoteObjectHost *remoteHost = nullptr;

signals:
    Q_INVOKABLE void recvMessage(QString message);

public slots:
};

#endif // MYQMLFUNCTION_H

MyQmlFunction. In CPP:

#include "MyQmlFunction.h"

MyQmlFunction::MyQmlFunction(QObject *parent) : QObject(parent)
{
    remoteHost = new QRemoteObjectHost(this);
    remoteHost->setHostUrl(QUrl("local:interfaces"));
    remoteObj = new CommonInterface(this);
    remoteHost->enableRemoting(remoteObj, "CommonInterfaceSource");
    connect(remoteObj, SIGNAL(sigReceiveMsg(QString)), this, SIGNAL(recvMessage(QString)));
}

void MyQmlFunction::sendToClient(QString information)
{
    remoteObj->sendMsg(information);
}

To interface with QML, add Q when defining a function_ I won't say more about these. I'll say a few lines of code to create the server.

In a few simple sentences, it should be noted that "local:interfaces" must be consistent with the client, otherwise the server will not be found.
In addition, when enableremoving, you can not specify the name "CommonInterfaceSource", but if you do not specify it, you can only connect with Qt clients, not python. This will also be mentioned later. Please continue to look.

Set the connection between MyQmlFunction and qml.
main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "MyQmlFunction.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    MyQmlFunction demo;
    engine.rootContext()->setContextProperty("QML",&demo);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}

We then write qml files to make the interface.
main.qml:

import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Qt Remote Server")

    Rectangle{
        anchors.fill: parent
        TextArea{
            id: textArea
            width: parent.width - 4
            height: parent.height - 46
            anchors.horizontalCenter: parent.horizontalCenter
            y: 2
            readOnly: true
            selectByMouse: true
        }
        TextField{
            id: inforText
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 2
            x: 2
            width: parent.width-86
            height: 40
        }
        Button{
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 2
            width: 80
            height: 40
            anchors.right: parent.right
            anchors.rightMargin: 2
            text: qsTr("send out")
            onClicked: {
                if(inforText.text !== "") QML.sendToClient(inforText.text)
            }
        }
    }

    Connections{
        target: QML
        onRecvMessage:{
            textArea.append(message)
        }
    }
}

Run it to see the interface.

Step 3 Qt create client

In the last step, we have created a server, but we can't see the effect without a client. Now let's try to be a client.
The rep file is still the commoninterface rep.
Similarly, we use Qt to create a qml project named RemoteObjectClient.
Open pro file write:

QT += remoteobjects
REPC_REPLICA += \
    ../Reps/CommonInterface.rep

Note that REPC is used here_ Replica is different from the server. Don't blame me if you write it wrong and can't find the header file.
Compile and run, so that we can get a Qt automatically generated header file rep under the build directory_ CommonInterface_ replica. h. Copy it to the bottom of the project.
The client does not need to create a special class to receive and send information like the server. We directly create a class connected to qml, named MyQmlFunction, and start writing code.
MyQmlFunction. In H:

#ifndef MYQMLFUNCTION_H
#define MYQMLFUNCTION_H

#include <QObject>
#include <QRemoteObjectNode>
#include "rep_CommonInterface_replica.h"

class MyQmlFunction : public QObject
{
    Q_OBJECT
public:
    explicit MyQmlFunction(QObject *parent = nullptr);
    Q_INVOKABLE void sendToServer(QString information);

private:
    QRemoteObjectNode remoteNode;
    CommonInterfaceReplica *remoteObj = nullptr;

signals:
    Q_INVOKABLE void recvMessage(QString message);

public slots:
};

#endif // MYQMLFUNCTION_H

MyQmlFunction. In CPP:

#include "MyQmlFunction.h"


MyQmlFunction::MyQmlFunction(QObject *parent) : QObject(parent)
{
    remoteNode.connectToNode(QUrl("local:interfaces"));
    remoteObj = remoteNode.acquire<CommonInterfaceReplica>("CommonInterfaceSource");
    connect(remoteObj, SIGNAL(sigMessage(QString)), this, SIGNAL(recvMessage(QString)));
}

void MyQmlFunction::sendToServer(QString information)
{
    remoteObj->onMessage(information); //Call the slot to send a message to the server
}

Let me talk about the code to connect to the server.

When connecting to the tonode server, the following QUrl must be consistent with the server, otherwise it will not be connected.
remoteNode. Acquire < > () CommonInterfaceReplica in angle brackets is the class in the header file automatically generated by Qt, and "CommonInterfaceSource" in parentheses is the resource name, which should also be consistent with the server.

Write the code to connect to QML. In fact, this part is the same as the server.
main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "MyQmlFunction.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    MyQmlFunction demo;
    engine.rootContext()->setContextProperty("QML",&demo);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}

main.qml:

import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Qt Remote Client")

    Rectangle{
        anchors.fill: parent
        TextArea{
            id: textArea
            width: parent.width - 4
            height: parent.height - 46
            anchors.horizontalCenter: parent.horizontalCenter
            y: 2
            readOnly: true
            selectByMouse: true
        }
        TextField{
            id: inforText
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 2
            x: 2
            width: parent.width-86
            height: 40
        }
        Button{
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 2
            width: 80
            height: 40
            anchors.right: parent.right
            anchors.rightMargin: 2
            text: qsTr("send out")
            onClicked: {
                if(inforText.text !== "") QML.sendToServer(inforText.text)
            }
        }
    }

    Connections{
        target: QML
        onRecvMessage:{
            textArea.append(message)
        }
    }
}

OK, let's run the server first, then run the client, and send information to each other.

Is it successful? The message sent by the server can be successfully received and displayed by the client. The message sent by the client can also be successfully received and displayed by the server.

Step 4 create Python client

OK, let's create a client in Python to connect to Qt's server.
Create a client Py file, write code:

import sys
from PyQt5.QtCore import QUrl, QTimer, QObject, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QApplication
from PyQt5.QtRemoteObjects import QRemoteObjectNode


class DynamicClient(QObject):
    def __init__(self, remoteObj, parent=None):
        super().__init__(parent)
        self._remoteObj = remoteObj
        self._remoteObj.initialized.connect(self.initConnection)    # It is not useful until initialized is successfully initialized.

        self.timerCount = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.slotTimeOut)
        self.timer.start(1000)

    sendMessage = pyqtSignal(str, name="sigMessage")    # Create a signal named sigMessage to send information to the server

    @pyqtSlot()
    def initConnection(self):
        self._remoteObj.sigMessage.connect(self.slotRecvMessage)
        self.sendMessage.connect(self._remoteObj.onMessage)

    @pyqtSlot(str)
    def slotRecvMessage(self, msg):     # Create a slot function to receive the information sent by the server
        print("python get message:", msg)

    @pyqtSlot()
    def slotTimeOut(self):
        self.timerCount += 1
        self.sendMessage.emit("client start times: " + str(self.timerCount))


def main():
    app = QApplication(sys.argv)
    remoteNode = QRemoteObjectNode()
    remoteNode.connectToNode(QUrl("local:interfaces"))
    remoteObj = remoteNode.acquireDynamic('CommonInterfaceSource')
    dy = DynamicClient(remoteObj=remoteObj)
    return app.exec()


if __name__ == "__main__":
    main()

Let me talk about it a little bit. pyqt is roughly the same as Qt syntax, and the way to open the client is also the same, but it should be noted that remotenode acquireDynamic(‘CommonInterfaceSource’).

The syntax of QT is remotenode acquire< CommonInterfaceReplica >(“CommonInterfaceSource”); Remember, there is no acquire method in python. python only has acquireddynamic, so you must enter a name here. If we don't specify a name when we create the server in QT, it certainly won't work here in python.

In addition, I passed RemoteObject as a parameter into the DynamicClient class. When calling the DynamicClient class, you must remember to export an object, dy = DynamicClient(remoteObj=remoteObj). You can call anything else instead of dy, but remember to have such a parameter, otherwise it can't be used. Don't ask me why I know.

Don't mention that QUrl is consistent. It is consistent with Qt client. In addition, let's look at the signal and slot function.
The signal sigMessage and slot function onMessage must be consistent with Qt, otherwise the message will not be received. Although python does not need the rep file and header file, the signals and slots defined here actually play the same role.

It should also be noted that because acquireddynamic is used in python, the connection slot function must be initialized before it is useful. Because I didn't make an interface in python, I opened a timer QTimer to send messages to the server regularly.

Let's run it, open the Qt server and client, and then open the python client.

See, the message sent regularly by Python has been received by the Qt server, and the message sent by the Qt server has been received by both the Qt client and the python client.

Step 5 create a Python server

Finally, we use Python to create a server, and try connecting Python client and Qt client at the same time.
Create a file server Py, write code:

import sys
from PyQt5.QtCore import QUrl, QTimer, QObject, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QApplication
from PyQt5.QtRemoteObjects import QRemoteObjectHost, QRemoteObjectNode, QRemoteObjectDynamicReplica


class MyServerRemote(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.timerCount = 0
        self.timer = QTimer()

    sendMessage = pyqtSignal(str, name="sigMessage")

    @pyqtSlot(str)
    def onMessage(self, msg):
        print("server get message:", msg)


    def startTimer(self):
        self.timer.timeout.connect(self.slotTimeOut)
        self.timer.start(1000)

    @pyqtSlot()
    def slotTimeOut(self):
        self.timerCount += 1
        self.sendMessage.emit("server start times: " + str(self.timerCount))


def main():
    app = QApplication(sys.argv)
    serverRemote = MyServerRemote()
    remoteHost = QRemoteObjectHost(QUrl('local:interfaces'))
    remoteHost.enableRemoting(serverRemote, "CommonInterfaceSource")
    serverRemote.startTimer()
    return app.exec()


if __name__ == "__main__":
    main()

There's nothing to say about creating a server. It's the same as Qt server. You also need to write a separate class for the server to send and receive signals. On the python side, I wrote a MyServerRemote. The signals and slots are created the same way. I also opened a timer to send messages regularly.
There's nothing to say. Run it directly.

It can be seen that both Python clients and qt clients can receive messages sent regularly from the python server. Let's take a look at the python server.

The python server can also receive timing messages from the python client and messages sent by clicking the button from the Qt client.
Isn't it great!

Topics: Python Qt pyqt