Qt 远程对象编译器#
REPC 概述#
复制编译器(repc)基于 API 定义文件生成 QObject 头文件。该文件(称为“rep”文件)使用特定(文本)语法来描述 API。按照惯例,这些文件以 .rep 为扩展名,代表副本。当这些文件由 repc 处理时,repc 将生成源和副本头文件。
Qt 远程对象模块还包括可以在项目文件中添加的 CMake 函数 和 qmake 变量,这些函数和变量可以自动运行 repc,并在构建过程中添加由元对象编译器处理的文件列表,从而使你在项目中使用 Qt 远程对象变得更加简单。
虽然 Qt 远程对象支持在网络中共享任何 QObject(在源端使用 enableRemoting,在副本端使用 acquireDynamic),但使用 repc 定义对象仍然有一些优势。首先,虽然 DynamicReplicas
很有用,但它们更繁琐。API 直到对象初始化后才知道,并且从 C++ 使用 API 需要通过 QMetaObject 的方法进行字符串查找。其次,在编译时知道接口,可以在编译时和运行时找到问题。第三,rep 格式支持默认值,如果你无法确保在实例化副本时源可用,这会很有用。
有关如何在您的代码中使用生成的文件的信息,请参阅 此处 的文档。在这里,我们将关注 repc 格式和选项。
rep 文件格式#
rep 文件格式是用于描述 Qt 远程对象 (QtRO) 支持的接口的简单 领域特定语言 (DSL)。由于 QtRO 是一个基于对象的系统,因此这些接口通过对象、即具有属性、信号和槽的类定义的 API 进行定义。
类类型#
在 rep 文件中定义的每个类都将成为生成的头文件中的 QObject,并为您的 API 生成描述。
要定义一个类,请使用 class
关键字,后跟您的类型名称,然后在括号中包含您的 API,如下所示
class MyType { //PROP/CLASS/MODEL/SIGNAL/SLOT/ENUM declarations to define your API };
在使用库内生成的头文件时,可能需要定义类属性以设置符号可见性。这可以像C++一样,在`class`关键字之后定义属性来完成。在以下示例中,使用了`MYSHAREDLIB_EXPORT`宏,该宏在`"mysharedlib_global.h"`中定义。有关更多信息,请参阅“创建共享库”。
#include "mysharedlib_global.h" class MYSHAREDLIB_EXPORT MyType { ... };
PROP
通过在rep文件中使用`PROP`关键字创建`Q_PROPERTY`元素。语法是`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)
有关创建自定义类型的更多信息,请在此处查找。
默认情况下,属性将具有获取器以及一个“push”槽定义,以及当值更改时发出的通知信号。Qt远程对象需要在源对象上触发发送到附加副本的更新的通知信号。在QtRO的较早版本中,属性默认为读写,即具有获取器和设置器。然而,由于QtRO的异步性质,这有时会导致不直观的行为。在`PROP`上设置`READWRITE`属性将提供旧的(获取器和设置器)行为。
// 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`在源端将`Q_PROPERTY`声明为`CONSTANT`。然而,副本在初始化之前无法知道正确的值,这意味着属性值必须在初始化期间允许更改。对于`READONLY`,源将没有设置器或`push`槽,副本侧也不会生成`push`槽。将`PERSISTED`特性添加到`PROP`将使`PROP`使用设置在任何节点上的`QRemoteObjectAbstractPersistedStore`实例(如果有)以保存/恢复`PROP`值。
还有一个细微的含义值SOURCEONLYSETTER,它提供了一种指定非对称行为的另一种方式,在此方式中,源(特别是辅助类,SimpleSource
)将为该属性提供公共的获取器和设置器,但是在副本侧将会是只读(带有通知信号)。因此,该属性可以从源端完全控制,但只能在副本端观察。SOURCEONLYSETTER是repc用于MODEL和CLASS实例的模式,这意味着源可以更改指向的对象,但不能提供新的对象,因为没有生成set< Prop>或push< Prop>方法。注意,这不会影响指向的类型的属性的行为,只会影响更改指针的能力。
CLASS#
CLASS关键字为QObject派生的对象生成特殊的Q_PROPERTY元素。这些属性与SOURCEONLYSETTER具有相同的语义。语法是CLASS关键字后面跟属性名,然后是括号内封装的子对象类型。
// In .rep file class OtherClass { PROP(int value) } class MainClass { CLASS subObject(OtherClass) }
MODEL#
MODEL关键字为QAbstractItemModel派生的对象生成特殊的Q_PROPERTY元素。这些属性与SOURCEONLYSETTER具有相同的语义。语法是MODEL关键字后面跟属性名,然后是括号,括号中包含应暴露给副本的角色(以逗号分隔)。
// In .rep file class CdClass { PROP(QString title READONLY) MODEL tracks(title, artist, length) }
SIGNAL#
信号方法是通过在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 SIGNALS中一样,在槽中的引用参数在传递给副本时会进行复制。
ENUM#
枚举(在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}
相关主题:ENUM类型,USE_ENUM关键字
POD类型#
普通旧数据(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远程对象.rep文件创建副本类型的C++头文件。
从Qt远程对象.repx文件创建源类型C++头文件。
从Qt远程对象.repx文件创建源类型和副本类型C++头文件。
从QObject头文件创建.repx文件。
qmake变量#
REPC_REPLICA#
指定项目中所有应用于生成副本头文件的所有.repx文件名称。
例如
REPC_REPLICA = media.rep \ location.rep
生成的文件将具有如下格式:rep_<replica_file_base>_replica.h
。
REPC_SOURCE#
指定项目中所有应用于生成源头文件的所有.repx文件名称。
例如
REPC_SOURCE = media.rep \ location.rep
生成的文件将具有如下格式:rep_<replica_file_base>_source.h
。
REPC_MERGED#
指定项目中所有应用于生成组合(源和副本)头文件的所有.repx文件名称。
例如
REPC_MERGED = media.rep \ location.rep
生成的文件将具有如下格式:rep_<replica_file_base>_merged.h
。
注意
通常,源和副本位于不同的进程或设备中,因此此变量不常用。
QOBJECT_REP#
指定应使用于生成相应.repx文件的所有现有QObject头文件名称。