使用 Qt IVI 生成器生成基于 QtRemoteObjects 的后端

学习如何使用 Qt IVI 生成器创建基于 QtRemoteObjects 的后端。

简介

此示例演示如何使用 Qt IVI 生成器生成中间件 API、中间件后端以及相应的中间件服务。后端与服务的通信通过 QtRemoteObjects 作为 IPC 完成。

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

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

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

演练

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

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

module Example.IVI.Remote 1.0;

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

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

    string lastMessage;

    int process(string data);
}

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

前端库

接下来,我们使用 IVI 生成器生成一个包含我们的模块及其接口的 C++ 实现的共享库;特别是前端模板。此模板生成一个从 QIviAbstractFeature 派生的类,包含所有指定的属性。生成的库使用来自 QtIviCore 的动态后端系统,因此提供了一种简单的方法来更改行为实现的方式。

要调用我们的共享库的自动生成器,qmake 项目文件需要使用 ivigenerator qmake 功能

CONFIG += ivigenerator
QFACE_SOURCES = ../example-ivi-remote.qface

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

QT_FOR_CONFIG += ivicore
!qtConfig(ivigenerator): error("No ivigenerator available")

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

QtRemoteObjects 后端插件

如上所述,前端库使用的是动态后端系统。这意味着要为库提供一些功能,我们还需要一个后端插件。这里生成的插件作为一个客户端使用Qt Remote Objects连接到服务器。qmake集成也以同样的方式工作,但它使用QFACE_FORMAT变量告知ivigenerator使用不同的生成模板,即backend_qtro

CONFIG += ivigenerator plugin
QFACE_FORMAT = backend_qtro
QFACE_SOURCES = ../example-ivi-remote.qface
PLUGIN_TYPE = qtivi
PLUGIN_CLASS_NAME = RemoteClientQtROPlugin

生成的后端插件代码可以直接使用,无需任何修改。因为我们想生成一个插件而不是普通的库,所以需要通过添加pluginCONFIG变量来指示qmake这样做。为了正确编译插件,它需要从先前创建的库中获取后端接口头文件。但是此头文件也是生成的,它不属于我们的源代码树,而是属于构建树的一部分。为了提供后端接口头文件,我们使用以下构造将其添加到包含路径

INCLUDEPATH += $$OUT_PWD/../frontend

后端插件中的大部分代码由IVI Generator生成,但也有一部分由Qt的远程对象编译器repc生成。为了实现这一点,IVI Generator生成了一个中间的.repc文件,然后由repc编译器进一步处理。此编译器通过构建目录中生成的.pri文件调用。请注意,您至少需要在一次调用qmake到项目上,以便生成文件可用。

我们的应用程序不了解后端插件的存在,因此我们需要将此插件放在应用程序通常查找插件的位置。默认情况下,Qt要么在其安装目录中的plugins文件夹中进行搜索,要么在应用程序当前的工作目录中。为了QtIvi插件可以被找到,它们需要在一个qtivi子文件夹中提供。按以下方式将以下行添加到后端项目文件

DESTDIR = ../qtivi

RemoteObjects 服务器

服务器是独立的、无GUI的应用程序,包含后端的业务逻辑,我们需要编写大部分的实现。尽管如此,生成器生成了一些代码以简化开发。我们可以通过使用带有server_qtro模板的IVI Generator来生成服务器端代码

TEMPLATE = app
QT -= gui
CONFIG += c++11 ivigenerator
...
QFACE_FORMAT = server_qtro
QFACE_SOURCES = ../example-ivi-remote.qface

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

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

class ProcessingService : public ProcessingServiceSimpleSource
{
public:
    ProcessingService();

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

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

// server_qtro/processingservice.cpp
ProcessingService::ProcessingService()
{
    setLastMessage(QStringLiteral("Service online."));
}

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

为了使ProcessingService类可通过远程访问,我们需要通过QRemoteObjectNode::enableRemoting()函数来共享它。生成的Core类提供了一个预配置的remotenode实例,用于远程访问。为了让插件连接到正确的对象,请使用“ModuleName.InterfaceName”格式的标识符,在我们的例子中是“Example.IVI.Remote.ProcessingService”。所有这一切都在main()函数中完成,包括主事件循环的开始

// server_qtro/main.cpp
#include <QCoreApplication>

#include "processingservice.h"
#include "core.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    ProcessingService service;
    Core::instance()->host()->enableRemoting(&service,QStringLiteral("Example.IVI.Remote.ProcessingService"));

    return app.exec();
}

这就是实现远程访问服务的所有内容;像往常一样使用属性并提供方法实现。QtRemoteObjects库负责通信。

演示客户端应用程序

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

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

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

// demo/main.qml
import Example.IVI.Remote 1.0

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

    UiProcessingService {
        id: processingService
    }
...

通过生成的API进行的每次方法调用都是异步的。这意味着我们不会直接返回返回值,而是返回一个QIviPendingReply对象。使用返回对象的QIviPendingReply::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

©2020 The Qt Company Ltd. 本文档中包含的文档贡献的版权归其各自的所有者所有。本提供的文档根据免费软件基金会发布的GNU自由文档许可协议第1.3版的条款提供。Qt和相应的标志是The Qt Company Ltd.在芬兰和/或其他国家的商标。所有其他商标均为其各自所有者的财产。