Generalising Message Implementation
Previous chapters described MessageBase class, which provided implementation for some portions of polymorphic behaviour defined in the interface class Message. Such implementation eliminated common boilerplate code used in every ActualMessage* class.
This chapter is going to generalise the implementation of MessageBase into the generic comms::MessageBase class, which is communication protocol independent and can be re-used in any other development.
The generic comms::MessageBase class must be able to:
provide the ID of the message, i.e. implement the
idImpl()virtual member function, when such ID is known at compile time.
provide common dispatch functionality, i.e. implement
dispatchImpl()virtual member function, described in
Message / Dispatching and Handling chapter.
support extension of the default message interface, described in
Message / Extending Interface chapter.
automate common operations on fields, i.e. implement
readImpl(),writeImpl(),lengthImpl(), etc..., described inFields / Automating Basic Operations chapter.
Just like common comms::Message interface class, the comms::MessageBase will also receive options to define its behaviour.
namespace comms
{
template <typename TBase, typename... TOptions>
class MessageBase : public TBase
{
...
};
} // namespace commsNote, that the comms::MessageBase class receives its base class as a template parameter. It is expected to be any variant of comms::Message or any extended interface class, which inherits from comms::Message.
The supported options may include:
Parsing the Options
The options provided to the comm::MessageBase class need to be parsed in a very similar way as it was with comms::Message in the previous chapter.
Starting with initial version of the options struct:
and replacing the initial value of the appropriate variable with new ones, when appropriate option is discovered:
Assemble the Required Implementation
Just like with building custom message interface, there is a need to create chunks of implementation parts and connect them using inheritance based on used options.
NOTE, that single option comms::option::FieldsImpl<> may facilitate implementation of multiple functions: readImpl(), writeImpl(), lengthImpl(), etc... Every such function was declared due to using a separate option when defining the interface. We'll have to cherry-pick appropriate implementation parts, based on the interface options. As the result, these implementation chunks must be split into separate classes.
All these implementation chunks are connected together using extra helper classes in a very similar way to how the interface chunks where connected:
Add idImpl() if needed
Add dispatchImpl() if needed
Add fields() access if needed
Add readImpl() if needed
And so on for all the required implementation chunks: writeImpl(), lengthImpl(), validImpl(), etc...
The final stage is to connect all the implementation chunks together via inheritance and derive comms::MessageBase class from the result.
NOTE, that existence of the implementation chunk depends not only on the implementation options provided to comms::MessageBase, but also on the interface options provided to comms::Message. For example, writeImpl() must be added only if comms::Message interface includes write() member function (comms::option::WriteIterator<> option was used) and implementation option which adds support for fields (comms::option::FieldsImpl<>) was passed to comms::MessageBase.
The implementation builder helper class looks as following:
Defining the generic comms::MessageBase:
Please note, that TBase template parameter is passed to MessageImplBuilder<>, which in turn passes it up the chain of possible implementation chunks, and at the end it turns up to be the base class of the whole hierarchy.
The full hierarchy of classes presented at the image below. 
The total number of used classes may seem scary, but there are only two, which are of any particular interest to us when implementing communication protocol. It's comms::Message to specify the interface and comms::MessageBase to provide default implementation of particular functions. All the rest are just implementation details.
Summary
After all this work our library contains generic comms::Message class, that defines the interface, as well as generic comms::MessageBase class, that provides default implementation for required polymorphic functionality.
Let's define a custom communication protocol which uses little endian for data serialisation and has numeric message ID type defined with the enumeration below:
Assuming we have relevant field classes in place (see Fields chapter), let's define custom ActualMessage1 that contains two integer value fields: 2 bytes unsigned value and 1 byte signed value.
That's it, no extra member functions are needed to be implemented, unless the message interface class is extended one. Note, that the implementation of the ActualMessage1 is completely generic and doesn't depend on the actual message interface. It can be reused in any application with any runtime environment that uses our custom protocol.
The interface class is defined according to the requirements of the application, that uses the implementation of the defined protocol.
For convenience the protocol messages should be redefined with appropriate interface:
Last updated
Was this helpful?