0

In my work project I have run into a particularly strange behaviour. During runtime, when calling QMetaObject::invokeMethod, I get a print indicating that the program is trying to find the specified method from QObject, instead of the actual class that the pointer I've given it points to. Even more strange is the fact that I made a mockup that, to my mind functions largely the same way as the code proper, with the difference that the mockup actually does what is expected. I can't find meaningful differences between the two nor figure out why my code fails to function as it should.

Please excuse some poor practices in the code shown. Due to reasons unrelated to this issue I've been forced to edit portions of the code, primarily names of classes and libraries, and haven't done a very good job in maintaining the quality present in the actual code. This has no effect in actual functionality, only readability.

I will provide the code of the mockup upon request, but this code is actually the one I'm having trouble with so I feel addingg that would just lead to unneccessary bloat.

EDIT

I was a dumb-dumb and forgot to include the error message. Please note that this too is similarily edited as the code base (that is, names of classes and some function names have been changed)

QMetaObject::invokeMethod: No such method QObject::simpleRx(QString,int,client*)

EDIT #2 Okay, so the source of the issue was the lack of Q_OBJECT macro in Host. Now I'm getting an "QMetaMethod::invoke: Unable to handle unregistered datatype 'int&'" error which I will look into next.

main.cpp

#include "maincontroller.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MainController mc(argc, argv);
    return a.exec();
}

maincontroller.h

class MainController : public QObject
{
    Q_OBJECT
private:
    //attributes
    client* comm;
    host* host;

    bool allOk;
    QStringList argList;

    int parseArguments(QStringList &arguments, int argc, char *argv[]);
public:
    //methods
    explicit MainController(int argc, char *argv[], QObject *parent = nullptr);
};

#endif // MAINCONTROLLER_H

maincontroller.cpp

#include "client.h"
#include "host.h"
#include <QCoreApplication>

MainController::MainController(int argc, char *argv[], QObject *parent) : QObject(parent)
{
    if(true)
    {
        host=new host();
        comm=new client(host, "main", this,0);
        //connect(comm, &client::killSignal, this, &MainController::quit);
    }
    else
    {
        exit(EXIT_FAILURE);
    }

}

client.h

#define client_H

#include <QObject>

class QUuid;
class host;

class client : public QObject
{
    Q_OBJECT
public:
    explicit client(host* givenHost, QString givenId, QObject* givenOwner, int givenPriority, QObject *parent=nullptr);

signals:
    void killSignal();
public slots:
private:
    //attributes
    //pointer to the instance that is using this instance of client
    QObject* owner;
    QString simpleId; //placeholder attribute until id mess is sorted
    host* host;
    QHash<QString, const char*>* handlers;
    //methods
    
public:
    //attributes

    //methods
    explicit client(host* givenHost, QString givenId, QObject* givenOwner, int givenPriority, QObject *parent=nullptr);
        int simpleTx(QString msg, QString msgName, QString simpleTarget, client* clientPtr=nullptr);
        int registerMe(QString threadName);
};

#endif // client_H

client.cpp

#include "host.h"
#include "globals.h"
#include<QUuid>
#include <QHash>
#include<QJsonObject>
#include<QJsonDocument>
#include <QMetaObject>
#include<QDebug>

client::client(host* givenHost, QString givenId, QObject* givenOwner, int givenPriority, QObject *parent) : QObject(parent),
    host(givenHost),
    handlers(new QHash<QString, const char*>),
    owner(givenOwner),
    simpleId(givenId),
    priority(givenPriority)
{
    id = new QUuid();
    id->createUuid();
    registerMe(givenId);
}

//registers this instance of client to host and sets simpleId to threadName
int client::registerMe(QString threadName)
{
    QJsonObject payload;
    payload.insert(SIMPLEID, threadName);
    payload.insert(PRIORITY, priority);
    payload.insert(ID, id->toString(QUuid::StringFormat::WithoutBraces));
    simpleId = threadName;
    simpleTx(JSONtoStr(payload),IPCREGISTRATIONHEADER, HOST, this);
    return ALLCLEAR;
}




