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();
}
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.
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();
}
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
:
"MySharedObject"
object identifier.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:
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)
};
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
Finally, we moc remoteobject.h
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).(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].
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;
}
As stated above, in the main
function, we need to add qRegisterMetaType<MyStruct>();
.
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.
On the client's side, we include the same declaration/definition for MyStruct
, operator>>
and operator<<
.
The remoteobject.rep
file must now look like:
#include "MyStruct.h"
class SharedObject
{
PROP(QString text READWRITE)
PROP(MyStruct dataStruct READWRITE)
};
This will generate:
dataStruct()
and a setDataStruct
method for the replica (it does not matter the names are not the same as the server's object).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.