2.2 消息总线中的弱类型处理(QVariant)

        本文是 《基于 Qt 实现消息总线》的其中一节,建议全章阅读。


        弱类型能够帮助我们进一步减少组件之间通信的耦合性,特别在编译链接阶段,我们并不需要包含(include)定义消息的头文件,也不需要链接(link)实现消息的链接库。

        在有些场景下,弱类型是必须的。比如在浏览器 js 中对接消息总线,就不可能包含头文件。

弱类型消息

        对于弱类型消息,除了消息类型是 QVariant 外,基本的订阅、发布实现与强类型消息是一样的,都是基于 Qt 的信号-槽机制。

        强类型消息通过消息的类型来关联发布和订阅,弱类型消息则必须有一个规定好的主题(字符串类型)。所以弱类型消息订阅、发布都会带上一个主题信息。

class QSimpleMessage : public QMessageBase
{
public:
    QSimpleMessage(QByteArray const & topic);
    typedef std::function<QVariant (QByteArray const & topic, QVariant const & message)> observ_t;
    virtual bool subscribe(QObject const * c, observ_t o);
    virtual bool unsubscribe(QObject const * c);
    virtual void publish(QVariant const & msg);
};

弱化强类型消息

        强类型消息也可以作为弱类型消息使用,只要满足下列条件:

  •  必须提前定义好一个主题。
  •  需要准备强弱类型之间的转换。这是通过 QMetaType 来实现的。

        作为一个例子,有这样一个消息结构:

struct TestMessage
{
    int i;
    int j;
};

       首先,需要在 QMetaType 类型系统中通过 Q_DECLARE_METATYPE 注册这个类型:

Q_DECLARE_METATYPE(TestMessage)

        有了上面的注册,我们就可以用 QVariant 封装,或者从 QVariant 解封装 TestMessage 对象:

template<typename F>
static QVariant QMessage<T>::toVar(T const & msg)
{
    return QVariant::fromValue(msg);
}

template<typename F>
static T QMessage<T>::fromVar(T const & msg)
{
    return msg.value<T>();
}

        但是,要作为弱类型使用,还需要定义并注册与弱类型之间的转换函数。弱类型可以选择 QVariantList 或者 QVariantMap 或者其他,具体用什么视情况而定,也可能需要同时注册多种类型的转换。作为一个例子,TestMessage 与 QVariantList 的转换可以这样实现(同时演示了注册方法 registerConverter):

QMetaType::registerConverter<TestMessage, QVariantList>([](auto & m) {
    return QVariantList{m.i, m.j};
});
QMetaType::registerConverter<QVariantList, TestMessage>([](auto & l) {
    return TestMessage{l[0].toInt(), l[1].toInt()};
});

        这样,我们在发布 TestMessage 消息时,可以用下面这种弱类型的方式(主题为 "test"):

bus.publish("test", QVariantList{1, 2});

        在订阅消息时,也可以用下面这样的弱类型方式:

bus.subscribe("test", [](QByteArray topic, QVariant message) {
    qDebug() << "test" << topic << message.toList();
});

        在上面两种场合下,都不需要强依赖 TestMessage 类型(对编译器是不可见的)。

绑定弱类型消息

        处理弱类型总不是那么方便,通常将弱类型消息转换为 QVariantList,然后拆解为接收方法的一系列参数。我们用一个工具类(QSubscriber)来实现这个过程。先看看如何使用:

class TestReceiver : public QObject
{
public slots:
    QString test(int i, QString s)
    {
        qDebug() << "TestReceiver" << i << s;
        return s + i;
    }
};


TestReceiver receiver;
bus.subscribe("test_topic", QSubscriber(&receiver, SLOT(test(int,QString))));
bus.subscribe("test_topic", QSubscriber(&receiver, &TestReceiver::test));

        在这个例子里面,TestMessage 的两个 int 属性被拆解为接收方法的两个参数,一个是 int 类型,一个是 QString 类型,工具类可以自动完成类型转换。

        这是如何实现的呢?

        我们先看看 SLOT 方式的实现(省略了部分中间过程):

QVariant QSubscriber::invoke(QObject *receiver, const QByteArray &target, QVariant args)
{
    QMetaObject const & meta = *receiver->metaObject();
    int index = meta.indexOfSlot(target);
    if (index < 0) {
        return QVariant();
    }
    QMetaMethod method = meta.method(index);
    if (method.parameterCount() >= 4)
        return QVariant();
    QGenericArgument argv[4];
    QVariant varg[4];
    QVariantList list;
    if (args.canConvert(qMetaTypeId<QVariantList>())
        && args.convert(qMetaTypeId<QVariantList>())) {
        list = args.toList();
    } else {
        list.append(args);
    }
    for (int i = 0; i < method.parameterCount(); ++i) {
        if (i < list.size())
            varg[i] = list[i];
        int t = method.parameterType(i);
        if (!varg[i].canConvert(t))
            return QVariant();
        if (!varg[i].convert(t))
            return QVariant();
        argv[i] = QGenericArgument(QMetaType::typeName(t), varg[i].data());
    }
    QVariant result;
    QGenericReturnArgument rtarg(QMetaType::typeName(method.returnType()), result.data());
    method.invoke(receiver, rtarg, argv[0], argv[1], argv[2], argv[3]);
    return result;
}

        可以看出,关键部分是 QMetaMethod 和 QVariant 的使用。通过 QMetaMethod 可以反射出方法的所有参数类型,然后将 QVariantList 的每一项(QVariant)转换为对应参数的类型,再调用 QMetaMethod::invoke 就可以跳转到处理消息的 SLOT 了。

        至于泛型函数 Func 形式的实现,与 SLOT 方式的区别在于,它是通过 C++ 泛型模板参数获得函数的各个参数类型:

int t = qMetaTypeId<Arg>();
if (!arg.canConvert(t))
    return false;
if (!arg.convert(t))
    return false;

        C++泛型处理是比较复杂的,我们不准备详细分析,有兴趣的读者可以阅读源代码。

        至此,我们完成了弱类型的大部分处理逻辑,唯一缺少的是弱类型的结果反馈,这将在下一章节中介绍。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值