使用 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
生成的后端插件代码可以直接使用,无需任何修改。因为我们想生成一个插件而不是普通的库,所以需要通过添加plugin
到CONFIG
变量来指示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::registerTypes
和 RemoteModule::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 } }
运行示例
要查看整个示例功能,同时运行服务器和示例应用程序。您可以继续运行服务器并重新启动应用程序,或反向操作,以查看重新连接是否正常工作。在没有服务器运行的情况下单独运行示例应用程序,以测试远程方法调用在无连接时如何失败。
©2020 The Qt Company Ltd. 本文档中包含的文档贡献的版权归其各自的所有者所有。本提供的文档根据免费软件基金会发布的GNU自由文档许可协议第1.3版的条款提供。Qt和相应的标志是The Qt Company Ltd.在芬兰和/或其他国家的商标。所有其他商标均为其各自所有者的财产。