Qt远程对象编译器

REPC概述

Replica (Rep) 编译器(repc)根据API定义文件生成QObject头文件。该文件(称为“rep”文件)使用特定的(文本)语法来描述API。按照惯例,这些文件以 reps 为扩展名,即复制品。当这些文件由repc处理时,repc会同时生成 Source 和 Replica 头文件。

Qt远程对象模块还包括CMake函数和qmake变量,可以将它们添加到项目文件中以自动运行repc,并在构建过程中将输出文件添加到MOC(元对象编译器)处理的文件列表中,使在项目中使用Qt远程对象变得简单。

虽然Qt远程对象支持通过网络(在源端使用enableRemoting,在副本端使用acquireDynamic)共享任何QObject,但让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)

有关创建自定义类型的更多信息,请参阅这里

默认情况下,属性将定义getter和一个"push"插槽,以及在值更改时发出notify信号。Qt远程对象需要源对象的notify信号来触发发送更新到附加的Replicas。在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在源端声明为CONSTANT的Q_PROPERTY。但是,副本无法在初始化之前知道正确值,这意味着必须在初始化期间允许属性值改变。对于READONLY,源将不会有setter和push插槽,副本端也不会生成push插槽。将PERSISTED特性添加到PROP会使PROP使用在节点(如果有)上设置的QRemoteObjectAbstractPersistedStore实例来保存/恢复PROP值。

另一个细微的值是SOURCEONLYSETTER,它提供了另一种指定非对称行为的方式,其中源(特别是辅助类SimpleSource)将具有属性的公共getter和setter,但在副本端将是ReadOnly(带有notify信号)。因此,属性可以从源端完全控制,但从副本端只能观察。SOURCEONLYSETTER是repc用于MODEL和CLASS实例的模式,这意味着源可以更改指向的对象,但副本不能提供新的对象,因为没有生成set或push方法。注意,这不会影响指向的对象的属性的语义,只是能否改变指针本身。

CLASS

CLASS关键字为继承自QObject的对象生成特殊的Q_PROPERTY元素。这些属性具有与SOURCEONLYSETTER相同的语义。语法是CLASS关键字后跟属性名称,然后是圆括号中包含的子对象类型。

// In .rep file
class OtherClass
{
    PROP(int value)
}

class MainClass
{
    CLASS subObject(OtherClass)
}

模型

使用“模型”关键字为从QAbstractItemModel派生的对象生成特殊的Q_PROPERTY元素。这些属性与SOURCEONLYSETTER具有相同的语义。语法是紧跟在属性名称后面的模型关键字,然后是包含要公开给副本的角色(以逗号分隔)的括号。

// In .rep file
class CdClass
{
    PROP(QString title READONLY)
    MODEL tracks(title, artist, length)
}

信号

在rep文件中使用信号关键字创建信号方法。

使用方法是声明紧跟在括号中的信号签名,其中所期望的签名在括号中被连接,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中的队列连接一样,传递到副本的信号引用参数将进行复制。

在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一样,传递到副本的槽引用参数将进行复制。

枚举

在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}

相关主题:枚举类型USE_ENUM关键字

POD类型

原始老数据(POD)是一个描述简单数据集合的术语,类似于C++的结构。例如,如果你有一个电话簿的API,你可能会在接口中使用“地址”的概念(其中地址可能包括街道、城市、州、国家以及邮政编码)。你可以使用POD关键字来定义这样的对象,然后在类定义中的PROP/SIGNAL/SLOT定义中使用。

使用方法是声明紧跟在括号中的类型名称,后面是类型/名称对,用逗号分隔,类型/名称对被括号包围。

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相同,但声明不包含在class声明中。

相关主题:ENUMUSE_ENUM关键字

USE_ENUM关键字

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

相关主题:ENUM枚举类型

指令

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

repc工具现在忽略从"#"符号到行尾的所有内容,并将这些内容添加到生成的文件中。因此,多行#if/#else/#endif语句和多行宏不受支持。

有两个特殊指令,分别是#HEADER#FOOTER。这些指令可以用来定义应原样放入生成的代码中的内容,即接口声明之前(HEADER)或之后(FOOTER)。删除了#HEADER#FOOTER的前导标记以及一个空白字符。

在以下示例中,生成的repc类被放入一个命名空间中。

#HEADER namespace MyNamespace {
class MyType
{
    ...
};
#FOOTER } // namespace MyNamespace

CMake函数

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

qt_add_repc_merged

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

qt_add_repc_replicas

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

qt_add_repc_sources

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

qt_reps_from_headers

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

qmake变量

REPC_REPLICA

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

例如

REPC_REPLICA = media.rep \
               location.rep

生成的文件将以rep_<replica file base>_replica.h的形式创建。

REPC_SOURCE

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

例如

REPC_SOURCE = media.rep \
              location.rep

生成的文件将以rep_<replica file base>_source.h的形式创建。

REPC_MERGED

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

例如

REPC_MERGED = media.rep \
              location.rep

生成的文件将以rep_<replica file base>_merged.h的形式创建。

注意:通常源和副本位于不同的进程或设备中,所以这个变量不常用。

QOBJECT_REP

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

另请参阅:QRemoteObjectAbstractPersistedStore.

© 2024 Qt公司有限公司。此处包含的文档贡献为各自所有者的版权。提供的文档是根据自由软件基金会发布的GNU自由文档许可证版本1.3许可的。Qt及其相关标志是芬兰和/或世界其他地区的The Qt Company Ltd.的商标。所有其他商标均为其各自所有者的财产。