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();
}
我正在为我们正在使用的 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();
}