int client::simpleTx(QString msg, QString msgName, QString SimpleTarget, client* clientPtr)
{
    int transmitReturnValue = 420;
    QJsonObject payload = StrToJSON(msg);
    QString fullMsg = JSONtoStr(JSONify(simpleId, SimpleTarget, msgName, payload));
    if(QMetaObject::invokeMethod(host, "simpleRx", Qt::QueuedConnection, Q_ARG(QString, fullMsg), Q_ARG(int, transmitReturnValue), Q_ARG(client*, nullptr)))
    {
        return ALLCLEAR;
    }
    else
    {
        return INVOKEERROR;
    }


}



int client::registerHandler(QString msgName, const char* method)
{
    handlers->insert(msgName, method);
    return ALLCLEAR;
}




int client::getPriority()
{
    return priority;
}




QJsonObject client::JSONify(QString src, QString dst, QString msgName, QJsonObject payload)
{
    QJsonObject header;
    QJsonObject msg;
    header.insert(SRC, src);
    header.insert(DST, dst);
    header.insert(MSGNAME, msgName);
    msg.insert(HEADER, header);
    msg.insert(PAYLOAD, payload);
    return msg;
}

QString client::JSONtoStr(QJsonObject json)
{
    QJsonDocument doc(json);
    QString msgString(doc.toJson(QJsonDocument::Compact));
    return msgString;
}

QJsonObject client::StrToJSON(QString jsonString)
{
    QJsonObject obj;
    QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8());

    // check validity of the document
    if(!doc.isNull())
    {
        if(doc.isObject())
        {
            obj = doc.object();
        }
        else
        {
            qDebug() << "Document is not an object";
        }
    }
    else
    {
        qDebug() << "Invalid JSON...\n" << jsonString;
    }
    return obj;
}

host.h

#define host_H

#include <QObject>

class client;

class host : public QObject
{
signals:
public slots:
private:
    //attributes
    QHash<QUuid*, client*>* registeredClients;
    QHash<QString, client*>* simpleRegisteredClients;
    QList<int> priorities;
    //methods
    int rx(QString jsonString, client* clientPtr);
    Q_INVOKABLE void simpleRx(QString jsonString, int &transmitreturnvalue, client* clientPtr=nullptr);
    QJsonObject StrToJSON(QString jsonString);
public:
    //attributes

    //methods
    explicit host(QObject *parent = nullptr);

};

#endif // host_H

host.cpp

#include "clent.h"
#include "globals.h"
#include<QJsonDocument>
#include<QJsonObject>
#include<QDebug>

host::host(QObject *parent) : QObject(parent)
{

}



void host::simpleRx(QString jsonString, int &transmitReturnValue, clent* clientPtr)
{
    QString msgName = StrToJSON(jsonString).value(HEADER).toObject().value(MSGNAME).toString();
    int rxret = 1;
    if(msgName==REGISTRATIONHEADER)
    {
        QString clientName = StrToJSON(jsonString).value(PAYLOAD).toObject().value(SIMPLEID).toString();
        simpleRegisteredClients->insert(clientName, clientPtr);
        if(clientPtr->getPriority()!=0)
        {
            priorities.push_back(clientPtr->getPriority());
        }
        transmitReturnValue = ALLCLEAR;
    }
    else if(msgName==ACCEPTDEATH)
    {
        QString simpleName = StrToJSON(jsonString).value(PAYLOAD).toObject().value(SIMPLEID).toString();
        simpleRegisteredClients->remove(simpleName);
    }
    else if(msgName==KILLMESSAGE && StrToJSON(jsonString).value(HEADER).toObject().value(DST).toString()==ALL)
    {
        std::sort(priorities.begin(), priorities.end());
        QHash<QString, clent*>::iterator i;
        while(!priorities.empty())
        {
            int currentPriority = priorities.takeLast();
            for (i = simpleRegisteredClients->begin(); i != simpleRegisteredClients->end(); ++i)
            {
                if(i.value()->getPriority()==currentPriority)
                {
                    QMetaObject::invokeMethod(i.value(), "rx", Qt::ConnectionType::QueuedConnection,
                                              Q_RETURN_ARG(int, rxret), Q_ARG(QString, jsonString));
                }
            }
        }
        QJsonObject finalMsg;
        QJsonObject payload;
        QJsonObject header;
        QString finalJsonString;
        header.insert(SRC, HOST);
        header.insert(MSGNAME, FINALMESSAGE);
        finalMsg.insert(HEADER, header);
        finalMsg.insert(PAYLOAD, payload);
        for (i = simpleRegisteredClients->begin(); i != simpleRegisteredClients->end(); ++i)
        {
            if(i.value()->getPriority()==0)
            {
                QMetaObject::invokeMethod(i.value(), "rx", Qt::ConnectionType::QueuedConnection,
                                          Q_RETURN_ARG(int, rxret), Q_ARG(QString, finalJsonString));
            }
        }
    }
    else
    {
        QString clientName = StrToJSON(jsonString).value(HEADER).toObject().value(DST).toString();
        bool invokeSuccesful=false;
        invokeSuccesful = QMetaObject::invokeMethod(simpleRegisteredClients->value(clientName), "rx",
                                  Qt::ConnectionType::QueuedConnection, Q_RETURN_ARG(int, rxret), Q_ARG(QString, jsonString));
        if(!invokeSuccesful)
        {
            qDebug() << "Invoke was not succesful";
            transmitReturnValue = INVOKEERROR;
        }
    }
    transmitReturnValue = ALLCLEAR;
}

