Introduction to Protocol Buffers

“Anyone who considers protocol unimportant has never dealt with a cat.” -- Robert A. Heinlein

I have been using protocol buffers on and off for around 10 years. The Wikipedia page is here: https://en.wikipedia.org/wiki/Protocol_Buffers and the documentation is at: https://protobuf.dev/. Originating at Google, these can be quite handy when you need a language agnostic way of passing around data. It is smaller and faster than XML https://en.wikipedia.org/wiki/XML, JSON https://en.wikipedia.org/wiki/JSON, or YAML https://en.wikipedia.org/wiki/YAML, especially when combined with gRPC: https://en.wikipedia.org/wiki/GRPC.

Protocol buffer’s limitations include:

·         doesn’t have a formal standard

·         lacks compression

·         not well suited to non-Object Oriented languages like Fortran

·         doesn’t work well with large multi-dimensional arrays of floating point numbers

See https://protobuf.dev/overview/#not-good-fit.

That said, it works extremely well for most computer networking applications!

Github Files

Sample files for this project are at: https://github.com/mday299/keypuncher/tree/main/Networking/protobuf/intro

Installing Protocol Buffers

I’ll be installing this on Ubuntu 22.04 via Visual Studio Code following the instructions in the README -- see https://github.com/protocolbuffers/protobuf/blob/main/src/README.md. However, we will be using CMake https://en.wikipedia.org/wiki/CMake instead of Bazel https://en.wikipedia.org/wiki/Bazel_(software) & Abseil https://abseil.io/ to install so we can skip installation of these libraries. 

Installation of protocol buffers is also possible for other Linux flavors, but you will need to locate the appropriate packages for your distro or build from source.

Installation for Windows requires you to first install CMake https://cmake.org/ and Git for Windows https://git-scm.com/download/win. See instructions here: https://github.com/protocolbuffers/protobuf/blob/main/cmake/README.md

As I am only installing for Ubuntu and not Windows you are on your own unfortunately. I tried briefly but as Google is famously not a fan of Microsoft I gave up after a few minutes. Let me know in the comments if I need to provide more detailed instructions for protocol buffers on Windows.

Instructions are not on this site for the Mac -- a search engine is your friend in that case.

Open up a command prompt on Ubuntu 22.04 and type:

sudo apt-get install build-essential g++ git cmake protobuf-compiler libprotobuf-dev

If you wish to install Visual Studio Code for Ubuntu follow these instructions on my site: https://www.keypuncher.net/blog/visual-studio-code.

Your First .proto File

This is covered on this documentation page: https://protobuf.dev/getting-started/cpptutorial/.

Enter the following in your favorite IDE with the file name “test.proto”:

syntax = "proto3";
package example;

message Position {
    int32 aircraft_id = 1;
    string aircraft_name = 2;
    float lat_deg = 3;
    float lon_deg = 4;
    float alt_msl = 5; 
    AltUnits alt_units = 6;

    enum AltUnits {
        METERS = 0;
        FEET = 1;
    }
}

Next add a file named test_proto.cpp as follows:

#include <iostream>

#include "test.pb.h"

void printPosition(const example::Position& position) {
    std::cout << "Aircraft ID: " << position.aircraft_id() << std::endl;
    std::cout << "Aircraft Name: " << position.aircraft_name() << std::endl;
    std::cout << "Latitude: " << position.lat_deg() << std::endl;
    std::cout << "Longitude: " << position.lon_deg() << std::endl;
    std::cout << "Altitude: " << position.alt_msl() << std::endl;
    std::cout << "Altitude Units: " << (position.alt_units() == example::Position::METERS ? "Meters" : "Feet") << std::endl;
}

int main(int argc, char* argv[]) {
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    example::Position pos;
    pos.set_aircraft_id(1);
    pos.set_aircraft_name("ac 1");
    pos.set_lat_deg(32.5);
    pos.set_lon_deg(-33.4);
    pos.set_alt_msl(100.1);
    pos.set_alt_units(example::Position::METERS); 

    printPosition(pos);

    return 0;
}

Now we are ready for the CMakeLists.txt file!

CMake Build

In the same directory as you put the other files add a file called CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project (test_proto)

find_package(Protobuf REQUIRED)
include_directories(${Protobuf_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})

set(PROTO_FILE test.proto)
set(PROTO_SRCS ${CMAKE_CURRENT_BINARY_DIR}/test.pb.cc)
set(PROTO_HDRS ${CMAKE_CURRENT_BINARY_DIR}/test.pb.h)

add_custom_command(
    OUTPUT ${PROTO_SRCS} ${PROTO_HDRS}
    COMMAND ${Protobuf_PROTOC_EXECUTABLE}
    ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR} -I ${CMAKE_CURRENT_SOURCE_DIR} ${PROTO_FILE}
    DEPENDS ${PROTO_FILE}
    COMMENT "Running protoc on ${PROTO_FILE}"
    VERBATIM
)

add_custom_target(generate_proto_files DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})

add_executable(test_proto_wrapper test_proto.cpp ${PROTO_SRCS} ${PROTO_HDRS} )
add_dependencies(test_proto_wrapper generate_proto_files)
target_link_libraries(test_proto_wrapper ${Protobuf_LIBRARIES})

Next, open a command prompt inside that same directory and type:

mkdir build
cd build
cmake ..
make

If all went well then you should see a “test_proto_wrapper” file in your build folder. This is the C++ executable and on my machine when I run then file I get:

./test_proto_wrapper 
Aircraft ID: 1
Aircraft Name: ac 1
Latitude: 32.5
Longitude: -33.4
Altitude: 100.1
Altitude Units: Meters

That’s it! You have successfully built your first .proto file in C++ with CMake!

Manually Building With g++

You can build with either CMake or g++ (I prefer CMake). To build with g++ enter this:

Open a terminal in the same directory you put test.proto in and enter:

protoc -I. --cpp_out=. test.proto

This will generate two files: test.pb.h and test.pb.cc. As they are autogenerated and rather verbose we will not go into detail about them, though you are free to look at them and they are covered in the documentation at https://protobuf.dev/getting-started/cpptutorial/#protobuf-api. Just don’t change them! Remember to recompile with protoc (see https://protobuf.dev/reference/cpp/cpp-generated/#invocation) whenever you change your .proto file

Troubleshooting

Note there is a nasty CMake bug that prevented me from building this on Ubuntu 20.04. See https://stackoverflow.com/questions/56704546/protobuf-compiler-version-doesnt-match-library-version-3-6-1-when-not-using-s and https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4121.

Credits

Protocol Buffers github repo: https://github.com/protocolbuffers/protobuf

Previous
Previous

A Deep Dive Into Remote Procedure Calls

Next
Next

Tradeoffs of Lambda Functions