QMetaObject invokeMethod 基于函数指针的语法

Functionpointer based syntax for QMetaObject invokeMethod

我正在为我们正在使用的 IPC 库开发一个简单的包装器。

我想将此库中的事件转换为对 Qt 插槽的调用。

现在我有这样的东西:

void Caller::registerCallback(int id, QObject* reciever, const char* member)
{
    _callbackMap[id] = std::make_pair(reciever, QString(member));
}

bool Caller::call(const SomeData data)
{
    auto reciever = _callbackMap.value(data.id);

    return QMetaObject::invokeMethod(reciever.first, reciever.second.toLocal8Bit(), Qt::QueuedConnection, 
        QGenericReturnArgument(),
        Q_ARG(SomeData, data));
}
void Receiver::someCallback(SomeData data)
{
    qDebug() << data.str;
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Caller caller;
    Receiver reciever;

    caller.registerCallback(1, &reciever, "someCallback");

    caller.call(SomeData({ "Hi", 1 }));

    return a.exec();
}
struct SomeData {
    QString str;
    int id;
}; Q_DECLARE_METATYPE(SomeData);

这个效果很好。但我不喜欢将回调注册为字符串。我更喜欢使用如下语法进行编译时检查:

caller.registerCallback(1, &reciever, &Reciever::someCallback);

我知道 this 实施。

我要注册的插槽总是只有一个参数并且没有 return 值。

我已经找到 this 请求可以解决我的问题,但不幸的是,这从未实现。 另外 this question 对我没有帮助,因为我无法修补我们正在使用的 moc。

那么 Qt 使用的所有元魔法真的不可能吗?


编辑:

我找到了一个解决方案,当来电者不知道接收者(实际上我需要的是什么)时也可以使用:

//Caller.h

class Caller : public QObject
{
    Q_OBJECT

public:
    Caller(QObject *parent = nullptr);
    ~Caller();

    //void registerCallback(int id, QObject* reciever, const char *member);
    template < class R, typename Func >
    void inline registerCallback(int id, R reciever, Func callback)
    {
        using std::placeholders::_1;
        registerCallbackImpl(id, reciever, std::bind(callback, reciever, _1));
    };

    bool call(const SomeData);

private:
    QMap<int, std::pair<QObject *, std::function<void(SomeData)>> > _callbackMap;

    void registerCallbackImpl(int id, QObject* reciever, std::function<void(SomeData)> callback);
};
//Caller.cpp
void Caller::registerCallbackImpl(int id, QObject* reciever, std::function<void(SomeData)> callback)
{
    _callbackMap[id] = std::make_pair(reciever, callback);
}

bool Caller::call(const SomeData data)
{
    auto reciever = _callbackMap.value(data.id).first;
    auto fn = _callbackMap.value(data.id).second;

    QMetaObject::invokeMethod(reciever, [reciever, fn, data]() {
        std::invoke(fn, data);
        fn(data);
        }, Qt::QueuedConnection);

    return true;
}

//main.cpp

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Caller caller;
    Receiver reciever;

    using std::placeholders::_1;

    caller.registerCallback(2, &reciever, &Receiver::someCallback);

    caller.call(SomeData({ "Hi2", 2 }));

    return a.exec();
}

另一种方法可能是使用合适的 std::function 来捕获回调,然后使用零超时的 QTimer::singleShot 在正确的上下文中调用回调。

struct SomeData {
    QString str;
    int     id;
};

class Caller {
public:
    using task = std::function<void(SomeData)>;

    void registerCallback (int id, QObject *receiver, task t)
    {
        _callbackMap[id] = std::make_pair(receiver, t);
    }
    bool call (SomeData data)
    {
        auto receiver = _callbackMap.value(data.id);
        QTimer::singleShot(0, receiver.first, [=](){ receiver.second(data); });
        return true;
    }
private:
    QMap<int, std::pair<QObject *, task>> _callbackMap;
};

class Receiver: public QObject {
public:
    void someCallback (SomeData data)
    {
        qDebug() << data.str;
    }
};

然后用作...

Caller caller;
Receiver receiver;

caller.registerCallback(1, &receiver, [&](SomeData d){ receiver.someCallback(d); });

caller.call(SomeData({ "Hi", 1 }));

此解决方案依赖于 std::invoke 和 lambda。

变体 1:直接使用 std::invoke 而不是 QMetaObject::invoke

变体 2:在传递给 QMetaObject::invoke

的 lambda 中使用 std::invoke

变体 3:在变体 2 中使用 MACRO 而不是 std::invoke。

如果您使用 QMetaObject::invoke,您可以选择连接类型 - 直接或排队。在变体 1 中,像在直接连接中一样立即调用调用。

receiver.h

#ifndef RECEIVER_H
#define RECEIVER_H

#include <QObject>
#include <QDebug>

struct SomeData {
    QString str;
    int id;
};
//Q_DECLARE_METATYPE(SomeData);

class Receiver : public QObject
{
    Q_OBJECT
public:
    explicit Receiver(QObject *parent = nullptr) : QObject(parent) {}

    void doSmth(SomeData data) {
        qDebug() << data.str;
    }

signals:

};

#endif // RECEIVER_H

caller.h

#ifndef CALLER_H
#define CALLER_H

#include <QObject>
#include <QMap>
#include <utility>
#include <map>

#include "receiver.h"
#define CALL_MEMBER_FN(object,ptrToMember)  ((object)->*(ptrToMember))
typedef void (Receiver::*callback)(SomeData);

class Caller : public QObject
{
    Q_OBJECT
public:
    explicit Caller(QObject *parent = nullptr) : QObject(parent) { }

    void registerCallback(int id, Receiver* receiver, callback c)
    {
        auto pair = std::make_pair(receiver, c);
        _callbackMap.emplace(id, pair);
    }

    bool call(const SomeData data)
    {
        auto &receiver = _callbackMap.at(data.id);
        return QMetaObject::invokeMethod(receiver.first, [data, receiver] () {
            // method 1
            std::invoke(receiver.second, receiver.first, data);
            // method 2 (better not to use a MACRO)
            CALL_MEMBER_FN(receiver.first, receiver.second)(data);
        }, Qt::QueuedConnection);
    }

    bool call_invoke(const SomeData data)
    {
        auto &receiver = _callbackMap.at(data.id);
        std::invoke(receiver.second, receiver.first, data);
        return true;
    }

signals:

private:
    std::map<int,std::pair<Receiver*,callback>> _callbackMap;
};

#endif // CALLER_H

main.cpp

#include <QCoreApplication>

#include "receiver.h"
#include "caller.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Caller caller;
    Receiver reciever;

    caller.registerCallback(1, &reciever, &Receiver::doSmth);
    caller.registerCallback(2, &reciever, &Receiver::doSmth);
    caller.call(SomeData({ "Hi", 1 }));
    caller.call_invoke(SomeData({ "Hi2", 2 }));

    return a.exec();
}