c++qtserverqt6qtremoteobjects

How to use the QRemoteObject class to share data between processes?


I'm trying to follow the Qt Remote Objects example, and I wrote a small example using Qt 6.3.

The code for the server is compiling, but the code of the client process does not compile, and I'm getting this error:

Error   C2661   'SharedObject::SharedObject': no overloaded function takes 2 arguments  C:\Qt\6.3.0\qtremoteobjects\src\remoteobjects\qremoteobjectnode.h   78  

Which is caused by this line:

auto sharedObject = node.acquire<SharedObject>();

server

// remoteobject.h
#include <QRemoteObjectRegistry>
#include <QRemoteObjectRegistryHost>
#include <QRemoteObjectHost>

class SharedObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)

public:
    QString text() const { return m_text; }
    void setText(const QString& text)
    {
        if (m_text != text) {
            m_text = text;
            emit textChanged(m_text);
        }
    }

signals:
    void textChanged(const QString& text);

private:
    QString m_text;
};

// main.cpp
#include "remoteobject.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    SharedObject sharedObject;
    sharedObject.setObjectName("sharedObject");
    QRemoteObjectHost host;
    host.setHostUrl(QUrl("local:sharedObject"));
    host.enableRemoting(&sharedObject);

    return a.exec();
}

client

// remoteobject.h
#include <QRemoteObjectRegistry>
#include <QRemoteObjectRegistryHost>
#include <QRemoteObjectHost>

class SharedObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)

public:
    QString text() const { return m_text; }
    void setText(const QString& text)
    {
        if (m_text != text) {
            m_text = text;
            emit textChanged(m_text);
        }
    }

signals:
    void textChanged(const QString& text);

private:
    QString m_text;
};

// main.cpp
#include "remoteobject.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QRemoteObjectNode node;
    node.connectToNode(QUrl("local:sharedObject"));
    auto sharedObject = node.acquire<SharedObject>();

    QObject::connect(sharedObject, &SharedObject::textChanged, [&](const QString& text) {
        qDebug() << "Text changed to:" << text;
    });

    QTimer::singleShot(10000, [&]() {
        qDebug() << "Current text:" << sharedObject->text();
    });

    return a.exec();
}

