I'm a bit new to Qt
, after reading this question I'm trying to learn more about QRemoteObject
, following the mentioned question, comments, and links, I tried to write a class to share data between processes.
Based on the doc I build this .rep
file into the two processes:
class SharedObject
{
SIGNAL(signalAPP1(QString text))
SIGNAL(signalAPP2(QString text))
};
However, when I press F2
on APP1
it's not firing the lambda on APP2
connect(ptr.data(), &SharedObjectReplica::signalAPP1, this, [](QString text) {
qDebug() << "signalAPP1! text:" << text;
});
The same thing happens when i press F2
on APP2
, it's not firing the lambda on APP1
connect(sharedObject, &SharedObject::signalAPP2, this, [=](QString text) {
qDebug() << "signalAPP2! text:" << text;
});
What i'm missing?
app1.h
#include "rep_sharedobject_source.h"
class SharedObject : public SharedObjectSource
{
Q_OBJECT
public:
signals:
void signalAPP1(QString text);
void signalAPP2(QString text);
};
QT_BEGIN_NAMESPACE
namespace Ui { class APP1Class; };
QT_END_NAMESPACE
class APP1 : public QMainWindow
{
Q_OBJECT
public:
SharedObject* sharedObject = nullptr;
QRemoteObjectHost srcNode;
Ui::APP1Class *ui;
APP1(QWidget *parent = nullptr) : QMainWindow(parent), ui(new Ui::APP1Class())
{
ui->setupUi(this);
sharedObject = new SharedObject();
sharedObject->setObjectName("sharedObject");
srcNode.setHostUrl(QUrl(QStringLiteral("local:sharedObject")));
srcNode.enableRemoting(sharedObject);
QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
connect(shortcut, &QShortcut::activated, [=] {
qDebug() << "emiting...";
emit sharedObject->signalAPP1("hello from APP1");
});
connect(sharedObject, &SharedObject::signalAPP2, this, [=](QString text) {
qDebug() << "signalAPP2! text:" << text;
});
}
};
app2.h
#include "rep_sharedobject_replica.h"
QT_BEGIN_NAMESPACE
namespace Ui { class APP2Class; };
QT_END_NAMESPACE
class APP2 : public QMainWindow
{
Q_OBJECT
public:
Ui::APP2Class *ui;
QSharedPointer<SharedObjectReplica> ptr;
QRemoteObjectNode repNode;
APP2(QWidget *parent = nullptr) : QMainWindow(parent), ui(new Ui::APP2Class())
{
ui->setupUi(this);
repNode.connectToNode(QUrl(QStringLiteral("local:sharedObject")));
ptr.reset(repNode.acquire<SharedObjectReplica>());
connect(ptr.data(), &SharedObjectReplica::initialized, this, [=]
{
qDebug() << "conected...";
connect(ptr.data(), &SharedObjectReplica::signalAPP1, this, [](QString text) {
qDebug() << "signalAPP1! text:" << text;
});
});
QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
connect(shortcut, &QShortcut::activated, [=] {
QString str = "hello from APP2";
emit ptr.data()->signalAPP2(str);
});
}
};
I managed to reproduce your problem with the code you provided and solved it.
First thing first: I find it strange your subclass of QMainWindow
is what is in charge of publishing/retrieving the remove object, let alone do it in the constructor.
Now for your issue. According to my analysis, the replica fails to acquire a connection to the original object because you did not name it.
That is something I warned about in the other question you consulted and I believe you should have notice you never get to execute the lambda connected to the SharedObjectReplica::initialized
signal that you expect. That was a big clue as to where exactly your code was not meeting your expectations.
In APP1, do, with a name:
srcNode.enableRemoting(sharedObject, QStringLiteral("MySharedObject"));
And the corresponding in APP2:
ptr.reset(repNode.acquire<SharedObjectReplica>(QStringLiteral("MySharedObject")));
Now that the replica is connected to the object, we need to address your SharedObject
class. As you have defined a SharedObjectSource
superclass from a .rep
file, the signals signalAPP1
and signalAPP2
are already defined after compiling it.
SharedObject::signalAPP1
/SharedObject::signalAPP2
conflict with SharedObjectSource::signalAPP1
/SharedObjectSource::signalAPP1
, so remove them from SharedObject
.
And finally, signalAPP1
works as expected (but not signalAPP2
).
The reason for that comes from a misunderstanding on your end: signals only flow from the server to the client.
Put another way: the client application can request the state of an object hosted on server (aka original object) to be changed but it makes no sense that the client application notifies the server said original object has changed.
signalAPP1
flows from the server to the client, so everything is good.signalAPP2
, in your code, is designed to flow from the client to the server which, like I said, makes no sense.The correct approach was to create a Q_INVOKABLE
method on the server.
class SharedObject : public QObject {
Q_OBJECT
[...]
Q_INVOKABLE void SharedObject::emitSignalAPP2(QString text) {
emit signalAPP2(text);
}
}
and in the .rep
file, declare a slot:
SLOT(emitSignalAPP2(QString))
And finally, what you must connect to the F2
key shortcut:
connect(shortcut, &QShortcut::activated, [=] {
QString str = "hello from APP2";
emit ptr.data()->signalAPP2(str);
}
);
Final note:
This way of connecting signals on the client is IMO wrong:
connect(ptr.data(), &SharedObjectReplica::initialized, this, [=]
{
qDebug() << "connected...";
connect(ptr.data(), &SharedObjectReplica::signalAPP1, this, [](QString text) {
qDebug() << "signalAPP1! text:" << text;
}
);
A SharedObjectReplica
is a valid QObject
so as long as you have a valid instance, you can create the connection even if SharedObjectReplica::isInitialized()
returns false for that instance.
The consequence of waiting for the &SharedObjectReplica::initialized
signal to be emitted is that if the server shuts down and then restarts, you will receive a new signal, therefore create a new connection. After that happens (and since you did not specify Qt::UniqueConnection
on &SharedObjectReplica::signalAPP1
), your lambda is executed twice for every signalAPP1
receives.
Then 3 times if the server restarts again, then 4 and so on.
The correct way to connect signals from a replica is to ignore whether or not the connection was established, i.e. simply do:
connect(ptr.data(), &SharedObjectReplica::signalAPP1, this,
[](QString text) {
qDebug() << "signalAPP1! text:" << text;
}
);