Skip to content

[C++] Generate extra header files #1041

Open
@szymonwieloch

Description

@szymonwieloch

While the current generator relieves us from writing a lot of code manually, there are still several things that could be generated that would be very helpful and would reduce the effort of working with SBE. We already have few things we generate from the XML file and it only took few lines of Python code per feature, so it should be easy and straightforward.

In my proposal all files are prefixed with "sbe" to avoid collisions with already existing messages or types.

Forward message declarations - sbeFwd.h

In our code we have files that declare functions that handle all messages types in the given protocol. To improve build speeds we generally declare all messages types. Whenever we extend or change the protocol, code gets re-generated and we have to manually update the list of declarations. It would be better if those declarations were maintained automatically.

This file could contain declarations of message classes. This is very useful to speed up the build process - you can include only declarations instead of full message headers. Example:

#pragma once
namespace Protocol{
class Message1;
class Message2;
} // namespace Protocol

Include complete protocol - sbeAll.h

In our source code we do have files that handle all messages from the given protocol. Currently whenever a new message gets added, a new include needs to be added manually. Instead we would prefer to have just one header that includes the complete protocol:

# pragma once
#include "MessageHeader.h"
#include "Message1.h"
#include "Message2.h"

Automatic parsing and dispatch of messages - sbeParser.h

Currently whenever a new message gets added, we need to manually handle it in a switch statement. Instead we would prefer to use an automatically generated parser. The code would look roughly like this:

#pragman once
namespace Protocol{
template<typename OnMsg, typename OnUnknown>
void sbeParse(const char * buf, size_t len, OnMsg && onMsg, OnUnknown && onUnknown){
  MessageHeader hdr(buf, size);
  // TODO: check schemaId
  switch(hdr.templateId()){
    case Message1.sbeTemplateId():{
      Message1 msg;
      message.wrapForDecode(hdr.buffer(), hdr.encodedLength(), hdr.blockLength(), hdr.version(), hdr.bufferLength());
      onMsg(msg);
    }
    // same for other message types
    default:
      onUnknown(hdr.templateId());
}
}
} // namespace Protocol

And an example use in code:

class Handler {
  void onMessage(Message1 & msg);
  // Other types go here
};

void handle(const char * buf, size_t len){
  Handler handler;
  Protocol::sbeParse(buf, len,
      [&](auto & msg){handler.onMessage(msg);},
      [](auto templateId){std::cerr << "Unknown message: " << templateId;}
  );
}

Alternatively the onUnknown callback could be replaced with some form of returned error, i. e. std::optional<uint16_t> to return the unknown message ID.

Please notice that for the onMsg can dispatch the message to either a class like in the example above or to a template function or to an overloaded function. So this approach is very flexible. It is also effectively zero-cost.

Some benefits:

  • Easier to use for new programmers. Less bugs with parsing.
  • Less code to maintain.
  • Whenever a new message is added, the code enforces adding a parser or it won't compile. No missed/forgotten messages.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions