1

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();
}
Cesar
  • 41
  • 2
  • 5
  • 16

1 Answers1

3

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:

  • A label indicating which side changed the text.
  • The text of the object.

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:

  • I added the "MySharedObject" object identifier.
  • I changed the class name from SharedObject to SharedObjectReplica.
// 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.

Atmo
  • 2,281
  • 1
  • 2
  • 21
  • And when its a struct? how to add it to the rep file? i cross-post this question [here](https://www.reddit.com/r/QtFramework/comments/1344y7h/how_to_share_a_struct_between_two_processes_using/) – Cesar Apr 30 '23 at 23:06
  • Google "C++ struct vs class" and the first result you will get is probably going to be [this](https://learn.microsoft.com/en-us/cpp/cpp/classes-and-structs-cpp?view=msvc-170), reminding you that *The two constructs are **identical** in C++ except that in structs the default accessibility is public, whereas in classes the default is private.* – Atmo May 01 '23 at 00:41
  • Atmo i still dont understand, how would be the Q_PROPERTY passing a struct? do you see my example on reddit? there i didnt use a rep file – Cesar May 01 '23 at 00:54
  • So.... to share an instance `MyStruct`? – Atmo May 01 '23 at 01:45
  • Yes! im struggling in understand how to share it – Cesar May 01 '23 at 02:16
  • It doubled the length of my answer but it's done. – Atmo May 01 '23 at 04:04
  • Thanks! i see, its `PROP(MyStruct dataStruct READWRITE)` and when the function which will be called expect more than one parameter? as property is limited to one parameter – Cesar May 01 '23 at 11:36
  • 1
    Functions are more straightforward than sharing `struct`. For the server, create a `Q_INVOKABLE` method or a slot. For the client, reproduce the content from the first link I have provided (go down to `SLOT` for the sample). You have enough clue to experiment by yourself now. – Atmo May 01 '23 at 11:56
  • The repc compiler is failing to compile the source of your rep – Cesar May 01 '23 at 12:42
  • It works for me, what's your error message? – Atmo May 01 '23 at 12:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/253411/discussion-between-joey-and-atmo). – Cesar May 01 '23 at 13:29
  • Hi Atmo, I'm trying to reproduce your code, when I emit a signal from the server it's not being fired on the client, I have added it to the rep file `SIGNAL(signalTest(QString text))`, then `emit sharedObject->signalTest("hello world");` and on the client, it's not fired. – Cesar May 01 '23 at 17:55
  • Based on just your comment, I cannot say what is going on. One thing for sure, you made no mistake declaring the signal in the rep file because it works on my computer. You need to create a separate question with minimal, reproducible example so I can analyze it. I recommend your class, and the .rep, only has this one signal and nothing else (no properties, no slots, no other signals, ...) – Atmo May 02 '23 at 04:45
  • I see, thanks, i have created a separate question – Cesar May 02 '23 at 07:36