The commsdsl2comms is main code generation tool provided by this project. It generates headers only protocol definition code defined using CommsDSL schema file(s).
Note that the output of the commsdsl2comms is CMake project which can be used to install all the necessary files (protocol definition headers as well as CMake confiugration files) into the installation directory.
The commsdsl2comms utility has multiple command line arguments, please
use -h option for the full list as well as default option values.
$> /path/to/commsdsl2comms -h
Below is a summary of most important ones.
In case there are only few schema files, it is possible to pass them at the end of the arguments list.
$> /path/to/commsdsl2comms <args> schema1.xml schema2.xml schema3.xml ...
The schema files will be processed in order of their listing.
In case there are lots of schema files (for example every message
is defined in separate schema file), it is recommended to create a separate
text file with list of all schema files and use -i option.
$> /path/to/commsdsl2comms -i schemas_list.txt ...
The schemas in the list file may use use absolute or relative path. In case
of the latter please also provide absolute path prefix using -p option. The
prefix will be prepended to every relative path inside the list file to locate
the schema file.
$> /path/to/commsdsl2comms -i schemas_list.txt -p /path/to/schemas/dir ...
By default the output CMake project is written to the current directory. It
is possible to change that using -o option.
$> /path/to/commsdsl2comms -o /some/output/dir schema.xml
The commsdsl2comms utility allows injection of custom C++ code into the
generated one in case the default code is incorrect and/or incomplete. For this
purpose -c option with path to directory containing custom code snippets is used.
$> /path/to/commsdsl2comms -c /path/to/custom/code/snippets schema.xml
In order to see what code injection elements are available using what files, temporarily use --code-inject-comments
command line option and review the generated files. The generated code will be populated with
// [CODE INJECT]: comment lines indicating places where code injection is possible.
$> /path/to/commsdsl2comms --code-inject-comments ...
Please read Custom Code section below for more details on how to format and where to place the custom code.
By default the protocol name defined in the schema file(s) is used as the
main namespace for the generated code. It is possible to change it using
-n option.
$> /path/to/commsdsl2comms -n other_ns_name schema.xml
The schema file(s) is expected to specify schema numeric version. By default
the generated code will be for the latest version including all the fields that
were introduced at some stage and omitting all the deprecated and removed
ones. However, the commsdsl2comms
utility allows generation of the code for any version of the protocol by using
--force-schema-version option.
$> /path/to/commsdsl2comms --force-schema-version 2 schema.xml
It is possible to set a semantic version of the generated
code using -V option. The information will be exposed in the Version.h header file
of the generated code.
$> /path/to/commsdsl2comms -V 1.2.3 schema.xml
For version dependent protocols (where the used protocol version is reported
to the other side in transport framing or payload of one of the messages), the
assumed minimal remote version is 0. In means all the fields that were
introduced at later stage will be optional ones that can exist or be missing.
If it is known for sure that the other side of communication won't use some early
versions, it is possible to generate more efficient code by passing -m option.
$> /path/to/commsdsl2comms -m 5 schema.xml
In the example above, all the fields that were introduced before or in version 5, will be regular ones (instead of optional).
The code generated by the commsdsl2comms utility can allow extra compile time customizations (such as choosing custom storage type and/or having extra functionality). There are 3 levels of available customizations:
- full - All fields and messages are going to be customizable.
- limited - Only variable length fields (such as
<string>,<data>, and<list>) and uni-directional messages will be customizable. - none - No fields or messages will allow extra compile time customization.
The recommended (and default) customization level is limited. However, when protocol schema being developed (i.e. new fields / and messages are being constantly added) it is recommended to temporarily use none as customization level in order to reduce (re)compilation times of the target project.
The customization level can be selected using --customization option.
$> /path/to/commsdsl2comms --customization=none schema.xml
The commsdsl2comms utility creates multiple bundles of messages based
on their direction (server vs client) as well as relevant code for dispatching
messages from such bundles to appropriate handlers. It is possible to provide
extra independent list of message types for extra bundles and extra relevant
dispatching code generation. For such purpose \n separated list of message
names needs to be created in a separate file and --extra-messages-bundle option
to be used. The format of the option value is
Name@File, where Name is the name of the bundle, while File is a path
to file containing message names (as defined in CommsDSL schema). The Name
part can be omitted resulting in the name be deduced from the specified file name.
To specify multiple bundles use comma separation
$> /path/to/commsdsl2comms --extra-messages-bundle=Set1:extra-set1.txt,Set2:extra-set2.txt schema.xml
As was already mentioned earlier, commsdsl2comms utility allows injection
of custom C++11 code snippets in the generated code. The
Injecting Custom Code section above described -c
option that can be used to specify directory with custom code snippets.
Every file inside that directory must have the same relative path to the file its
going to update, as the generated file inside the output directory.
Every global field and/or message class will be defined in a separate file and will have the following basic operations:
- construct - Construction (applicable only to messages).
- read - Read operation.
- write - Write operation.
- length - Serialization length calculation.
- valid - Contained value validity check.
- refresh - Bring the contents into consistent state.
- name - Get a descriptive name of the field / message.
In order to replace the default implementation of any of these operations,
the file with the same name (and relative path) must be created, but also have
additional file extension with the operation name. For example let's assume we have
protocol named demo, with message Msg1. The class for the message
will reside in include/demo/message/Msg1.h. In order to replace the default read
operation, there is a need to create include/demo/message/Msg1.h.read file
which will define custom read function. Following the same logic to
replace all the operations there is a need to have the following files:
include/demo/message/Msg1.h.constructinclude/demo/message/Msg1.h.readinclude/demo/message/Msg1.h.writeinclude/demo/message/Msg1.h.lengthinclude/demo/message/Msg1.h.validinclude/demo/message/Msg1.h.refreshinclude/demo/message/Msg1.h.name
The message constructor must contain the actual class name. For the other functions they differ between fields and messages.
Note that for fields the provided functions are expected to have the following signatures.
template <typename TIter>
comms::ErrorStatus read(TIter& iter, std::size_t len);
template <typename TIter>
comms::ErrorStatus write(TIter& iter, std::size_t len) const;
std::size_t length() const;
bool valid() const;
bool refresh();
static const char* name();For messages it is similar, but with do* prefix.
template <typename TIter>
comms::ErrorStatus doRead(TIter& iter, std::size_t len);
template <typename TIter>
comms::ErrorStatus dowrite(TIter& iter, std::size_t len) const;
std::size_t doLength() const;
bool doValid() const;
bool doRefresh();
static const char* doName();To simplify custom code injection the commsdsl2comms also allows injection of the function body while
generating function signature itself. To inject the custom function body code use approprite .x_body file
extension:
include/demo/message/Msg1.h.read_bodyinclude/demo/message/Msg1.h.write_bodyinclude/demo/message/Msg1.h.length_bodyinclude/demo/message/Msg1.h.valid_bodyinclude/demo/message/Msg1.h.refresh_bodyinclude/demo/message/Msg1.h.name_body
It is also possible to add unrelated custom code to public, protected, and/or private areas by using relevant file extension.
include/demo/message/Msg1.h.publicinclude/demo/message/Msg1.h.protectedinclude/demo/message/Msg1.h.private
In case there is a need for a custom destructor and/or assignment operator, use either .construct or .public code injection to define them.
The custom operation may required additional includes, which may be provided by using appropriate definition file with .inc file extension. The contents of this file will be added after all the default includes.
include/demo/message/Msg1.h.inc
It may happen that provided above customization options are not sufficient, and need to be completely rewritten. In this case it is possible to create appropriate file with .replace file extension, contents of which will completely replace the file generated by commsdsl2comms.
include/demo/message/Msg1.h.replace
The commsdsl2comms also allows to reuse the class it generates by default and extend the existing functionality with the new one. It can be achieved by defining a file with .extend file extension. Similar to .replace, it must define the full class, but it is expected to extend and reuse default definition which will have Orig suffix added to its name.
class Msg1 : public Msg1Orig {...};There are cases when commsdsl2comms cannot generate some pieces of code and they must be provided externally. For example, custom checksum algorithm and/or custom framing layer. The custom definition files are expected to be found in the following relative paths:
include/<main_namespace>/frame/checksum/<checksum_name>.hinclude/<main_namespace>/frame/layer/<layer_name>.h
The example of checksum definition can be found at UbloxChecksum.h file from cc.ublox.commsdsl protocol definition.
The example of custom layer can be found at IdAndFlags.h file from cc.mqtt311.commsdsl protocol definition (defines custom ID layer that also contains extra flags) or at Length.h file from cc.mqttsn.commsdsl protocol definition.
Please note that custom layer that replaces <id> one is expected to use
the following template parameters.
/// @tparam TField Used field type
/// @tparam TMessage Interface class
/// @tparam TAllMessages Input messages
/// @tparam TNext Layer Next frame layer
/// @tparam TExtraOpt Extra options passed to @b comms::MsgFactory
template <
typename TField,
typename TMessage,
typename TAllMessages,
typename TNextLayer,
typename... TExtraOpt>
class MyIdLayer : public ...
{
...
};Any other custom layer is expected to use the following template parameters
/// @tparam TField Used field type
/// @tparam TNext Layer Next frame layer
/// @tparam TExtraOpt Extra options passed to the base class
template <
typename TField,
typename TNextLayer,
typename... TExtraOpts>
class MyCustomLayer : public ...
{
...
};NOTE, that customization is available for global fields (including ones that defined in any separate namespace) as well as messages. At this stage injecting custom code to the fields defined inside message body is not supported.
For more code customization examples it is recommended to take a look at the following real-life protocols.
The commsdsl2comms also allows appending any text to any generated file
(not necessarily C++ code). It is done by creating appropriate file
with .append file extension. For example, adding extra logic to the generated CMake
file(s) are possible by creating CMakeLists.txt.append file with custom
content.
The commsdsl2comms utility will also copy all the files, residing in the source directory and not having any of the special extensions (.read, .write, .length, etc...), as-is without modification into the output directory preserving their relative path.