本文主要是介绍二 Qt Remote Objects (REPC 编译器),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
REPC 概述
Replica Compiler ( repc )基于 API 定义文件生成QObject头文件。该文件(称为“rep”文件)使用特定的(文本)语法来描述 API。文件扩展名为 .rep,是 Replica 的缩写。当这些文件被repc 处理时,repc 会同时生成Source和Replica头文件。
Qt Remote Objects 模块还包括 qmake 宏(REPC_SOURCE,REPC_REPLICA),可以将它们添加到您的项目文件中以自动运行 repc。
IPC本质上就是调用远程进程的功能。怎么定义能够在QtRO中共享的功能类呢?
QtRO中的功能类没什么特殊的,其实就是个QObject派生类,所以任何派生自QObject的类型都能够分享到QtRO网络中。但是为了更好地和其他模块开发者协同,我推荐使用rep文件定义接口然后再实现的方式来编写功能类。
rep文件是一种DSL(Domain Specific Language),专门用于定义QtRO接口。在编译的时候,该文件会首先经过repc.exe这个程序处理,生成对应的头文件和源文件。只要安装Qt时选择了Qt RemoteObjects模块,repc.exe就在Qt安装目录的bin目录中。
使用Recp来定义接口有以下优点(对比Dyanmic Replica):
1 使用Dynamic Replica,API 在对象初始化之前是未知的,并且使用 C++ 中的 API 需要通过QMetaObject的方法进行字符串查找;
2 在编译时知道接口的任何问题;
3 rep 格式支持默认值,如果在实例化 Replica 时无法确保 Source 可用,这会很方便。
repc 文件格式
rep 文件格式是一种简单的域特定语言 (DSL),用于描述 Qt 远程对象 (QtRO) 上支持的接口。由于 QtRO 是一个基于对象的系统,这些接口由通过对象可用的 API 定义,即具有属性、信号和插槽的类。
class 类型
在 rep 文件中定义的每个类都成为生成的头文件中的QObject,并为您生成所描述的 API。
要定义一个类,请使用class关键字,后跟您想要的类型名称,然后将您的 API 括在括号中,如下所示
class MyType { //PROP/SIGNAL/SLOT/ENUM 声明来定义你的 API };
PROP
Q_PROPERTY元素是使用 rep 文件中的 PROP 关键字创建的。语法是PROP关键字后跟用引号括起来的定义,其中定义是类型、名称和(可选)默认值或属性。
PROP(bool simpleBool) // 名为 simpleBool 的布尔值
PROP(bool defaultFalseBool = false ) // 名为 defaultFalseBool 的布尔值,默认值为falsePROP( int lifeUniverseEverything = 42 ) // 默认为 42 的 int 值
PROP( QByteArray myBinaryInfo) // Qt 类型很好,可能需要 #include // 在您的 rep 文件中添加额外的头文件PROP( QString name CONSTANT) // 带有 CONSTANT 属性的属性
PROP( QString setable READWRITE) // 带有 READWRITE 属性的属性// 注意:属性默认为 READPUSH // (参见下面的描述)PROP(SomeOtherType myCustomType) // 自定义类型有效。需要#include 用于// 您的类型的 适当的标头,确保// 确保元对象// 系统知道您的类型,并确保它支持排队// 连接(参见Q_DECLARE_METATYPE 和// qRegisterMetaType)
默认情况下,属性将定义 getter 和“推送”插槽,以及在值更改时发出的通知信号。Qt Remote Objects 需要 Source 对象上的 notify 信号来触发向附加副本发送更新。在 QtRO 的早期版本中,属性默认为读/写,即具有 getter 和 setter。然而,由于 QtRO 的异步特性,这有时会导致不直观的行为。在 PROP 上设置 READWRITE 属性将提供旧的(getter 和 setter)行为。
// 在 .rep 文件中,旧的(setter)行为
PROP ( int myVal READWRITE ) // 使用 setMyVal(int myVal) 方法的旧行为
// 在代码中...假设 myVal 在 Source 中初始设置为 0
int originalValue = rep - > myVal (); // 将是 0
rep - > setMyVal ( 10 ); // 调用 setter,期待阻塞// 非异步返回
如果( rep- > myVal ( ) == 10 ) 。. . // 测试通常会失败
如果需要在值更改之前进行阻塞,则需要执行以下操作。
// 在 .rep 文件中,旧的(setter)行为
PROP ( int myVal READWRITE ) // 使用 setMyVal(int myVal) 方法的旧行为 // 在代码中...假设 myVal 在 Source 中初始设置为 0
bool originalValue = rep - > myVal (); // 将为 0// 我们可以使用 QSignalSpy 等待变化
QSignalSpy spy ( rep , SIGNAL ( myValChanged ( int )));
rep- > setMyVal (10);// 调用 setter,期待阻塞// 非异步返回
spy.wait() //阻塞,直到收到更改的信号if ( rep - > myVal () == 10 ) 。. . // 假设测试将成功 // 1. 源对象已连接 //2. 没有其他人(源或其他副本) 将 myVal 设置为其他东西(竞争条件)而不是使用 QSignalSpy,//事件驱动的做法是将 myValChanged 通知信号连接到响应更改的 Slot。
QtRO 现在默认为 READPUSH,它提供了一个自动生成的 Slot 用于请求属性更改。
// In .rep file, defaults to READPUSHPROP(bool myVal) // No setMyVal(int myVal) on Replica, has// pushMyVal(int myVal) instead// In code... Assume myVal is initially set to 0 in Sourcebool originalValue = rep->myVal(); // Will be 0// We can wait for the change using \l QSignalSpyQSignalSpy spy(rep, SIGNAL(myValChanged(int)));rep->pushMyVal(10); // Call push method, no expectation that change// is applied upon method completion.// Some way of waiting for change to be received by the Replica is still necessary,// but hopefully not a surprise with the new pushMyVal() Slot.spy.wait(); // spy.wait() blocks until changed signal// is receivedif (rep->myVal() == 10) ... // Test will succeed assuming// 1. Source object is connected// 2. Nobody else (Source or other Replica)// set the myVal to something else (race// condition)
您还可以在 PROP 声明中使用CONSTANT、READONLY、PERSISTED、READWRITE或READPUSH关键字,这会影响其实现方式。如果未使用任何值,则 READPUSH 是默认值。
PROP ( int lifeUniverseEverything = 42 CONSTANT )
PROP ( QString name READONLY )
请注意这里有一些微妙之处。CONSTANT PROP 在 SOURCE 端有一个Q_PROPERTY声明为 CONSTANT。但是,副本在初始化之前无法知道正确的值,这意味着必须允许在初始化期间更改属性值。对于 READONLY,Source 既没有设置器也没有推送槽,副本端不会生成推送槽。将 PERSISTED 特征添加到 PROP 将使 PROP 使用节点上设置的QRemoteObjectPersistedStore实例(如果有)来保存/恢复 PROP 值。
信号
信号方法是使用 rep 文件中的 SIGNAL 关键字创建的。
用法是声明SIGNAL后跟括在括号中的所需签名。跳过 void 返回值。
SIGNAL ( test ())
SIGNAL ( test ( QString foo , int bar ))
SIGNAL ( test ( QMap < QString , int > foo ))
SIGNAL ( test ( const QString & foo ))
SIGNAL ( test ( QString & foo ))
就像在 Qt queued connections中一样,作为引用的信号中的参数在传递给副本时将被复制。
SLOT
槽方法是使用 rep 文件中的 SLOT 关键字创建的。
用法是声明SLOT后跟括在括号中的所需签名。返回值可以包含在声明中。如果跳过返回值,将在生成的文件中使用 void。
SLOT(test())SLOT(void test(QString foo, int bar))SLOT(test(QMap<QString,int> foo))SLOT(test(QMap<QString,int> foo, QMap<QString,int> bar))SLOT(test(QMap<QList<QString>,int> foo))SLOT(test(const QString &foo))SLOT(test(QString &foo))SLOT(test(const QMap<QList<QString>,int> &foo))SLOT(test(const QString &foo, int bar))
就像在 Qt队列连接和 QtRO SIGNALS 中一样,作为引用的槽中的参数在传递给副本时将被复制。
枚举
枚举(在 QtRO 中使用 C++ 枚举和 Qt 的Q_ENUM的组合)使用 ENUM 关键字进行描述。
ENUM MyEnum {Foo}ENUM MyEnum {Foo, Bar}ENUM MyEnum {Foo, Bar = -1}ENUM MyEnum {Foo=-1, Bar}ENUM MyEnum {Foo=0xf, Bar}ENUM MyEnum {Foo=1, Bar=3, Bas=5}
POD 类型
Plain Old Data (POD) 是一个描述简单数据集合的术语,类似于 C++ 结构。例如,如果您有一个电话簿 API,您可能希望在其界面中使用“地址”的概念(其中地址可能包括街道、城市、州、国家和邮政编码)。您可以使用 POD 关键字来定义这样的对象,然后可以在类定义中的 PROP/SIGNAL/SLOT 定义中使用这些对象。
用法是声明POD后跟生成类型的名称,后跟以逗号分隔的类型和名称对,其中类型/名称对包含在括号中。
POD Foo(int bar)POD Foo(int bar, double bas)POD Foo(QMap<QString,int> bar)POD Foo(QList<QString> bar)POD Foo(QMap<QString,int> bar, QMap<double,int> bas)
一个完整的例子:
POD Foo(QList<QString> bar)class MyType{SIGNAL(sendCustom(Foo foo));};
由repc 生成的代码为每个POD 创建一个Q_GADGET类,并为POD 定义的每种类型对应的Q_PROPERTY成员。
枚举类型
在类中定义 ENUM 通常更容易和更清晰(参见ENUM),但如果您需要独立的枚举类型,在类定义之外使用 ENUM 关键字可能会有所帮助。这将在您的头文件中生成一个处理编组等的新类。语法与ENUM相同,除了这种情况下的声明不包含在class声明中。
USE_ENUM
USE_ENUM 关键字是在通过 ENUM 关键字自动生成之前实现的。保留它是为了向后兼容。
指令
rep 文件定义了一个接口,但接口通常需要外部元素。为了支持这一点,repc 将在生成的文件的顶部包含任何(单行)指令。例如,这允许您使用支持所需逻辑或数据类型的#include 或#define 指令。
repc 工具目前只包含从“#”符号到行尾的所有内容,并将其添加到生成的文件中。所以不支持多行#if/#else/#endif 语句和多行宏。
项目文件宏
REPC_REPLICA
...REPC_SOURCE
...QOBJECT_REPLICA
这篇关于二 Qt Remote Objects (REPC 编译器)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!