This tutorial shows how to use the Apache Thrift Interface Definition Language to serialize data sent over YARP ports and define interfaces for RPC-based services in YARP Modules.
Apache Thrift allows to define data types and service interfaces in a simple definition file. Taking that file as input, a compiler generates source code which can be used by different client modules and a server.
The following is a summary of the Thrift language reference, with corresponding mapping to YARP (C++) code. Most of it was adapted from
http://thrift.apache.org/
http://diwakergupta.github.com/thrift-missing-guide/
The Thrift type system consists of pre-defined base types, user-defined structs, container types, and service definitions.
bool
: A boolean value (true or false), one byte; mapped to bool
byte
: A signed byte; mapped to int8_t
i16
: A 16-bit signed integer; mapped to int16_t
i32
: A 32-bit signed integer; mapped to int32_t
i64
: A 64-bit signed integer; mapped to int64_t
double
: A 64-bit floating point number; mapped to double
string
: Encoding agnostic text or binary string; mapped to std::string
Note that Thrift does not support unsigned integers.
list<t1>
: An ordered list of elements of type t1. May contain duplicates. Mapped to std::vector
. set<t1>
: An unordered set of unique elements of type t1. Mapped to std::set<t1>
. map<t1, t2>
: A map of strictly unique keys of type t1 to values of type t2. Mapped to std::map<t1, t2>
.Types used in containers many be any valid Thrift type excluding services.
Structs are the basic building blocks in a Thrift IDL. A struct is composed of fields; each field has a unique, positive integer identifier, a type, a name and an optional default value. Example:
Note that structs may contain other structs, and that multiple structs can be defined and referred to within the same Thrift file.
Structs translate to C++ classes that inherit from the yarp::os::idl::WirePortable
class. For each struct, a .h and a .cpp file are created, which contain a definition of the class and an implementation of the default constructor and of the read/write methods of the yarp::os::idl::WirePortable
interface.
In case a certain structure should be translated to an existing YARP type, this can be declared with yarp.name
and, if needed, yarp.includefile
annotations:
Thrift supports C/C++ style typedefs.
Note that there is no trailing semi-colon, and that not only base types but also structs can be used in typedefs. If any typedef or constant value (see Constants) is defined, a <thriftFileName>_common.h
file is generated, which contains all typedefs and constants; this file is automatically included by all the other generated files.
Thrift lets you define constants for use across languages. Complex types and structs are specified using JSON notation.
Note that semi-colon is optional; hex values are valid here. If any typedef (see Typedefs) or constant value is defined, a <thriftFileName>_common.h
file is generated, which contains all typedefs and constants; this file is automatically included by all other generated files.
Enums are specified C-style. Compiler assigns default values starting at 0, but specific integral values (in the range of positive 32-bit integers) can be specified for constants. Hex values are also acceptable.
Note that there is no trailing semi-colon, and that the fully qualified name of the constant must be used when assigning default values. For each enum, a .h
and a .cpp
file are created, which contain the definition of the enum and a helper class that handles number/string conversion for the enum elements.
Namespaces in Thrift are akin to namespaces in C++ or packages in Java: they offer a convenient way of organizing (or isolating) your code. Namespaces may also be used to prevent name clashes between type definitions. Thrift allows you to customize the namespace behavior on a per-language basis. YARP example:
means that all the code in the generated files will be included in
It is often useful to split up Thrift definitions in separate files to ease maintenance, enable reuse and improve modularity/organization. Thrift allows files to include other Thrift files. Included files are looked up in the current directory and by searching relative to the path from which the yarp_idl_to_dir
macro is executed (see Code generation ).
Included objects are accessed using the name of the Thrift file as a prefix (see example in Services). In generated files, the needed header files generated from the PointD.thrift
file will be included with the same inclusion prefix (in this case, firstInterface
).
Service definitions are semantically equivalent to defining an interface (or a pure virtual abstract class) in object-oriented programming. The Thrift compiler generates fully functional client and server stubs that implement the communication routine for the interface. Services contain a collection of method definitions.
A method definition has a return type and arguments, like C code. Note that argument lists are specified using the exact same syntax as field lists in structs. Return types can be primitive types or structs; the oneway
modifier can precede a void
return type to indicate that the client only requests that the server execute the function, but does not wait for an acknowlegment that the execution has completed (asynchronous processing). Default values can be provided for tail arguments; clients can avoid providing values for those parameters, which is especially useful when sending RPC calls via command line, as will be shown in section Complete example.
Services support inheritance: a service may optionally inherit from another service using the extends
keyword.
For each service, a .h
and a .cpp
file are created, which contain the definition of the interface as a class derived from yarp::os::Wire
. The implementation of the read
method to receive commands over a YARP port is provided, as well as the implementation of the command transmission over YARP for function calls performed by a client. The description of how to use this generated code to create server and client modules is provided in sections Server implementation and Client use respectively.
Thrift supports shell-style, C-style multi-line as well as single-line Java/C++ style comments.
Generation of code for a Thrift definition file PointD.thrift
in the firstInterface
directory can be automatically performed by CMake calling the yarp_idl_to_dir
macro:
The macro defines a CMake "advanced" option, ALLOW_IDL_GENERATION
, which is by default set to OFF
if there is already generated code in the desired output directory. Code generation occurs at CMake-configure time only when this option is enabled, otherwise it is assumed that code has already been generated and/or committed.
Upon execution of the macro, the code is generated by the yarpidl_thrift
compiler and copied into the <desired_output_dir>
. In particular, .h
files get copied in the include
subdirectory, while .cpp
files go into the src
subdirectory. The directory structure inside these subdirectories replicates the one of the definition file: since PointD.thrift
is in the firstInterface
directory, .h
files will go to the <desired_output_dir>/include/firstInterface/
folder, and .cpp files will go to <desired_output_dir
>/src/firstInterface/` folder.
You can ask for a list of generated source and header files to be placed in variables for you to refer to later:
You can also get a list of paths to include:
Typical usage of these variables would be something like this:
The purpose of a server is to listen for commands on a YARP port, execute the method that each command refers to, and send back the reply. With Thrift, a server is created from a service
interface class (generated as in section Services), creating an object that implements the methods of that interface, and attaching it to a YARP port.
An altenative solution is to create a YARP module that implements the service interface:
Clients can invoke a remote procedure on the server by simply declaring the interface and attaching it to a YARP port connected to the server.
Simple example:
A complete example of Thrift code generation and server/client creation with CMake is available in example/idl/thrift/
The server can be launched from command line (assuming a yarpserver
is running):
yarp: Port /demoServer active at tcp://10.xxx.xx.xx:10002
From another terminal, the communication on the server port can be eavesdropped with this command:
From yet another terminal, the client can be run with the following command:
yarp: Port /demo/client active at tcp://10.xxx.xx.xx:10004 yarp: Sending output from /demo/client to /demoServer using tcp == get_answer == 42 == add_one == 43 == double_down == 86 == add_point == == done! == yarp: Removing output from /demo/client to /demoServer
Note that RPC calls can also be sent to the server from command line:
get answer Response: 42 get_answer Response: 42 add one 42 Response: 43 double down 43 Response: 86 add point 1 2 3 4 5 6 Response: 5 7 9 add one 1