📜 ⬆️ ⬇️

Inter-Process Object Replication with QtRemoteObjects

On October 7, 2014, the Qt-module QtRemoteObjects sources appeared in public access. The module was created in the depths of the Ford Motor Company (by Brett Stottlemyer). The thing, in my opinion, is very promising. The module allows, for example, to transmit signals between objects over the network. But this module is not limited to. More precisely, the essence of the module is described by its previous name, Replica, since objects are “replicated” between processes.



The key idea of ​​QtRemoteObjects, which qualitatively distinguishes it from other methods of interprocess communication / remote procedure call, is the idea to completely duplicate the Qt-object to other processes. This means that all changes of properties (properties) in an object - source are reflected (with notification by signals) in a replica object. Any signals emitted by the source object will also be emitted at each replica object. You can also set properties, call slots in a replica object, whereby requests are sent to the source object that processes them and then changes are reflected in other replica objects by means of signals or by changing properties. As a result, all objects (including the source object) are synchronized. At the same time, the complexity of interprocess communication is hidden inside QtRemoteObjects.

Assembly and installation of the module


1. Install private header files for your Qt 5 version

On your system, performed by installing the package qtbase5-private-dev
')
2. Get the source of the module

git clone gitorious.org/qtplayground/qtremoteobjects.git

3. Assemble and install the module

Collected the project in QtCreator, using shadow assembly. As a result, all the necessary files appeared in the build-qtremoteobjects-qt5_3_0-Release directory. If you then perform make install in this directory, then the module header files, library and code generator for the rep files will be installed in the corresponding directories. The only thing that was not installed is the contents of the qtremoteobjects / mkspecs / features directory, it needs to be copied to the / usr / lib / x86_64-linux-gnu / qt5 / mkspecs / features directory.

If there are problems with make install, you can do everything “manually”: copy the library libQt5RemoteObjects.so (dll) to the Qt library directory and the / include / QtRemoteObjects directory to the Qt directory, respectively. Also, in “manual” mode, you will need to copy the binary from the bin / repc build directory to the Qt binary directory (in my case, this is / usr / lib / x86_64-linux-gnu / qt5 / bin / repc).

Use of the module


The presence of the object - the source and the object - the replica evokes the idea of ​​a client-server architecture. Therefore, to demonstrate the operation of the module, we will create two projects - the client and the server. The source object on the server will emit a signal, in the parameters of which the text will be transmitted, and in the slot of the client connected to the signal of the replica object, the transmitted text will be processed.
The example is very simple, but with this example it will be easier to show the operation of the module. Two pictures of the resulting applications and further code.

Server

Customer


Client and server projects

We will have one common directory (for example remoteobj) and 2 subdirectories: client and server, in which projects of two applications will be located. In the project files of both applications you need to connect the module.
QT += remoteobjects 

.Rep files

Files with the .rep extension are used to describe the interface of the object that will be used for interprocess communication. In the directory that is parent of the client and server projects directory, create a text file MessageSender.rep with the following contents:
 #include <QtCore> class MessageSender { SIGNAL(sendMessage(const QString &message)); }; 

Remember I wrote about the files from the directory mkspecs / features? It is in these files that the described ones are described for processing rep files. In order for them to be processed during assembly, the following lines must be added to the project files:
for client
 REPC_REPLICA += ../MessageSender.rep 
for server
 REPC_SOURCE += ../MessageSender.rep 

During the build from the file MessageSender.rep will be generated header files
for client - rep_MessageSender_replica.h
 #ifndef REP_MESSAGESENDER_REPLICA_H #define REP_MESSAGESENDER_REPLICA_H // This is an autogenerated file. // Do not edit this file, any changes made will be lost the next time it is generated. #include <QObject> #include <QVariantList> #include <QMetaProperty> #include <QRemoteObjectNode> #include <QRemoteObjectReplica> #include <QRemoteObjectPendingReply> #include <QtCore> class MessageSenderReplica : public QRemoteObjectReplica { Q_OBJECT Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "MessageSender") friend class QRemoteObjectNode; private: MessageSenderReplica() : QRemoteObjectReplica() {} void initialize() { QVariantList properties; properties.reserve(0); setProperties(properties); } public: virtual ~MessageSenderReplica() {} Q_SIGNALS: void sendMessage(const QString & message); }; #endif // REP_MESSAGESENDER_REPLICA_H 


