4. Tutorial: Generating Code from Protocol Buffers Messages
This chapter uses an RTI example, protobuf-sdk, to demonstrate how
Protocol Buffers Extension enables Connext applications to use Protocol Buffers message types
as DDS Topics. It details the workflow for defining,
converting, and using Protocol Buffers 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 Protocol Buffers 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 <path to examples>/connext_dds/c++/protobuf-sdk.
See Paths Mentioned in Documentation for the path to the example.
4.1. Before You Begin
Set up the Connext environment variables required to run RTI Code Generator (
rtiddsgen) using thertisetenvscript for your architecture. For example:source <NDDSHOME>/resource/scripts/rtisetenv_x64Linux4gcc8.5.0.bash
This script sets up the environment variables for running
rtiddsgen. For more information, see Setting up Environment Variables in the Connext Getting Started Guide.Install the protocol buffer compiler (
protoc), then add the installation directory to yourPATHenvironment variable. For example:export PATH=<protoc install dir>/bin:$PATH
See the Protocol Buffers documentation for installation instructions.
Add
<NDDSHOME>/binto yourPATHenvironment variable. For example:export PATH=<NDDSHOME>/bin:$PATH
The
PATHvariable must include this directory to access the Protocol Buffers Extension plugins.Import RTI’s
<NDDSHOME>/include/omg/dds/descriptor.protofile into your.protofiles to enable the DDS Options for Protocol Buffers.
4.2. Define Protocol Buffers Message Types
To integrate Protocol Buffers with Connext, first define your Protocol Buffers 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.
// 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 Protocol Buffers data models can be used without modifications because Protocol Buffers Extension provides detailed mapping between Protocol Buffers and IDL4 definitions.
4.3. Convert Protocol Buffers Data
Next, convert the Protocol Buffers types into types for the programming language used by the application code.
To convert using two phases:
Use
protocto convert the.protofile to an equivalent.idlfile.Use RTI Code Generator (
rtiddsgen) to generate the remaining source code needed to use the Protocol Buffers types as DDS Topics in Connext.
For example:
# 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:
# Run rtiddsgen to convert the .proto file and generate code in one step
4.4. Exchange Protocol Buffers Data Over DDS
After converting the Protocol Buffers types, applications can use the generated type support code to exchange Protocol Buffers data directly over DDS Topics.
Note
Protocol Buffers Extension supports the Protocol Buffers 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 Protocol Buffers types.
These files are excerpted below:
// addressbook_publisher.cxx // Include the standard DDS C++ API. #include <dds/dds.hpp> // 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<tutorial::AddressBook> topic(participant, "Example tutorial_AddressBook"); dds::pub::Publisher publisher(participant); dds::pub::DataWriter<tutorial::AddressBook> 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);// addressbook_subscriber.cxx // Include the standard DDS C++ API. #include <dds/dds.hpp> // 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<tutorial::AddressBook> topic(participant, "Example tutorial_AddressBook"); dds::sub::Subscriber subscriber(participant); dds::sub::DataReader<tutorial::AddressBook> reader(subscriber, topic); // Take samples from the typed DataReader. dds::sub::LoanedSamples<tutorial::AddressBook> 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; } }
4.5. Communicate with Other DDS Applications
On running the protoc command, Protocol Buffers 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 Protocol Buffers Extension IDL4 Converter 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:
// 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 IDL4 types can be used in other applications that use any of the programming languages supported by Connext (see the RTI Connext Getting Started Guide). For example, to use the Modern C++ API with the latest language binding defined by the IDL4 specification:
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
4.6. 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:
source <NDDSHOME>/resource/scripts/rtisetenv_<architecture>.bash
Protocol Buffers Extension is included with Connext, so no additional Connext configuration is needed.
Build the example applications:
// Create a build directory and enter it mkdir build cd build // Generate the CMake build files cmake <NDDSHOME>/resource/template/rti_workspace/examples/connext_dds/c++11/protobuf // Build the example applications cmake --build .
Note
If the Google
protobuflibraries are not installed on your system, CMake may not be able to find all the required dependencies. Use the-DCMAKE_PREFIX_PATH=<Path to protobuf build>/lib64/cmakeand-DProtobuf_USE_CONFIG=ONoptions when generating the CMake build files. These options will help CMake configure the dependencies and compile correctly.
Publish data samples to a DDS Topic using the Protocol Buffers types using one of the supported language bindings:
Start a publisher using the Protocol Buffers C++ language binding:
./addressbook_publisher
Start a publisher using the Connext C++ language binding:
./addressbook_publisher_dds
Subscribe to the DDS Topic using the Protocol Buffers types:
Start a subscriber using the Protocol Buffers C++ language binding:
./addressbook_subscriber
Start a subscriber using the Connext C++ language binding:
./addressbook_subscriber_dds
Start RTI Spy (
rtiddsspy) to subscribe to all data in the DDS domain:rtiddsspy -printSample
This command displays all data samples exchanged in the DDS domain, including the Protocol Buffers published data samples.
For information on using Spy for debugging and verifying the data exchanged in DDS domains, see the RTI Spy User’s Manual.