define what to say
The fundametal concept behind protcol buffers is to define a data structure containing all informations you like to involved. For this data structure a special format is used and it is defined in a so called .proto file.
After that the protocl buffer compiler translates this .proto file into a C++ class which is allows to access the data and in addition to that to do serializing/deserializing operations.
Because the .proto file kind of gerneral and descriptive they can translate to C#,Go,Java and Phyton out of the box , too.
That means once a common data structure or message is defined , different languages can talk to each other.
So lets jump into an example. Lets say we want to design a message a robot can send out to the world. Our .proto file RobotMsg.proto could look like that
message RobotMsg{ required int32 messageId = 1; required string deviceName = 2; required string timestamp = 3; enum RobotStates{ Unkown =0; Error=1; Connected=2; Idle=3; Moving=4; }; required RobotStates robotState = 4; repeated double position = 5; optional int32 digitalInputBitMask= 6; } |
We see that POD types a available like int32 or double and composite types like enum , too. A dynamic array is defined by the key word repeated. Each data member is labeled by unique numbered tag.
forever or compatible
Sometimes it is neccessary to update your proto message by for example adding one data member. Therefore the keyword optional gives you the opportuity to keep things compatible in older versions. On the other hand the required keyword makes things stay forever. Data field with repeated keyword are optional by nature, because an array can have zero elements. In this case some robots do have a digital input extension, some have not so the bitmask is labeled optional.
I recommend to take a look at the offical protocol buffer documentation [1] for in deeper readings.
let’s generate
If you we do not want to get in touch with the protocol buffer compiler by hand everytime we can make use of cmake integration. The followinng lines in our CMakeLists.txt will do the job.
PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS RobotMsg.proto) include_directories(${CMAKE_CURRENT_BINARY_DIR}) |
That means our RobotMsg.proto file is feed to the compiler and the generates two files: RobotMsg.pb.h and RobotMsg.pb.cpp. These files are accessed by PROTO_SRCS and PROTO_HDRS variables.
The generated files are placed into our build tree directory. Therefore it is very important to add the CMAKE_CMAKE_CURRENT_BINARY_DIRBUILD_DIR to out include directories. Of course we have to compile the RobotMsg.pb.cpp and add it our the project see full CMakeLists.txt on github [2].
Hello Robot
No we ready to use our RobotMsg in our C++ code :
#include <iostream> #include "RobotMsg.pb.h" int main() { RobotMsg msg; msg.set_devicename(std::string("R2D2")); msg.set_robotstate(RobotMsg_RobotStates_Connected); std::cout << "Hello Robot: size is " << sizeof(msg) << "\n"; std::cout << "Hello Robot: byte size is " << msg.ByteSize() << "\n"; std::cout << "Hello Robot: " << msg.devicename() << "\n"; return(0); } |
Here we create the RobotMsg object on the stack and use setters/ getters for the data members we defined in the .proto file.
references / further reading
[1] https://developers.google.com/protocol-buffers/docs/proto