for the server - rep_MessageSender_source.h
 #ifndef REP_MESSAGESENDER_SOURCE_H #define REP_MESSAGESENDER_SOURCE_H // This is an autogenerated file. // Do not edit this file, any changes made will be lost the next time it is generated. #include <QObject> #include <QVariantList> #include <QMetaProperty> #include <QRemoteObjectNode> #include <qremoteobjectsource.h> #include <QtCore> class MessageSenderSource : public QObject { Q_OBJECT Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "MessageSender") friend class QRemoteObjectNode; public: MessageSenderSource(QObject *parent = Q_NULLPTR) : QObject(parent) { } public: virtual ~MessageSenderSource() {} Q_SIGNALS: void sendMessage(const QString & message); }; class MessageSenderSimpleSource : public QObject { Q_OBJECT Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "MessageSender") friend class QRemoteObjectNode; public: MessageSenderSimpleSource(QObject *parent = Q_NULLPTR) : QObject(parent) { } public: virtual ~MessageSenderSimpleSource() {} Q_SIGNALS: void sendMessage(const QString & message); }; template <class ObjectType> struct MessageSenderSourceAPI : public SourceApiMap { MessageSenderSourceAPI() { _properties[0] = 0; _signals[0] = 1; _signals[1] = qtro_signal_index<ObjectType>(&ObjectType::sendMessage, static_cast<void (QObject::*)(QString)>(0),signalArgCount+0,signalArgTypes[0]); _methods[0] = 0; } QString name() const Q_DECL_OVERRIDE { return QStringLiteral("MessageSender"); } int propertyCount() const Q_DECL_OVERRIDE { return _properties[0]; } int signalCount() const Q_DECL_OVERRIDE { return _signals[0]; } int methodCount() const Q_DECL_OVERRIDE { return _methods[0]; } int sourcePropertyIndex(int index) const Q_DECL_OVERRIDE { return _properties[index+1]; } int sourceSignalIndex(int index) const Q_DECL_OVERRIDE { return _signals[index+1]; } int sourceMethodIndex(int index) const Q_DECL_OVERRIDE { return _methods[index+1]; } int signalParameterCount(int index) const Q_DECL_OVERRIDE { return signalArgCount[index]; } int signalParameterType(int sigIndex, int paramIndex) const Q_DECL_OVERRIDE { return signalArgTypes[sigIndex][paramIndex]; } int methodParameterCount(int index) const Q_DECL_OVERRIDE { return methodArgCount[index]; } int methodParameterType(int methodIndex, int paramIndex) const Q_DECL_OVERRIDE { return methodArgTypes[methodIndex][paramIndex]; } int propertyIndexFromSignal(int index) const Q_DECL_OVERRIDE { Q_UNUSED(index); return -1; } const QByteArray signalSignature(int index) const Q_DECL_OVERRIDE { switch (index) { case 0: return QByteArrayLiteral("sendMessage(QString)"); } return QByteArrayLiteral(""); } const QByteArray methodSignature(int index) const Q_DECL_OVERRIDE { Q_UNUSED(index); return QByteArrayLiteral(""); } QMetaMethod::MethodType methodType(int) const Q_DECL_OVERRIDE { return QMetaMethod::Slot; } const QByteArray typeName(int index) const Q_DECL_OVERRIDE { Q_UNUSED(index); return QByteArrayLiteral(""); } int _properties[1]; int _signals[2]; int _methods[1]; int signalArgCount[1]; const int* signalArgTypes[1]; int methodArgCount[0]; const int* methodArgTypes[0]; }; #endif // REP_MESSAGESENDER_SOURCE_H 



These files must be included in the header files of the client and server - according to their belonging.

Customer

First we will analyze the client, as it is simpler than the server.
You need to add a member of the client class:
 QRemoteObjectNode clientNode; 

And then in the initializing function (or directly in the constructor) we write:
 clientNode = QRemoteObjectNode::createNodeConnectedToRegistry(); //  QRemoteObjectReplica *sender = m_client.acquire< MessageSenderReplica >(); //    connect(sender, SIGNAL(sendMessage(const QString &)), this, SLOT(appendMessage(const QString &))); //    

In the appendMessage slot, the resulting string is simply added to the list and therefore I’ll skip its description and go to the client’s description.

Server

Since only the interface is in the generated header files, in order to do useful work with our source object, you need to add functionality to it. To do this, we inherit from the generated class and define the slot:
 class MessageSender : public MessageSenderSource { Q_OBJECT public slots: void postMessage(const QString &message); }; 

The implementation of this slot will simply emit a signal.
 void MessageSender::postMessage(const QString &message) { emit sendMessage(message); } 


Now add the following server class members:
 MessageSender *serverSender; //   QRemoteObjectNode registryHostNode; //  QRemoteObjectNode objectNode; //  


And in the initializing function (or directly in the constructor) we write:
 connect(ui->sendButton, SIGNAL(clicked()), this, SLOT(startSendMessage())); //  registryHostNode = QRemoteObjectNode::createRegistryHostNode(); //   objectNode = QRemoteObjectNode::createHostNodeConnectedToRegistry(); //   serverSender = new MessageSender(); // - objectNode.enableRemoting(serverSender);//    


In the startSendMessage () slot, the source object slot will be called:
 QString messageText = ui->messageTextEdit->text(); serverSender->postMessage(messageText); 


Now we run the applications: first the server and then the client.

Networking

In this example, interprocess communication within a single host has been described. When creating nodes without parameters, it is considered that the interaction is local.

For networking, you need to modify the node creation code.
server side
 objectNode = QRemoteObjectNode::createHostNode(QUrl("tcp://localhost:9999")); //registryHostNode   

client side
 clientNode = QRemoteObjectNode(); clientNode.connect(QUrl("tcp://localhost:9999")); 


Instead of conclusion

The article does not cover the use of properties. An example of using properties and slots (which are also described in the rep file) can be seen in the examples that come with the module.

Link to the archive with the source server and client.
Presentation with Qt Developers Days 2014 North America

Source: https://habr.com/ru/post/216939/


All Articles