.. _section-hello-types: Tutorial: Generating Code from |PROTOBUF| Messages ************************************************** This chapter uses an RTI example, ``protobuf-sdk``, to demonstrate how |PROTOBUF_EXT| enables |CONNEXT| applications to use |PROTOBUF| message types as DDS |TOPICS|. It details the workflow for defining, converting, and using |PROTOBUF| message types in |CONNEXT| applications. The chapter concludes with instructions for building and running the example applications. The ``protobuf-sdk`` example files reference the `AddressBook example `__ included in the |PROTOBUF| documentation, which describes a simple data model composed of a single ``.proto`` file. The complete source code for this example is available in your |CONNEXT| installation at ``/connext_dds/c++/protobuf-sdk``. See :ref:`section-paths-mentioned` for the path to the example. .. _section-before-you-begin: Before You Begin ================ - Set up the |CONNEXT| environment variables required to run |RTI_CODEGEN| (``rtiddsgen``) using the ``rtisetenv`` script for your architecture. For example: .. code-block:: shell source /resource/scripts/rtisetenv_x64Linux4gcc8.5.0.bash This script sets up the environment variables for running ``rtiddsgen``. For more information, see :link_connext_dds_pro_gsg_cpp11:`Setting up Environment Variables ` in the |CORE_GSG|. - Install the protocol buffer compiler (``protoc``), then add the installation directory to your ``PATH`` environment variable. For example: .. code-block:: bash export PATH=/bin:$PATH See the `Protocol Buffers documentation `__ for installation instructions. - Add ``/bin`` to your ``PATH`` environment variable. For example: .. code-block:: bash export PATH=/bin:$PATH The ``PATH`` variable must include this directory to access the |PROTOBUF_EXT| plugins. - Import RTI's ``/include/omg/dds/descriptor.proto`` file into your ``.proto`` files to enable the :ref:`section-dds-options`. Define |PROTOBUF| Message Types =============================== To integrate |PROTOBUF| with |CONNEXT|, first define your |PROTOBUF| message types in a ``.proto`` schema file. This step is illustrated in the ``addressbook.proto`` file in the ``protobuf-sdk`` example. Below is an excerpt of the ``addressbook.proto`` file, which defines the ``Person`` and ``AddressBook`` message types. .. code-block:: protobuf // addressbook.proto syntax = "proto3"; package tutorial; import "google/protobuf/timestamp.proto"; message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; google.protobuf.Timestamp last_updated = 5; } message AddressBook { repeated Person people = 1; } Most |PROTOBUF| data models can be used without modifications because |PROTOBUF_EXT| provides :ref:`detailed mapping ` between |PROTOBUF| and |IDL| definitions. .. _section-converting-data: Convert |PROTOBUF| Data ======================= Next, convert the |PROTOBUF| types into types for the programming language used by the application code. To convert using two phases: - Use ``protoc`` to convert the ``.proto`` file to an equivalent ``.idl`` file. - Use *RTI Code Generator* (``rtiddsgen``) to generate the remaining source code needed to use the |PROTOBUF| types as DDS |TOPICS| in |CONNEXT|. For example: .. code-block:: shell # First run the protocol buffers compiler with the RTI plugins protoc --idl4_out=build \ --cpp_out=build \ --connext-cpp_out=build \ -I . \ addressbook.proto \ google/protobuf/timestamp.proto # Then use the Connext compiler to generate additional code from the generated IDL rtiddsgen -language C++11 \ -standard PROTOBUF_CPP \ -I build \ build/addressbook.idl \ build/google/protobuf/timestamp.idl To convert using a single phase, use ``rtiddsgen`` to convert the ``.proto`` file and generate code in one step. For example: .. code-block:: shell # Run rtiddsgen to convert the .proto file and generate code in one step Exchange |PROTOBUF| Data Over DDS ================================= After converting the |PROTOBUF| types, applications can use the generated type support code to exchange |PROTOBUF| data directly over DDS |TOPICS|. .. note:: |PROTOBUF_EXT| supports the |PROTOBUF| C++ language binding to exchange ``protobuf`` data. The ``protobuf-sdk`` example includes the ``addressbook_publisher.cxx`` and ``addressbook_subscriber.cxx`` files to illustrate how to publish and subscribe to a DDS |TOPIC| using the converted |PROTOBUF| types. These files are excerpted below: .. code-block:: c++ // addressbook_publisher.cxx // Include the standard DDS C++ API. #include // Include the generated header files for the Protocol Buffers types. // addressbook.hpp is generated from addressbook.idl by rtiddsgen. // It includes addressbook.pb.h, which is generated by protoc // from addressbook.proto. #include "addressbook.hpp" // Instantiate DDS entities to publish an AddressBook topic on domain 0. dds::domain::DomainParticipant participant(0); dds::topic::Topic topic(participant, "Example tutorial_AddressBook"); dds::pub::Publisher publisher(participant); dds::pub::DataWriter writer(publisher, topic); // Create an instance of the AddressBook message and populate it. tutorial::AddressBook data; auto person = data.add_people(); person->set_name("John Doe"); person->set_id(1); person->set_email("johndoe@example.org"); auto phone = person->add_phones(); phone->set_number("867-5309"); phone->set_type(tutorial::Person_PhoneType_HOME); // Write the AddressBook message using the typed DataWriter. writer.write(data); .. code-block:: c++ // addressbook_subscriber.cxx // Include the standard DDS C++ API. #include // Include the generated header files for the Protocol Buffers types. #include "addressbook.hpp" // Instantiate DDS entities to subscribe to an AddressBook topic on domain 0. dds::domain::DomainParticipant participant(0); dds::topic::Topic topic(participant, "Example tutorial_AddressBook"); dds::sub::Subscriber subscriber(participant); dds::sub::DataReader reader(subscriber, topic); // Take samples from the typed DataReader. dds::sub::LoanedSamples samples = reader.take(); for (const auto &sample : samples) { if (sample.info().valid()) { // Samples are instances of the Protocol Buffers AddressBook type. tutorial::AddressBook &data = sample.data(); std::cout << data.DebugString() << std::endl; } } Communicate with Other DDS Applications ======================================= On running the ``protoc`` command, |PROTOBUF| message types are converted into equivalent DDS-XTypes, which can then be used to communicate with other DDS applications. These DDS-XTypes are defined in the ``.idl`` files generated by the |PROTOBUF_EXT| :ref:`section-idl4-plugin`. In the ``protobuf-sdk`` example, the ``addressbook.proto`` file is converted into the ``addressbook.idl`` file, which defines the DDS-XTypes for the ``AddressBook`` and ``Person`` message types. The generated ``addressbook.idl`` file contains the following definitions: .. code-block:: omg-idl // addressbook.idl // ----------------------------------------------------------------------------- // WARNING --------------------------------------------------------------------- // This file was automatically generated by RTI's IDL4 plugin for protoc, // from Protobuf source file: addressbook.proto // Do not edit this file manually. ALL CHANGES WILL BE LOST! // ----------------------------------------------------------------------------- #ifndef tutorial_addressbook_proto_IDL4_ #define tutorial_addressbook_proto_IDL4_ #include "google/protobuf/timestamp.idl" module tutorial { @containing_type("Person") enum Person_PhoneType { @value(0) @default_literal Person_PhoneType_MOBILE, @value(1) Person_PhoneType_HOME, @value(2) Person_PhoneType_WORK }; // enum Person_PhoneType struct Person_PhoneNumber; struct Person; struct AddressBook; @nested @containing_type("Person") @mutable struct Person_PhoneNumber { @id(1) @field_presence(implicit) string number; @id(2) @field_presence(implicit) ::tutorial::Person_PhoneType type; }; // struct Person_PhoneNumber @mutable struct Person { @id(1) @field_presence(implicit) string name; @id(2) @field_presence(implicit) int32 id; @id(3) @field_presence(implicit) string email; @id(4) sequence<::tutorial::Person_PhoneNumber> phones; @id(5) @optional ::google::protobuf::Timestamp last_updated; }; // struct Person @mutable struct AddressBook { @id(1) sequence<::tutorial::Person> people; }; // struct AddressBook }; // tutorial #endif // tutorial_addressbook_proto_IDL4_ The generated |IDL| types can be used in other applications that use any of the programming languages supported by |CONNEXT| (see the :link_connext_dds_pro_gsg:`RTI Connext Getting Started Guide `). For example, to use the :link_connext_dds_api_cpp2:`Modern C++ API ` with the latest language binding defined by the |IDL| specification: .. code-block:: shell mkdir build-dds rtiddsgen -language C++11 \ -standard IDL4_CPP \ -I build \ -d build-dds \ build/addressbook.idl mkdir -p build-dds/google/protobuf rtiddsgen -language C++11 \ -standard IDL4_CPP \ -I build \ -d build-dds/google/protobuf \ build/google/protobuf/timestamp.idl Build and Run the Example ========================= After completing the above steps, you can build and run the ``protobuf-sdk`` example applications. #. Set the |CONNEXT| environment variables: .. code-block:: shell source /resource/scripts/rtisetenv_.bash |PROTOBUF_EXT| is included with |CONNEXT|, so no additional |CONNEXT| configuration is needed. #. Build the example applications: .. code-block:: shell // Create a build directory and enter it mkdir build cd build // Generate the CMake build files cmake /resource/template/rti_workspace/examples/connext_dds/c++11/protobuf // Build the example applications cmake --build . .. note:: If the Google ``protobuf`` libraries are not installed on your system, CMake may not be able to find all the required dependencies. Use the ``-DCMAKE_PREFIX_PATH=/lib64/cmake`` and ``-DProtobuf_USE_CONFIG=ON`` options when generating the CMake build files. These options will help CMake configure the dependencies and compile correctly. 3. Publish data samples to a DDS |TOPIC| using the |PROTOBUF| types using one of the supported language bindings: a. Start a publisher using the |PROTOBUF| C++ language binding: .. code-block:: shell ./addressbook_publisher b. Start a publisher using the |CONNEXT| C++ language binding: .. code-block:: shell ./addressbook_publisher_dds #. Subscribe to the DDS |TOPIC| using the |PROTOBUF| types: a. Start a subscriber using the |PROTOBUF| C++ language binding: .. code-block:: shell ./addressbook_subscriber b. Start a subscriber using the |CONNEXT| C++ language binding: .. code-block:: shell ./addressbook_subscriber_dds #. Start *RTI Spy* (``rtiddsspy``) to subscribe to all data in the DDS domain: .. code-block:: shell rtiddsspy -printSample This command displays all data samples exchanged in the DDS domain, including the |PROTOBUF| published data samples. For information on using *Spy* for debugging and verifying the data exchanged in DDS domains, see the :link_tools_rti_dds_spy:`RTI Spy User's Manual `.