QtProtobuf  0.6
Protobuf plugin to generate Qt classes
QtProtobuf Client Tutorial

This tutorial will show how to use QtProtobuf client with some existing server and specified communication protocol.

Add QtProtobuf package to CMake project

After you successfully installed/built QtProtobuf (see README.md) with complete set of dependencies you may start creation either CMake or qmake project in QtCreator. Now we need to tell our project about QtProtobuf. Add following line in your CMakeLists.txt to add QtProtobuf as project dependency:

find_package(QtProtobuf COMPONENTS ProtobufGenerator Protobuf Grpc REQUIRED)
...
target_link_libraries(clienttutorial PRIVATE QtProtobuf::Grpc QtProtobuf::Protobuf)

At this point you will have full access to QtProtobuf libraries and macroses.

Generate code

Let's imagine you have echo sever with following protocol definition saved to tutorial.proto:

syntax="proto3";
package qtprotobuf.tutorial;
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}
service EchoService {
rpc Echo(EchoRequest) returns (EchoResponse);
}

Integration with your Qt application starts at point when you add protobuf and gRPC generation for .proto filed. Add qtprotobuf_generate function to your CMakeLists.txt as following:

...
add_executable(clienttutorial main.cpp qml.qrc) #Add QtProtobuf generator rules after CMake target defined
qtprotobuf_generate(TARGET clienttutorial QML TRUE PROTO_FILES tutorial.proto)
...

We specified QML TRUE to have generated classes visibled in QML. At this point we have direct access to all classes specified in tutorial.proto from C++ and QML contexts.

To use them in C++ you need to include tutorial.qpb.h to get all messages defined in tutorial.proto accessible in C++ context:

#include "tutorial.qpb.h"

To access messages from QML code add appropriate import of protobuf package:

import qtprotobuf.tutorial 1.0
Note
All QML imports by default generated with version 1.0.

Implement client-side business logic

Now lets go deeper and try to interact with server using our gRPC API. For server communication we will have C++ model, that implements business logic and encapsulates QtGrpc client for communication purpose:

#include "tutorial.qpb.h"
#include "tutorial_grpc.qpb.h"
class EchoClientEngine : public QObject
{
Q_OBJECT
Q_PROPERTY(qtprotobuf::tutorial::EchoResponse *response READ response CONSTANT)
public:
explicit EchoClientEngine(QObject *parent = nullptr);
Q_INVOKABLE void request(qtprotobuf::tutorial::EchoRequest *req);
qtprotobuf::tutorial::EchoResponse *response() const
{
return m_response.get();
}
private:
std::unique_ptr<qtprotobuf::tutorial::EchoServiceClient> m_client;
std::unique_ptr<qtprotobuf::tutorial::EchoResponse> m_response;
};

Here we specify our interface to communicate to QML part.

  • response property provides recent response from server. We made it CONSTANT because pointer it selves don't need any modifications.
  • request invocable function expects pointer to EchoRequest, that will be utilized to call Echo remote procedure.
  • m_client keeps pointer to generated EchoServerClient.
Note
This implementation is one of possible usage of client-side grpc API.

Definition part of EchoClientEngine includes two main points:

  1. Initialization of gRPC channel and attaching our EchoServiceClient to it.
  2. Implement request call

For gRPC channel we will use Http2 protocol without credentials. Simply create new channel with QGrpcInsecureChannelCredentials / QGrpcInsecureCallCredentials and pass it as parameter to attachChannel call right in constructor:

EchoClientEngine::EchoClientEngine(QObject *parent) : QObject(parent), m_client(new EchoServiceClient), m_response(new EchoResponse)
{
m_client->attachChannel(std::shared_ptr<QAbstractGrpcChannel>(new QGrpcHttp2Channel(QUrl("http://localhost:65000"),
QGrpcInsecureChannelCredentials()|QGrpcInsecureCallCredentials())));
}

Request function is very simple only utilizes generated Echo method, one of possible implementations as below:

void EchoClientEngine::request(qtprotobuf::tutorial::EchoRequest *req)
{
m_client->Echo(*req, m_response.get());
}

Let's stop at parameters, that we pass to Echo method. First parameter is request. Methods generated by QtProtobuf pass only reference to method argument, that's why we derefence it and provide as value. Second parameter is pointer to response that will be modified when server response will be received. Important point here is that responce value passed as QPointer and its time of life is controlled by grpc client. In case if pointer was removed client will ignore server response silently. So from this perspective QtGrpc provides to you async response safety.

We complete our model implementation, and it's time to make it visible in QML context. Simply do it in main:

...
#include <QtProtobufTypes>
#include "echoclientengine.h
...
int main(int argc, char *argv[])
{
... //Before load QML
QtProtobuf::qRegisterProtobufTypes();
qmlRegisterSingletonType<EchoClientEngine>("qtprotobuf.tutorial", 1, 0, "EchoClientEngine", [](QQmlEngine *engine, QJSEngine *){
static EchoClientEngine echoEngine;
engine->setObjectOwnership(&echoEngine, QQmlEngine::CppOwnership);
return &echoEngine;
});
...
}

It's important to call qRegisterProtobufTypes to register all QtProtobuf types including generated user types. In our case it's types generated from tutorial.proto.

Implement client-side UI and interact to business logic

Let's move to presentation part. Of course it's better to use QML when we describe our project UI:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import qtprotobuf.tutorial 1.0
Window {
...
EchoRequest {
id: request
message: messageInput.text
}
...
TextField {
id: messageInput
anchors.verticalCenter: parent.verticalCenter
width: 400
onAccepted: {
echoEngine.request(request);
text = ""
}
}
...
Text {
anchors.verticalCenter: parent.verticalCenter
text: echoEngine.response.message
}
}

After cut off all fancy QML stuff, only QtProtobuf related things are left. Its important to do not forget import of our freshly generated qtprotobuf.tutorial package. At this point we have access to any message inside qtprotobuf.tutorial package. So we create view model right in QML code by adding EchoRequest object and bind message property to messageInput text.

Note
It's not mandatory and in some cases may cause often field update, but in out case it's just tutorial ¯\_(ツ)_/¯.

Only thing left is to call our request method. We pass request that already contains text, that we would like to send to server. Result will be received and written to echoEngine.response.message property. We bound it to appropriate text field, that will be updated "immediately".

  • It's time for testing! For tests you may use test server from projects examples/tutorial/tutorialserver folder. Run "build_and_run.sh" script in examples/tutorial/tutorialserver folder and enjoy echo functionality of your QtProtobuf EchoService client UI.

Feedback

Please provide feedback using this issue in project bug tracker.

References

Complete tutorial code