QJsonObject host::StrToJSON(QString jsonString)
{
    QJsonObject obj;
    QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8());

    // check validity of the document
    if(!doc.isNull())
    {
        if(doc.isObject())
        {
            obj = doc.object();
        }
        else
        {
            qDebug() << "Document is not an object";
        }
    }
    else
    {
        qDebug() << "Invalid JSON...\n" << jsonString;
    }
    return obj;
}

CMakeLists.txt


project(myProject LANGUAGES CXX)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Network REQUIRED)


set(srcPath ${CMAKE_CURRENT_LIST_DIR}/sources)
set(hdrPath ${CMAKE_CURRENT_LIST_DIR}/headers)
set(extLib ${CMAKE_CURRENT_SOURCE_DIR}/../../externals/path_to/so_file)
set(libIncludes ${CMAKE_CURRENT_SOURCE_DIR}/../../externals/path_to/library/source/)

set(fileList
    ${srcPath}/main.cpp
    ${srcPath}/client.cpp
    ${srcPath}/host.cpp
    ${srcPath}/maincontroller.cpp
    ${hdrPath}/maincontroller.h
    ${hdrPath}/globals.h
    ${hdrPath}/client.h
    ${hdrPath}/host.h
    )

add_executable(myProject
  ${fileList}
)
set_target_properties(myProject PROPERTIES
    AUTOMOC ON
    CXX_STANDARD 17
    CXX_STANDARD_REQUIRED ON
    VERSION 0.9.0
    EXPORT_NAME "myProject"
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/build/archive"
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/build/lib"
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/build/run"
    )

target_include_directories(myProject PRIVATE headers/)

target_include_directories(myProject PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/headers ${libIncludes})
target_link_libraries(myProject PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network ${extLib})

I'm running Qt 6.1.0 on an Ubuntu 20.04

Please ask for any further details you need, and I will provide them if I can.

Your help is much appreciated.

Narmondur
  • 111
  • 1
  • 2
  • 7
  • 1
    Your `host` class is missing the `Q_OBJECT` macro. – cmannett85 Jul 14 '21 at 07:37
  • 1
    (beaten by @cmannett85 by 44 seconds) `host` doesn't have a `Q_OBJECT` ; if that's not an effect of your source-editing, that's likely it. – Adriaan de Groot Jul 14 '21 at 07:39
  • Does host need both Q_OBJECT and Q_INVOKABLE? It does sound like that's the issue if that's indeed the case. Does the location of the macro matter? – Narmondur Jul 14 '21 at 07:45
  • You can not pass non-const references through signal/slot - as you can see it's looking for `int` and not for `int &` – chehrlic Jul 14 '21 at 07:49
  • Since signals and slots can be run asynch you will not be able to pass a (non-const) reference through a signal/slot connection. No matter what someone tells or wants. – chehrlic Jul 14 '21 at 12:34

0 Answers0