Qt 远程对象编译器¶
REPC 概述¶
Replica Compiler (repc) 根据API定义文件生成QObject头文件。该文件(称为“rep”文件)使用特定的(文本)语法来描述API。按照惯例,这些文件以.rep为扩展名,代表Replica。当这些文件被repc处理时,repc会生成Source和Replica头文件。
Qt Remote Objects 模块还包括 CMake 函数 和 qmake 变量,可以将它们添加到您的项目文件中,以自动运行 repc,并将生成的文件添加到构建过程中由 Meta Object Compiler 处理的文件列表中,使在您的项目中使用 Qt Remote Objects 变得简单。
虽然Qt Remote Objects支持通过网络共享任何QObject(在源端使用enableRemoting,在副本端使用acquireDynamic),但让repc定义您的对象有几个优势。首先,虽然DynamicReplicas很有用,但它们使用起来更麻烦。API在对象初始化之前是未知的,并且从C++使用API需要通过QMetaObject的方法进行字符串查找。其次,在编译时知道接口可以在编译时而不是运行时发现任何问题。第三,rep格式支持默认值,如果您无法确保在实例化副本时源可用,这可能会很方便。
请参阅文档这里了解如何在代码中使用生成的文件。这里我们将重点介绍repc格式和选项。
rep文件格式¶
rep文件格式是一种简单的领域特定语言(DSL),用于描述通过Qt远程对象(QtRO)支持的接口。由于QtRO是一个基于对象的系统,这些接口由通过对象可用的API定义,即具有属性、信号和槽的类。
类类型¶
在rep文件中定义的每个类在生成的头文件中都成为一个QObject,并为您生成所描述的API。
要定义一个类,请使用class关键字,后跟您想要的类型名称,然后将您的API括在括号中,如下所示
class MyType { //PROP/CLASS/MODEL/SIGNAL/SLOT/ENUM declarations to define your API };
在库中使用生成的头文件时,可能需要定义类属性以设置符号可见性。这可以通过在class关键字后定义属性来实现,类似于C++。在以下示例中,使用了MYSHAREDLIB_EXPORT宏,该宏在"mysharedlib_global.h"中定义。有关此工作原理的更多信息,请参见创建共享库。
#include "mysharedlib_global.h" class MYSHAREDLIB_EXPORT MyType { ... };
属性¶
Q_PROPERTY 元素通过在 rep 文件中使用 PROP 关键字创建。语法是 PROP 关键字后跟括号内的定义,其中定义包括类型、名称以及(可选的)默认值或属性。
PROP(bool simpleBool) // boolean named simpleBool PROP(bool defaultFalseBool=false) // boolean named defaultFalseBool, with false // as the default value PROP(int lifeUniverseEverything=42) // int value that defaults to 42 PROP(QByteArray myBinaryInfo) // Qt types are fine, may need #include // additional headers in your rep file PROP(QString name CONSTANT) // Property with the CONSTANT attribute PROP(QString setable READWRITE) // Property with the READWRITE attribute // note: Properties default to READPUSH // (see description below) PROP(SomeOtherType myCustomType) // Custom types work. Needs #include for the // appropriate header for your type, make // sure your type is known to the metabject // system, and make sure it supports Queued // Connections (see Q_DECLARE_METATYPE and // qRegisterMetaType)
有关创建自定义类型的更多信息可以在这里找到。
默认情况下,属性将具有getter和一个“push”槽定义,以及在值更改时发出的通知信号。Qt Remote Objects 需要在源对象上发出通知信号,以触发向附加的副本发送更新。在早期版本的QtRO中,属性默认为可读/写,即具有getter和setter。然而,由于QtRO的异步特性,这有时会导致不直观的行为。在PROP上设置READWRITE属性将提供旧的(getter和setter)行为。
// In .rep file, old (setter) behavior PROP(int myVal READWRITE) // Old behavior with setMyVal(int myVal) method // In code... Assume myVal is initially set to 0 in Source int originalValue = rep->myVal(); // Will be 0 rep->setMyVal(10); // Call setter, expecting a blocking/ // non-asynchronous return if (rep->myVal() == 10) ... // Test will usually fail
如果需要阻塞直到值发生变化,则需要类似以下的内容。
// In .rep file, old (setter) behavior PROP(int myVal READWRITE) // Old behavior with setMyVal(int myVal) method // 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->setMyVal(10); // Call setter, expecting a blocking/ // non-asynchronous return 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) // sets the myVal to something else (race // condition) // Rather than use QSignalSpy, the event-driven practice would be to connect the // myValChanged notify signal to a slot that responds to the changes.
QtRO 现在默认使用 READPUSH,它提供了一个自动生成的槽函数,用于请求属性更改。
// 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)
你也可以在PROP声明中使用CONSTANT、READONLY、PERSISTED、READWRITE、READPUSH或SOURCEONLYSETTER关键字,这些关键字会影响属性的实现方式。如果没有使用任何值,READPUSH是默认值。
PROP(int lifeUniverseEverything=42 CONSTANT) PROP(QString name READONLY)
请注意这里有一些微妙之处。一个 CONSTANT PROP 在 SOURCE 端声明为 CONSTANT 的 Q_PROPERTY。然而,副本在初始化之前无法知道正确的值,这意味着在初始化期间必须允许属性值发生变化。对于 READONLY,源端既不会有 setter 也不会有 push slot,副本端也不会生成 push slot。将 PERSISTED 特性添加到 PROP 将使 PROP 使用在 Node 上设置的 QRemoteObjectAbstractPersistedStore 实例(如果有)来保存/恢复 PROP 值。
另一个微妙的值是SOURCEONLYSETTER,它提供了另一种指定非对称行为的方式,其中Source(特别是辅助类SimpleSource)将具有属性的公共getter和setter,但在Replica端将是只读的(带有通知信号)。因此,属性可以完全从Source端控制,但只能从Replica端观察。SOURCEONLYSETTER是repc用于MODEL和CLASS实例的模式,意味着Source可以更改指向的对象,但Replica不能提供新对象,因为没有生成set
类¶
CLASS 关键字为从 QObject 派生的对象生成特殊的 Q_PROPERTY 元素。这些属性具有与 SOURCEONLYSETTER 相同的语义。语法是 CLASS 关键字后跟属性名称,然后是括号内的子对象类型。
// In .rep file class OtherClass { PROP(int value) } class MainClass { CLASS subObject(OtherClass) }
模型¶
MODEL 关键字为从 QAbstractItemModel 派生的对象生成特殊的 Q_PROPERTY 元素。这些属性具有与 SOURCEONLYSETTER 相同的语义。语法是 MODEL 关键字后跟属性名称,然后是括号括起来的(逗号分隔的)角色,这些角色应暴露给副本。
// In .rep file class CdClass { PROP(QString title READONLY) MODEL tracks(title, artist, length) }
信号¶
信号方法是通过在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队列连接中一样,信号中的引用参数在传递给副本时将被复制。
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信号中一样,当参数传递给Replicas时,槽函数中的引用参数将被复制。
枚举¶
枚举(在QtRO中使用C++的enum和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}
相关主题: ENUM 类型 , USE_ENUM 关键字
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成员。
在库中使用生成的头文件时,可能需要定义类属性以设置符号可见性。这可以通过在POD关键字后定义属性来完成。在以下示例中,使用了MYSHAREDLIB_EXPORT宏,该宏在"mysharedlib_global.h"中定义。有关此工作原理的更多信息,请参见创建共享库。
#include "mysharedlib_global.h" POD MYSHAREDLIB_EXPORT Foo(int bar)
ENUM类型¶
在类内部定义ENUM通常更简单和清晰(参见ENUM),但如果你需要一个独立的枚举类型,在类定义之外使用ENUM关键字可能会很有帮助。这将在你的头文件中生成一个新的类,用于处理编组等操作。语法与ENUM相同,唯一的区别是这种情况下的声明不包含在class声明中。
相关主题:ENUM,USE_ENUM 关键字
USE_ENUM 关键字¶
USE_ENUM 关键字是在通过 ENUM 关键字添加自动生成功能之前实现的。它被保留以保持向后兼容性。
指令¶
rep 文件定义了一个接口,但接口通常需要外部元素。为了支持这一点,repc 将在生成文件的顶部包含任何(单行)指令。这使您能够使用 #include 或 #define 指令来支持所需的逻辑或数据类型。
repc工具目前会忽略从“#”符号到行尾的所有内容,并将其添加到生成的文件中。因此,不支持多行的#if/#else/#endif语句和多行的宏。
CMake 函数¶
下面列出了用于生成源和副本类型的CMake函数。
从Qt Remote Objects的.rep文件创建副本类型的C++头文件。
从Qt Remote Objects的.rep文件为源类型创建C++头文件。
从Qt Remote Objects的.rep文件创建源和副本类型的C++头文件。
从QObject头文件创建.rep文件。
qmake 变量¶
REPC_REPLICA¶
指定项目中所有用于生成副本头文件的rep文件的名称。
例如:
REPC_REPLICA = media.rep \ location.rep
生成的文件将具有以下形式 rep_。
REPC_SOURCE¶
指定项目中所有用于生成源头文件的rep文件的名称。
例如:
REPC_SOURCE = media.rep \ location.rep
生成的文件将具有以下形式 rep_。
REPC_MERGED¶
指定项目中所有应用于生成组合(源和副本)头文件的rep文件的名称。
例如:
REPC_MERGED = media.rep \ location.rep
生成的文件将具有以下形式:rep_。
注意
通常源和副本位于不同的进程或设备中,因此这个变量不常用。
QOBJECT_REP¶
指定应使用的现有QObject头文件的名称,以生成相应的.rep文件。