赞
踩
Qt Remote Objects 是从 Qt5.9 加入的 RPC 远程调用模块,详情可以从文档中了解:
https://doc.qt.io/qt-5/qtremoteobjects-index.html
从个人使用实践来看,相比其他流行的 RPC 框架,也有一些优势:
也存在一些问题:
QtRO 模块使用前先在 pro 文件加上 remoteobjects 启用该模块
QT += remoteobjects
有两种使用方式:1.Static Source,定义一个 rep 接口文件,分别生成服务端和客户端的接口定义;2.Dynamic Replica,服务端定义接口(用不用 rep 都可以),客户端连接上后动态获取元对象信息。
本文 Demo:
https://github.com/gongjianbo/MyTestCode/tree/master/Qt/QtRemoteObjects
基本使用流程为:写接口定义文件 rep,生成 replica(客户端) 和 source(服务端) 两套接口实现,在代码中使用生成的接口进行远程调用。
接口定义参考官方文档:
https://doc.qt.io/qt-5/qtremoteobjects-repc.html
属性、信号槽的定义类似 QObject 子类中的定义方式。
这里分享几个我遇到的问题:
下面是我的 simple.rep 接口定义文件和 Define.h 结构体头文件:
- //rep文档https://doc.qt.io/qt-5/qtremoteobjects-repc.html
- //引入结构体定义的头文件
- #include "Define.h"
-
- //POD,类似结构体,但是成员都是属性
- POD MyPod(QString name, int age, bool sex)
-
- //MODEL和QAbstractItemModel相关,太鸡肋,略去
-
- //类
- class MyObject{
- //属性
- PROP(int value)
- }
- //会生成两种接口
- //[类名Replica]给客户端
- //[类名Source]和[类名SimpleSource]给服务端
- class Interface{
- //属性
- PROP(QString name="gongjianbo" READONLY)
- PROP(QString tag)
- //使用rep中定义的类作为属性
- CLASS subObject(MyObject)
- //信号
- SIGNAL(dataChanged(const QString &data))
- //槽函数,省略返回值则为void
- SLOT(void setData(const QString &data))
- SLOT(QString getData())
- //枚举,不放在class里会自动再套一层QObject类不方便访问
- ENUM MyEnum{
- EnumA = 0,
- EnumB
- }
- SLOT(void testEnum(MyEnum t))
- //使用自定义的结构体
- SLOT(void testStruct(MyStruct t))
- }

- #pragma once
- #include <QObject>
- #include <QDataStream>
-
- //支持使用自定义结构体,但是只能以include的方式,不能直接在rep中定义
- //由于自动生成的代码需要比较和序列化,所以要重载一些函数
- struct MyStruct
- {
- QString name;
- int age;
- bool sex;
- };
- Q_DECLARE_METATYPE(MyStruct)
-
- bool operator==(const MyStruct &left, const MyStruct &right);
- bool operator!=(const MyStruct &left, const MyStruct &right);
- //重载输入输出运算符,以支持datastream序列化自定义结构
- QDataStream& operator <<(QDataStream& out, const MyStruct& item);
- QDataStream& operator >>(QDataStream& in, MyStruct& item);

定义好之后在接口客户端引入:
REPC_REPLICA += $$PWD/simple.rep
构建后会在输出目录生成 rep_simple_replica.h,里面有实现了接口的 QRemoteObjectReplica 子类。
在接口服务端引入:
REPC_SOURCE += $$PWD/simple.rep
构建后会在输出目录生成 rep_simple_source.h,里面有实现了部分接口(槽需要继承重写)的一个接口类。
当然也可以用 REPC_MERGED 引入,会同时生成客户端和服务端的两套接口。
生成接口类文件后,在客户端和服务端分别引入。服务端可能需要实现部分虚函数,客户端可以直接使用。
首先是客户端的基本操作(注意这个 Interface 是因为我 rep 接口定义取的名字叫 Interface):
- QRemoteObjectNode remoteNode;
- remoteNode.connectToNode(QUrl("local:qro_test"));
- InterfaceReplica *replica = remoteNode.acquire<InterfaceReplica>();
- //客户端可以判断连接与断开,服务端没有对应接口查询客户端列表
- //如果stateChanged直连槽函数调用接口,Qt5.15 mac上在拿返回值时会卡很久,可以使用QueuedConnection队列链接
- connect(replica,&InterfaceReplica::stateChanged,
- this,[this](QRemoteObjectReplica::State state, QRemoteObjectReplica::State oldState){
- },Qt::QueuedConnection);
- //连接服务端信号
- connect(replica,&InterfaceReplica::dataChanged,[]{});
- //调用服务端的接口
- if(replica->isReplicaValid())
- replica->setData("hello");
比较可惜的是客户端不能直接发信号,只能接收服务端过来的信号,以及调用槽函数。connetToNode 调用后如果服务端还没开,它会自动检测,也可以调用 replica 的 waitForSource 接口主动等。服务端重启后 node 会自动重连。
服务端的基本操作:
- QRemoteObjectHost host;
- MySource source; //继承生成的类并实现了部分虚函数
- host.setHostUrl(QUrl("local:qro_test"));
- host.enableRemoting(&source);
- //开启服务后可以发信号或者等被调用
- emit source.dataChanged("hello");
客户端可以判断连接与断开,服务端没有对应接口查询客户端列表,这点比较可惜。
基本使用流程为:source(服务端)定义好接口,replica(客户端)连接成功后通过宏形式的信号槽,或者 QMetaObject::invokeMethod 来调用服务端接口。
相对于 Static Source,主要区别在于客户端没法直接访问服务端接口了,只能通过字符串的形式来调用。而服务端原本 rep 生成的就只是个 QObject 的子类,我们自己定义都可以。
- QRemoteObjectNode remoteNode;
- remoteNode.connectToNode(QUrl("local:qro_test"));
- //acquire的name,和host.enableRemoting的name配对
- QRemoteObjectDynamicReplica *replica = remoteNode.acquireDynamic("QRO");
- //客户端可以判断连接与断开,服务端没有对应接口查询客户端列表
- //如果stateChanged直连槽函数调用接口,Qt5.15 mac上在拿返回值时会卡很久,可以使用QueuedConnection队列链接
- connect(replica,&InterfaceReplica::stateChanged,
- this,[this](QRemoteObjectReplica::State state, QRemoteObjectReplica::State oldState){
- },Qt::QueuedConnection);
- //连接服务端信号槽,需要在连接完成后
- connect(replica,&QRemoteObjectDynamicReplica::initialized,
- [](){
- connect(replica, SIGNAL(serverSignal(const QString &)), this, SLOT(clientSlot(const QString &)));
- connect(this, SIGNAL(clientSignal(const QString &)), replica, SLOT(serverSlot(const QString &)));
- });
- //调用服务端的接口
- if(replica->isReplicaValid())
- QMetaObject::invokeMethod(replica, "setData", Qt::DirectConnection, Q_ARG(QString, "hello"));

文档:https://doc.qt.io/qt-5/qtremoteobjects-gettingstarted.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。