Qt 远程对象编译器

Qt远程对象编译器生成SourceReplica头文件

REPC 概述

Replica Compiler (repc) 根据API定义文件生成QObject头文件。该文件(称为“rep”文件)使用特定的(文本)语法来描述API。按照惯例,这些文件以.rep为扩展名,代表Replica。当这些文件被repc处理时,repc会生成SourceReplica头文件。

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声明中使用CONSTANTREADONLYPERSISTEDREADWRITEREADPUSHSOURCEONLYSETTER关键字,这些关键字会影响属性的实现方式。如果没有使用任何值,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或push方法。请注意,这不会影响指向类型的属性的行为,只会影响更改指针本身的能力。

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

相关主题:ENUMUSE_ENUM 关键字

USE_ENUM 关键字

USE_ENUM 关键字是在通过 ENUM 关键字添加自动生成功能之前实现的。它被保留以保持向后兼容性。

相关主题: ENUM , ENUM类型

指令

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

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

CMake 函数

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

qt_add_repc_replicas

从Qt Remote Objects的.rep文件创建副本类型的C++头文件。

qt_add_repc_sources

从Qt Remote Objects的.rep文件为源类型创建C++头文件。

qt_add_repc_merged

从Qt Remote Objects的.rep文件创建源和副本类型的C++头文件。

qt_reps_from_headers

从QObject头文件创建.rep文件。

qmake 变量

REPC_REPLICA

指定项目中所有用于生成副本头文件的rep文件的名称。

例如:

REPC_REPLICA = media.rep \
               location.rep

生成的文件将具有以下形式 rep_ file base>_replica.h

REPC_SOURCE

指定项目中所有用于生成源头文件的rep文件的名称。

例如:

REPC_SOURCE = media.rep \
              location.rep

生成的文件将具有以下形式 rep_ file base>_source.h

REPC_MERGED

指定项目中所有应用于生成组合(源和副本)头文件的rep文件的名称。

例如:

REPC_MERGED = media.rep \
              location.rep

生成的文件将具有以下形式:rep_ file base>_merged.h

注意

通常源和副本位于不同的进程或设备中,因此这个变量不常用。

QOBJECT_REP

指定应使用的现有QObject头文件的名称,以生成相应的.rep文件。