使用 Qt 接口框架生成器生成基于 QtRemoteObjects 的后端

了解如何使用 Qt 接口框架生成器来创建基于QtRemoteObjects的后端。

简介

此示例演示了如何使用 Qt 接口框架生成器生成中间件 API、中间件后端和相应的中间件服务。后端和服务之间的通信使用QtRemoteObjects作为 IPC。

我们使用一个单独的 QFace IDL 文件来生成

  • 包含前端代码的共享库
  • 一个后端插件,实现了一个连接到服务器的客户端
  • 一个运行实际后端逻辑的独立服务器进程
  • 一个连接到服务器并提供用户界面以使用服务的演示应用程序

除了生成的 C++ 代码外,后端插件和服务器还包含了一个中间的 .rep 文件,该文件由replica 编译器进一步处理以生成源和副本类。

流程

示例中使用的 IDL 文件表示一个处理数据的虚拟远程服务。它包含一个接口,其中一个属性和一个方法。

首先,我们需要定义我们想描述的哪个模块。模块充当命名空间,因为 IDL 文件可以包含多个接口。

module Example.If.RemoteModule 1.0;

最重要的部分是 接口 的定义。

@config: { qml_type: "UiProcessingService" }
interface ProcessingService {

    string lastMessage;

    int process(string data);
}

在这种情况下,我们定义了一个名为 ProcessingService接口,其中有一个属性和一个方法。每个属性和方法定义都需要包含至少一个类型和一个名称。大多数基本类型是内置的,可以在 QFace IDL 语法 中找到。

前端库

接下来,我们使用接口框架生成器生成一个共享库,其中包含我们模块的 C++ 实现及其接口;尤其是前端模板。该模板生成一个从 QIfAbstractFeature 继承的类,它包括所有指定的属性。生成的库使用来自 QtInterfaceFramework动态后端系统,从而提供了一种简单方便地更改行为实现方式的方法。

要调用为我们共享库的自动生成器,它需要集成到构建系统中。

CMake:

首先,需要使用 find_package 找到 InterfaceFramework 软件包

find_package(Qt6 REQUIRED COMPONENTS InterfaceFramework)

然后,我们继续构建一个库,并让自动生成器通过调用 qt6_ifcodegen_extend_target 将生成的源代码扩展到此目标。

qt_add_library(QtIfRemoteExample)

# Interface Framework Generator:
qt_ifcodegen_extend_target(QtIfRemoteExample
    IDL_FILES ../example-remote.qface
    TEMPLATE frontend
)

qmake:

CONFIG += ifcodegen
IFCODEGEN_SOURCES = ../example-remote.qface

通过将 ifcodegen 添加到 CONFIG 变量中,可以加载 ifcodegen 功能文件,并解释 IFCODEGEN_SOURCES 变量。这与常规 qmake 项目的 SOURCES 变量类似。然而,通过 CONFIG 变量激活 qmake 功能有一个缺点:如果功能不可用,则不会报告错误。我们建议使用以下附加代码来报告错误。

QT_FOR_CONFIG += interfaceframework
!qtConfig(ifcodegen): error("No ifcodegen available")

项目文件的其余部分是正常的库设置,适用于 Linux、macOS 和 Windows。

QtRemoteObjects 后端插件

如上所述,前端 库使用 动态后端系统。这意味着为了使库提供某些功能,我们还需要一个 后端 插件。此处生成的插件充当客户端,通过 Qt Remote Objects 连接到服务器。构建系统集成以相同的方式工作,但它使用不同的生成模板。

CMake:

通过调用代码生成器并使用 backend_simulator 模板来定义和扩展插件。

qt_add_plugin(remote_backend_qtro PLUGIN_TYPE interfaceframework)

qmake:

CONFIG += ifcodegen plugin
IFCODEGEN_TEMPLATE = backend_qtro
IFCODEGEN_SOURCES = ../example-remote.qface
PLUGIN_TYPE = interfaceframework
PLUGIN_CLASS_NAME = RemoteClientQtROPlugin

生成的后端插件代码可以直接使用,无需任何修改。因为我们想生成一个插件而不是一个普通库,我们需要通过将 plugin 添加到 CONFIG 变量来让 qmake 知道这一点。

为了使插件正确编译,它需要从先前创建的库中获取后端接口头文件。但这个头文件也是生成的,所以它不是我们的源代码树的一部分,而是构建树的一部分。为了提供后端接口头文件,我们使用以下代码将其添加到包含路径中:

CMake:

target_link_libraries(remote_backend_qtro PUBLIC
    QtIfRemoteExample
)

qmake:

INCLUDEPATH += $$OUT_PWD/../frontend

后端插件中的大部分代码是由接口框架生成器生成的,但其中一些是由 Qt 的远程对象编译器 repc 生成的。为了实现这一点,接口框架生成器生产一个中间的 .repc 文件,该文件随后由 repc 编译器进一步处理。这个编译器通过生成的构建系统文件调用,该文件在构建目录中找到。

我们的应用程序不知道我们的后端插件的存存,所以我们需要将此插件放置在应用程序通常查找插件的文件夹中。默认情况下,Qt 要么在其安装目录内的 plugins 文件夹中搜索,要么在应用程序当前工作目录中搜索。为了使 QtInterfaceFramework 插件可找到,它们需要在 interfaceframework 子文件夹内提供。将以下行添加到后端构建系统文件中,如下所示:

