Qt IVI Generator Remote Objects Example

This Example shows how to use the Qt IVI Generator to create remote backends.

Introduction

This example shows how to create a remote backend and the client side components using the Qt IVI Generator. The communication between the client and server is done with QtRemoteObjects. Based on a single qface IDL file, it will generate:

  • a shared library with the front-end code
  • a back-end plugin that implements a client for connecting to the server
  • a server that runs the actual remote backend logic
  • a demo application that connects to the server and provides and UI for using the service

In addition to the generated C++ code, the backend-plugin and the server contain also an intermediate .rep -file that is further processed by Qt’s replica compiler to produce the actual source and replica classes.

Walkthrough

The IDL file used in the example represents an imaginary remote service for processing data . It contains a single interface with a single property and a single method.

First we need to define which module we want to describe. The module acts as a namespace, because the IDL file can contain multiple interfaces.

module Example.IVI.Remote 1.0;

The most important part is the definition of the interface.

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

    string lastMessage;

    int process(string data);
}

In this case, we define an interface named ProcessingService consisting of a single property and a single method. Every property and method definition needs to contain at least a type and a name. Most of the basic types are builtin and can be found in the reference.

Frontend library

Now we want to use the IVI Generator to generate a shared library containing a C++ implementation of our module and its interface. For this, the frontend template is used. This will generate a class derived from QIviAbstractZonedFeature including all the specified properties. The generated library will use the Dynamic Backend System from QtIviCore and by that provide an easy way to change the behavior implementations.

In order to call the autogenerator for our shared library, the qmake project file needs to use the ivigenerator qmake feature. The following snippet shows how it can be added:

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

By adding ivigenerator to the CONFIG variable, the ivigenerator feature file will be loaded and interpret the QFACE_SOURCES variable similar to SOURCES variable of normal qmake projects. Activating the qmake feature using the CONFIG variable has the disadvantage that it doesn't report any errors if the feature is not available. Because of this, it is encouraged to use the following additional code to report errors:

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

The other part of the project file is a normal library setup which is supposed to work on Linux, macOS and Windows.

RemoteObjects Backend Plugin

As mentioned above, the frontend library will use the Dynamic Backend System. This means that for the library to provide some functionality, we also need a backend plugin. The generated plugin here will work as a client that connects to the server using the Qt Remote Objects. The qmake integration works in the same way, but it is using the QFACE_FORMAT variable to tell the ivigenerator to use a different generation template, backend_qtro:

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

The generated backend plugin code is usable as is, and doesn't require further changes by the developer. As we want to generate a plugin instead of a plain library, we need to instrument qmake to do so by adding plugin to the CONFIG variable. For the plugin to compile correctly it needs to get the backend interface header from the previously created library. As this header is also generated, it is not part of our source tree, but part of the build tree. We do this by adding it to the include path using the following construct:

INCLUDEPATH += $$OUT_PWD/../frontend

Most of the code in the backend plugin is generated by the IVI Generator, but some of it is generated by the Qt's remote object compiler (repc). This is achieved by IVI Generator producing an intermediate .repc file that is further processed by the repc compiler. The repc is called via the generated .pri file, found in the build directory (notice, that you have to call qmake on the project at least once to have the generated files available).

As our application doesn't know about the existence of our backend plugin, we need to put this plugin in a folder where our application searches for plugins. By default Qt either search in the plugins folder within Qt's installation directory or in the current working directory of the application. For QtIvi plugins to be found, they need to be provided within a qtivi sub-folder. This is achieved automatically by adding the following line to our backend project file:

DESTDIR = ../qtivi

RemoteObjects Server

The server is an independent, GUIless application that contains the actual business logic of our backend and hence needs to have most of its implementation written by the developer. Nevertheless, the generator produces some code to simplify the development. We can generate server side code by using the IVI Generator with server_qtro template:

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

To use the generated remote source, we need to inherit from one of the classes defined in the generated rep_processingservice_source.h file. In this example we implement our servers logic in the ProcessingService class and use the ProcessingServiceSimpleSource as the base class:

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

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

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

Please notice, that the base class contains already the definitions for property accessors, but any custom method or slot needs to be overridden and defined by the developer. Our implementation of the process function merely counts and returns the length of the passed data and updates the lastMessage property:

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

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

In order to make the class ProcessingService accessible remotely, we need to share it. This is done with the QRemoteObjectNode::enableRemoting() function. The generated Core class provides a preconfigured instance of a remotenode that is used for the remoting. In order for the plugin to connect to the right object, use an identifier in the format ModuleName.InterfaceName, which in this case is "Example.IVI.Remote.ProcessingService". All this is done in the main()-function, along with the start of the main event loop:

// 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();
}

Implementing a service that is accessible remotely is as simple as that; use the properties as usual and provide the method implementations. The QtRemoteObjects library takes care of the communication.

Demo Client Application

The demo application presents a simple QML GUI for using the remote service over the generated interface.

As we do not provide a QML plugin, the application needs to link to the generated frontend library and call the RemoteModule::registerTypes and RemoteModule::registerQmlTypes methods that are generated in the module singleton to register all autogenerated interfaces and types with the QML engine.

In our QML application, we still need to import the module using the name we provided to RemoteModule::registerQmlTypes. Afterwards the interface can be instantiated like any other QML item.

// demo/main.qml
import IviRemote 1.0

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

    UiProcessingService {
        id: processingService
    }
...

Every method call that is made through a generated API, is asynchronous. This means, that instead of directly returning a return value, a QIviPendingReply object is returned. Using the QIviPendingReply::then() method on the returned object, we may assign callbacks to it that are called when the method call has been successfully finished or if it has failed.

// 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"
        } )
}

In case of properties, we just use bindings as usual:

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

Running the Example

In order to see the whole functionality of the demo, run both the server and the demo application at the same time. You may leave the server running and restart the application, or vice versa, to see that the reconnection works. Run the demo application alone without the server running, to test how the remote method call fails when there is no connection.

Files:

© 2020 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.