当前位置:   article > 正文

二 Qt Remote Objects (REPC 编译器)

qt remote objects

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 }; 
  • 1

PROP

Q_PROPERTY元素是使用 rep 文件中的 PROP 关键字创建的。语法是PROP关键字后跟用引号括起来的定义,其中定义是类型、名称和(可选)默认值或属性。

PROP(bool simpleBool)                 // 名为 simpleBool 的布尔值
PROP(bool defaultFalseBool = false )     // 名为 defaultFalseBool 的布尔值
                                     ,默认值为false

PROP( 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

默认情况下,属性将定义 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 ). . // 测试通常会失败     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果需要在值更改之前进行阻塞,则需要执行以下操作。

// 在 .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。  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

QtRO 现在默认为 READPUSH,它提供了一个自动生成的 Slot 用于请求属性更改。

// In .rep file, defaults to READPUSH
  PROP(bool myVal)                      // No setMyVal(int myVal) on Replica, has
                                        // pushMyVal(int myVal) instead

  // In code...  Assume myVal is initially set to 0 in Source
  bool originalValue = rep->myVal();    // Will be 0

  // We can wait for the change using \l QSignalSpy
  QSignalSpy 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 received
  if (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)
                                   
                                  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

您还可以在 PROP 声明中使用CONSTANT、READONLY、PERSISTED、READWRITE或READPUSH关键字,这会影响其实现方式。如果未使用任何值,则 READPUSH 是默认值。

PROP ( int lifeUniverseEverything = 42 CONSTANT ) 
PROP ( QString name READONLY )
  • 1
  • 2

请注意这里有一些微妙之处。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 ))    
  • 1
  • 2
  • 3
  • 4
  • 5

就像在 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))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

就像在 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}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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)
  • 1
  • 2
  • 3
  • 4
  • 5

一个完整的例子:

POD Foo(QList<QString> bar)
  class MyType
  {
      SIGNAL(sendCustom(Foo foo));
  };
  • 1
  • 2
  • 3
  • 4
  • 5

由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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/184739
推荐阅读
相关标签
  

闽ICP备14008679号