Qt 远程对象编译器#

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声明。

相关主题:ENUMUSE_ENUM关键字

USE_ENUM关键字#

USE_ENUM关键字在通过ENUM关键字自动生成之前就已经实现。它保留为了与旧版本保持兼容。

相关主题:ENUMENUM类型

指令#

rep文件定义了一个接口,但接口通常需要外部元素。为了支持这一点,repc会在生成的文件顶部包含任何(单行)指令。这允许你使用#include或#define指令来支持所需的逻辑或数据类型。

repc工具目前忽略从“#”符号到行尾的所有内容,将其添加到生成的文件中。所以不支持多行#if/#else/#endif语句和多行宏。

CMake函数#

以下列出用于生成源类型和副本类型的CMake函数。

qt_add_repc_replicas

从Qt远程对象.rep文件创建副本类型的C++头文件。

qt_add_repc_sources

从Qt远程对象.repx文件创建源类型C++头文件。

qt_add_repc_merged

从Qt远程对象.repx文件创建源类型和副本类型C++头文件。

qt_reps_from_headers

从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头文件名称。