Solution

  • Before I dive into the answer, let me describe a widget I added to make the example more interactive, by allowing the text to be modified by hand from either the server or the client. The widget contains:

    For the sake of providing a code that works by itself without making people copy parts of your question, I will reproduce everything, including what works from what you wrote.


    Server-side

    Very few mistakes there.
    If anything, I'd say you must specify a name in enableRemoting() (I called it "MySharedObject")

    // remoteobject.h
    #include <QtRemoteObjects/QRemoteObjectRegistry>
    #include <QtRemoteObjects/QRemoteObjectRegistryHost>
    #include <QtRemoteObjects/QRemoteObjectHost>
    
    class SharedObject : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
    public:
        QString text() const { return m_text; }
        void setText(const QString& text)
        {
            if (m_text != text) {
                m_text = text;
                emit textChanged(m_text);
            }
        }
    
    signals:
        void textChanged(const QString& text);
    
    private:
        QString m_text;
    };
    
    
    
    // main.cpp
    #include <QtWidgets/QApplication>
    #include <QtWidgets/QLabel>
    #include <QtWidgets/QLineEdit>
    #include "remoteobject.h"
    
    
    int main(int argc, char* argv[])
    {
        QApplication a(argc, argv);
        a.setQuitOnLastWindowClosed(true);
    
        SharedObject sharedObject;
        QRemoteObjectHost host;
        host.setHostUrl(QUrl("local:sharedObject"));
        host.enableRemoting(&sharedObject, QStringLiteral("MySharedObject"));
    
        QWidget w;
        w.setFixedSize(250, 80);
        w.setWindowTitle("Server");
    
        QLineEdit edit(&w);
        edit.setFixedWidth(250);
        edit.move(0, 40);
        edit.setText(sharedObject.text());
        QObject::connect(&edit        , &QLineEdit::textEdited,
                         &sharedObject, &SharedObject::setText);
        QObject::connect(&sharedObject, &SharedObject::textChanged,
                         &edit        , &QLineEdit::setText);
    
        QLabel label(&w);
        label.setFixedWidth(250);
        QObject::connect(&edit        , &QLineEdit::textEdited,
                         [&label]() { label.setText("Changed from Server"); });
        QObject::connect(&sharedObject, &SharedObject::textChanged,
                         [&label]() { label.setText("Changed from Client"); });
    
        w.show();
        return a.exec();
    }
    

    Client-side

    This is where you got lost.

    The error 'SharedObject::SharedObject': no overloaded function takes 2 arguments is about the constructor expected to have 2 parameters. If you do not know what parameters are expected there, it is absolutely normal and I am going to describe what you should have done instead in a minute.

    Before that, let us see what changes I made to the client's main.cpp:

    // main.cpp
    #include <QtWidgets/QApplication>
    #include <QtWidgets/QLabel>
    #include <QtWidgets/QLineEdit>
    #include "remoteobject.h"
    
    int main(int argc, char* argv[])
    {
        QApplication a(argc, argv);
        a.setQuitOnLastWindowClosed(true);
    
        
        QRemoteObjectNode node;
        node.connectToNode(QUrl("local:sharedObject"));
        auto sharedObject = node.acquire<SharedObjectReplica>(QStringLiteral("MySharedObject"));
    
    
        QWidget w;
        w.setFixedSize(250, 120);
        w.setWindowTitle("Client");
    
        QLineEdit edit(&w);
        edit.setFixedWidth(250);
        edit.move(0, 80);
        edit.setText(sharedObject->text());
        QObject::connect(&edit, &QLineEdit::textEdited, sharedObject, &SharedObjectReplica::setText);
        QObject::connect(sharedObject, &SharedObjectReplica::textChanged, &edit, &QLineEdit::setText);
    
        QLabel label1(&w), label2(&w);
        label1.setFixedWidth(250);
        QObject::connect(&edit, &QLineEdit::textEdited, [&label1]() { label1.setText("Changed from Client"); });
        QObject::connect(sharedObject, &SharedObjectReplica::textChanged, [&label1]() { label1.setText("Changed from Server"); });
    
        label2.setFixedWidth(250);
        label2.move(0, 40);
        //label2 will be used later
    
    
        w.show();
        return a.exec();
    
    }
    
    

    Now, we have a SharedObjectReplica defined nowhere, whose constructor must have 2 parameters that we do not choose...
    The trick to get ourselves unstuck is to have Qt generate the SharedObjectReplica class for us.
    The server has the object with all the code; the client only needs a reference of the remote data.

    To do this:

    1. We create a very simple remoteobject.rep file (documented here). Your SharedObject class only has 1 read/write property with a signal. That is exactly what we type in the file.

      class SharedObject
      {
          PROP(QString text READWRITE)
      };
      
    2. Similar to the meta-object compiler (moc.exe), we compile the above file with the replica compiler (repc.exe).
      This creates our SharedObjectReplica class in a header file, with the correct property, methods and signal. You will notice the constructor has 2 parameters like we wanted.

      repc.exe" -o replica remoteobject.rep remoteobject.h
      
    3. Finally, we moc remoteobject.h


    The result

    1. You get 1 server + 1 client applications, each showing a window where you can edit the object's text.
    2. You are free to start the client after or before the server, it will work anyway.
    3. You will probably notice the window of the client application only shows the data is changed by the server.
      This is something I wanted to demonstrate: when you change the text from the client window, you will immediately get the textChanged signal from the server in return, and it happens so fast it is practically impossible to event see the text flicker (keep your finger onto a key and you will see this happen, eventually).
      This is usually not a problem, because it never matters where the change originated from, but you may want to keep that in mind ... maybe.

    Going beyond

    (Edit to address the comment with link to there).

    Structures can indeed be shared with a bit of effort. You must follow this page (a link to it is provided in the repc helppage from above).
    It is important MyStruct supports queued connections (I know there is a warning somewhere about this fact but I could not find it to add the link).
    -> we need to follow the instruction until and including the section [Creating and Destroying Custom Objects].

    1. We declare the struct with required default constructor, destructor, and copy constructor.

      //MyStruct.h
      #include <QtCore/QDataStream>
      #include <QtCore/QString>
      
      struct MyStruct {
          MyStruct() = default;
          ~MyStruct() = default;
          MyStruct(int val, const QString& text) : x(val), test(text) {}
          MyStruct(const MyStruct& other) {
              x = other.x;
              test = other.test;
          }
          MyStruct& operator=(const MyStruct&) = default;
      
      
          int x = 0;
          QString test;
      };
      
      Q_DECLARE_METATYPE(MyStruct)
      
      QDataStream& operator<<(QDataStream& out, const MyStruct& myStruct);
      QDataStream& operator>>(QDataStream& in, MyStruct& myStruct);
      
      
      //MyStruct.cpp
      QDataStream& operator<<(QDataStream& out, const MyStruct& myStruct) {
          out << myStruct.x << myStruct.test;
          return out;
      }
      
      QDataStream& operator>>(QDataStream& in, MyStruct& myStruct) {
          in >> myStruct.x >> myStruct.test;
          return in;
      }
      
    2. As stated above, in the main function, we need to add qRegisterMetaType<MyStruct>();.

    3. In the server's ShareObject class, we add the property (that I leave for now incomplete, to illustrate something later):

      Q_PROPERTY(MyStruct dataStruct READ getStruct WRITE setStruct)
      

      I let you implement the getter/setter for the property.

    4. On the client's side, we include the same declaration/definition for MyStruct, operator>> and operator<<.

    5. The remoteobject.rep file must now look like:

      #include "MyStruct.h"
      
      class SharedObject
      {
          PROP(QString text READWRITE)
          PROP(MyStruct dataStruct READWRITE)
      };
      

      This will generate:

      • a dataStruct() and a setDataStruct method for the replica (it does not matter the names are not the same as the server's object).
      • The qRegisterMetaType<MyStruct>(); inside the generated class, so we do not have to add it ourselves.

      I declare the property with READWRITE for the sake of having a more complete illustration but I invite you to test the other keyworks CONSTANT, READONLY, PERSISTED, READWRITE, READPUSH (default), SOURCEONLYSETTER and look at the generated class to see what getter, setter and signal you get out of each.



    The code from the above sample can now be edited for illustration:

    On the server, I changed the setText method so that the attached struct counts how many times it was edited + what the last value was (I declared the private member as MyStruct myStruct;):

    void SharedObject::setText(const QString& text)
    {
        if (m_text != text) {
            myStruct.x += 1;
            myStruct.test = m_text;
            m_text = text;
            emit textChanged(m_text);
        }
    }
    

    On the client, we can now make use of the previously unused label2, from the main function, by adding:

    QObject::connect(sharedObject, &SharedObjectReplica::textChanged,
        [sharedObject, &label2]() {
            auto newStruct = sharedObject->dataStruct();
            label2.setText(QString("sharedStruct: %1 , %2").arg(newStruct.x).arg(newStruct.test));
            if (newStruct.x >= 10)
                sharedObject->setDataStruct(MyStruct{ 0, "Reset" });
        }
    );
    

    You will notice that label2 does not get updated unless you restart the client, contrary to what the connection would suggest. It is able to write a new structure into the server but nothing is visible in real time.
    This is caused by the fact no signal is attached to the dataStruct property.
    Without a signal, the client does not know the structure changes and keeps a copy of it; this is the same behavior as property binding in QML.

    To correct it, you therefore need to change the property declaration to

    Q_PROPERTY(MyStruct dataStruct READ getStruct WRITE setStruct NOTIFY dataStructChanged)
    

    Then add the signal to your class. Do not forget to actually emit the dataStructChanged(...); inside the modified setText method.

    And voila! the client can get the structure, gets notified when it changes and can also write into it.