CMake:

set_target_properties(remote_backend_qtro PROPERTIES LIBRARY_OUTPUT_DIRECTORY ../interfaceframework)

qmake:

DESTDIR = ../interfaceframework

RemoteObjects 服务器

服务器是一个独立的无 GUI 应用程序,包含后端的企业逻辑,我们需要编写其大部分实现。然而,生成器生成了一些代码以简化开发。我们可以通过使用 server_qtro 模板的接口框架生成器来生成服务器端代码。

CMake:

# Interface Framework Generator:
qt_ifcodegen_extend_target(qface-remote-server
    IDL_FILES ../example-remote.qface
    TEMPLATE server_qtro
)

qmake:

TEMPLATE = app
QT += interfaceframework
QT -= gui
CONFIG += c++11 ifcodegen
...
IFCODEGEN_TEMPLATE = server_qtro
IFCODEGEN_SOURCES = ../example-remote.qface

要使用生成的远程源,我们需要从生成的 rep_processingservice_source.h 文件中定义的类之一继承。在本例中,我们在 ProcessingService 类中实现我们服务器的逻辑,并使用 ProcessingServiceSimpleSource 作为基类。

// server_qtro/processingservice.h
#include "rep_processingservice_source.h"

class ProcessingService : public ProcessingServiceSimpleSource
{
public:
    ProcessingService(QObject *parent);

    QVariant process(const QString & data) override;
};

请注意,基类已经具有属性访问器定义,但任何自定义方法或槽都需要重写和定义。我们实现的进程函数仅仅计算和返回传入数据的长度,并更新 lastMessage 属性。

// server_qtro/processingservice.cpp
ProcessingService::ProcessingService(QObject *parent)
    : ProcessingServiceSimpleSource(parent)
{
    setLastMessage(u"Service online."_s);
}

QVariant ProcessingService::process(const QString & data)
{
    setLastMessage(u"Processed data \'%1\'"_s.arg(data));
    return data.length();
}

为了让ProcessingService类能够在远程访问,我们需要通过QRemoteObjectNode::enableRemoting()函数来共享它。而不是直接调用该函数,我们使用< trợ giúp từQIfRemoteObjectsConfig类,因为它还负责创建一个正确配置的QRemoteObjectHost。为此,我们需要将模块名、接口名以及ProcessingService实例一起传递给QIfRemoteObjectsConfig::enableRemoting()函数。

所有这些都可以在我们的main.cpp实现中完成,但要充分利用QIfRemoteObjectsConfig类,我们让ifcodegen为我们生成main.cpp。这个自动生成的main引入了命令行参数来配置应该使用哪个URL进行共享,并将调用我们需要的serverMain函数。

首先在qface文件中启用main.cpp的自动生成

@config_server_qtro: { useGeneratedMain: true }
module Example.If.RemoteModule 1.0;

现在,实现serverMain来实例化和共享我们的服务

#include <QCoreApplication>

#include "processingservice.h"

#include <QIfRemoteObjectsConfig>

using namespace Qt::StringLiterals;

void serverMain(QIfRemoteObjectsConfig &config)
{
    auto service = new ProcessingService(qApp);
    config.enableRemoting(u"Example.If.RemoteModule"_s, u"ProcessingService"_s, service);
}

这就是实现远程访问服务的全部操作;使用属性通常,并提供方法实现。QtRemoteObjects库负责通信。

演示客户端应用程序

演示应用程序提供了一个简单的QML GUI以使用生成的接口通过远程服务。

由于我们没有提供QML插件,应用程序需要链接到生成的前端库,并调用在模块单例中生成的RemoteModule::registerTypesRemoteModule::registerQmlTypes方法,以将所有自动生成的接口和类型注册到QML引擎。

在我们的QML应用程序中,我们仍然需要使用在QFace文件中使用的相同模块URI导入模块。之后,接口可以像任何其他QML项一样实例化。

// demo/main.qml
import Example.If.RemoteModule

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QtIF Remote example")

    UiProcessingService {
        id: processingService
    }
...

通过生成的API发出的每个方法调用都是异步的。这意味着不是直接返回返回值,而是返回一个QIfPendingReply对象。通过在返回的对象上使用QIfPendingReply::then()方法,我们可以为其分配回调,当方法调用成功完成后调用;或如果调用失败。

// demo/main.qml
Button {
    text: "Process"

    onClicked: processingService.process(inputField.text).then(
        function(result) { //success callback
            resultLabel.text = result
        },
        function() { //failure callback
            resultLabel.text = "failed"
        } )
}

对于属性,我们像通常一样使用绑定

// demo/main.qml
Row {
    Text { text: "Last message: " }
    Text {
        id: serverMessage
        text: processingService.lastMessage
    }
}

运行示例

要查看演示的完整功能,同时运行服务器和演示应用程序。您可以让服务器运行并重新启动应用程序,或者反之亦然,以查看重连是否工作。在没有服务器运行的情况下单独运行演示应用程序以测试在无连接时远程方法调用失败的测试。

示例项目 @ code.qt.io

© 2024 Qt公司有限公司。此处包含的文档贡献是各自所有者的版权。此处提供的文档是根据自由软件基金会发布的GNU自由文档许可第1.3版许可的。Qt和相应的标志是芬兰和/或全球其他国家的The Qt Company Ltd.的商标。所有其他商标属于其各自所有者。