c++qtsignals-slotsqtremoteobjects

How to handle signals on QRemoteObject?


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);
        });
    }
};

Solution

  • 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.

    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;
        }
    );