在C ++中实现二进制通信协议(用于嵌入式系统)(译文)
By S.F.
本文链接 https://www.kyfws.com/news/implementing-binary-communication-protocols-in-cpl/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 23 分钟阅读 - 11076 个词 阅读量 0在C ++中实现二进制通信协议(用于嵌入式系统)(译文)
原文地址:https://www.codeproject.com/Articles/1278887/Implementing-Binary-Communication-Protocols-in-Cpl
原文作者:Alex Robenko
译文由本站翻译
前言
Easy compile-time configurable implementation of binary communication protocols in C++11 C ++ 11中二进制通信协议的易于编译时可配置实现 This article is about easy and compile-time configurable implementation of binary communication protocols using C++11 programming language. 本文是关于使用C ++ 11编程语言的二进制通信协议的简单且可编译时可配置的实现.
(Introduction) 介绍
(This article is about) 这篇文章是关于**(*easy*) 简单**(*and*) 和**(*compile-time configurable*) 编译时可配置**(*implementation of binary communication protocols using C++11 programming language, with the main focus on embedded systems (including bare-metal ones).*) 使用C ++ 11编程语言实现二进制通信协议,主要侧重于嵌入式系统(包括裸机). (*Interested? Then buckle up and read on.*) 有兴趣吗然后扣紧并继续阅读.
(Background) 背景
(Almost every electronic device/component nowadays has to be able to communicate with other devices, components, or the outside world over some I/O link. Such communication is implemented using various communication protocols, which are notorious for requiring a significant amount of boilerplate code to be written. The implementation of these protocols can be a tedious, time consuming and error-prone process. Therefore, there is a growing tendency among developers to use third party code generators for data (de)serialization. Usually, such tools receive description of the protocol data structures in separate source file(s) with a custom grammar, and generate appropriate (de)serialization code and necessary abstractions to access the data.) 如今,几乎每个电子设备/组件都必须能够通过某些I/O链接与其他设备,组件或外界通信.这种通信是使用各种通信协议实现的,这些协议以要求写入大量的样板代码而臭名昭著.这些协议的实现可能是一个繁琐,耗时且容易出错的过程.因此,开发人员中越来越有使用第三方代码生成器进行数据(反序列化)的趋势.通常,此类工具使用自定义语法在单独的源文件中接收协议数据结构的描述,并生成适当的(反序列化)代码和访问数据所需的抽象. (The main problem with the existing tools is that their major purpose is data structures serialization and/or facilitation of remote procedure calls (RPC). The binary data layout and how the transferred data is going to be used is of much lesser importance. Such tools focus on speed of data serialization and I/O link transfer) 现有工具的主要问题在于它们的主要目的是数据结构序列化和/或简化远程过程调用(RPC).二进制数据布局以及如何使用传输的数据的重要性要小得多.这些工具专注于数据序列化和I/O链接传输的速度**(*rather than*) 而不是**(*on safe handling of malformed data, compile time customization needed by various embedded systems and significantly reducing amount of boilerplate code, which needs to be written to integrate generated code into the product’s code base.*) 安全处理格式错误的数据,各种嵌入式系统所需的编译时间自定义和显着减少样板代码的数量,需要编写样板代码才能将生成的代码集成到产品的代码库中. (*The binary communication protocols, which may serve as an API or control interface of the device, on the other hand, require a different approach. Their specification puts major emphasis on binary data layout, what values are being transferred (data units, scaling factor, special values, etc.) and how the other end is expected to behave on certain values (what values are considered to be valid and how to behave on reception of invalid values). It requires having some extra meta-information attached to described data structures, which needs to propagate and be accessible in the generated code. The existing tools either don’t have means to specify such meta-information (other than in comments), or don’t know what to do with it when provided. As a result, the developer still has to write a significant amount of boilerplate code in order to integrate the generated serialization focused code to be used in binary communication protocol handling.*) 另一方面,可以用作设备的API或控制接口的二进制通信协议需要不同的方法.他们的规范主要着重于二进制数据布局,正在传输的值(数据单位,比例因子,特殊值等)以及另一端在某些值上的预期行为(哪些值被认为是有效的以及如何处理).在接收到无效值时表现出来).它要求在描述的数据结构上附加一些额外的元信息,这些信息需要传播并在生成的代码中可访问.现有工具要么没有办法指定这种元信息(除了注释中),要么不知道提供时如何处理.结果,开发人员仍然必须编写大量的样板代码,以便集成要在二进制通信协议处理中使用的生成的序列化代码.
(Required Features) 必备功能
(As an embedded C++ developer, I require the following features to be available to me, at least to some extent.) 作为嵌入式C ++开发人员,我至少在某种程度上要求具备以下功能.
(Polymorphic Interfaces Configuration) 多态接口配置
(Usually every message definition is implemented (or code generated) as a separate class. In many cases, there is a common code that is applicable to all such message classes. The proper implementation would be to introduce polymorphic behavior with virtual functions (such as reading message data, writing it, calculating serialization length, etc.). However, creation of full interface to be polymorphic may be impractical. Every application that might use the protocol definition code is different. For example, in case many messages are uni-directional, one side () 通常,每个消息定义都作为单独的类实现(或生成的代码).在许多情况下,存在适用于所有此类消息类的通用代码.正确的实现方式是使用虚拟函数引入多态行为(例如,读取消息数据,写入消息,计算序列化长度等).但是,将全接口创建为多态的可能是不切实际的.每个可能使用协议定义代码的应用程序都是不同的.例如,如果许多消息是单向的,则一侧((client) 客户() will require polymorphic write (but not read) for such messages, the one side () )将需要对此类消息进行多态写入(但不能读取),而一侧((server) 服务器() will require the opposite - polymorphic read (but not write). Most of the virtual functions will end up being included in the final binary / image, even if they are unused. It can result in unnecessary code bloat, which may be a problem for embedded systems (especially bare-metal ones).) )将需要相反的操作-多态读取(而不是写入).大多数虚拟函数最终将被包含在最终的二进制文件/映像中,即使它们未使用也是如此.这可能导致不必要的代码膨胀,这对于嵌入式系统(尤其是裸机系统)可能是个问题. (There is a need for compile time, or at least code generation time configuration of the polymorphic interfaces that are going to be used. In the best case scenario, such configuration should be done per message class.) 需要编译时间,或者至少要使用的多态接口的代码生成时间配置.在最佳情况下,应按消息类别进行这种配置.
(Extra Meta Information) 额外的元信息
(There may be a significant amount of extra meta information that comes with the protocol definition. For example, one of the protocol fields in one of the messages needs to report a distance between two points. The protocol designer has decided to report the distance in) 协议定义可能附带大量额外的元信息.例如,消息之一中的协议字段之一需要报告两个点之间的距离.协议设计者已决定报告距离**(*centimeters*) 厘米**(*. Such information will probably be written in plain text in protocol specification and if some third party code generator is used, then this information might end up being in the comment section describing the field or a message. However, in most cases, such information will not end up being in generated code. The developer that needs to integrate the generated code into its business logic needs to manually write some boilerplate code to do the math of conversion into the different distance units (such as*) .此类信息可能会在协议规范中以纯文本形式编写,并且如果使用了某些第三方代码生成器,则此信息可能最终会出现在描述该字段或消息的注释部分中.但是,在大多数情况下,此类信息最终不会出现在生成的代码中.需要将生成的代码集成到其业务逻辑中的开发人员需要手动编写一些样板代码,以将数学转换成不同的距离单位(例如**(*meters*) 米**(*) relevant to the application being developed.*) )与正在开发的应用程序相关. (*It is also not very uncommon for the binary protocol being specified at the same time the application that uses it being developed. Imagine that some specific use case pops up during the development, where distance in*) 在开发使用二进制协议的应用程序的同时指定二进制协议也很常见.想象一下,在开发过程中弹出了一些特定的用例,**(*centimeters*) 厘米**(*doesn’t provide sufficient precision. Thanks to the fact that protocol specification hasn’t been finalized yet, the developer decides to change the reported distance units from*) 无法提供足够的精度.由于协议规范尚未最终确定,开发人员决定将报告的距离单位更改为**(*centimeters*) 厘米**(*to*) 至**(*millimeters*) 毫米**(*. It means that other developers that might have written their boilerplate code with assumption of distance units being*) .这意味着其他开发人员可能在假设距离单位为**(*centimeters*) 厘米**(*must be aware of the change and not forget to modify their code.*) 必须意识到更改,不要忘记修改其代码. (*Some protocol developers also dislike sending floating point numbers “as-is” over the communication link. In many such cases, floating point numbers are multiplied by some predefined value and sent over the I/O links as integers while the remaining fraction after the decimal point is dropped. The other side does the opposite operation on reception of the message, i.e., dividing the received integer by the same predefined value to get the floating point number. Which predefined value to use for such scaling is also meta information, which is not transferred “on the wire” and should be present in the generated or manually written code of the protocol definition.*) 一些协议开发人员还不喜欢通过通信链接按原样发送浮点数.在许多此类情况下,浮点数会乘以某个预定义值,然后以整数形式通过I/O链接发送,而小数点后的剩余分数将被删除.另一方在接收消息时执行相反的操作,即,将接收到的整数除以相同的预定义值以获得浮点数.用于这种缩放的哪个预定义值也是元信息,该元信息不会"在线"传输,而应该出现在协议定义的生成或手动编写的代码中. (*Also in many cases, the protocol may define some values with special meaning. Let’s assume there is a need to communicate a delay in seconds before some event needs to happen. There should be some special value that indicates*) 同样在许多情况下,协议可能会定义一些具有特殊含义的值.让我们假设需要在某些事件发生之前传达延迟(以秒为单位).应该有一些特殊的值表明**(*infinite*) 无限的**(*duration. Usually it is either*) 持续时间.通常是**(*0*) 0**(*or maximum possible value of the unsigned type being used. The developer that integrates the generated code into the application needs to know what value it is. There is a need to write at least one extra piece of boilerplate code that wraps the special value and gives it a name. Wouldn’t it be better if the generated code contained such helper function already?*) 或正在使用的无符号类型的最大可能值.将生成的代码集成到应用程序中的开发人员需要知道它的价值.需要编写至少一个额外的样板代码,以包装特殊值并为其命名.如果生成的代码已经包含这样的辅助函数,会更好吗? (*Many protocols specify ranges of valid values and expect certain behavior on invalid values. There is an expectation that generated code would provide an ability to inquire that the received value is valid to avoid manually written boilerplate code that checks this information.*) 许多协议指定有效值的范围,并期望无效值具有某些行为.期望生成的代码将提供查询接收到的值是否有效的能力,从而避免手动编写用于检查此信息的样板代码. (*The list of such examples with meta-information that is part of the protocol definition, but not transferred as data of the messages can go on and on. There is a need for the generated code to provide the required functionality to be used by the integration developer without any concern of what to do when the meta-information is modified.*) 具有元信息的此类示例的列表是协议定义的一部分,但由于消息的数据可以不断传输而没有传送.需要生成的代码提供集成开发人员要使用的所需功能,而不必担心修改元信息时该做什么.
(Customization of Data Types) 数据类型的定制
(Most of the currently available solutions for the protocol code generation use hard-coded types for some particular data structures, such as) 当前,用于协议代码生成的大多数解决方案都对某些特定的数据结构使用硬编码类型,例如 std::string
(for strings and/or) 用于字符串和/或 std::vector
(for lists. Also in many cases, the generated functions that perform serialization / deserialization receive their input / output as) 用于列表.同样在许多情况下,执行序列化/反序列化的生成函数将其输入/输出接收为 std::istream
(or) 要么 std::ostream
(. These data structures may be unsuitable for some applications, especially embedded (including bare-metal) ones.) .这些数据结构可能不适用于某些应用程序,尤其是嵌入式(包括裸机)应用程序.
(There is a need to be able to substitute the default data structures with some equivalent replacements, ideally at compile time for selected fields / messages, but globally (during code generation for example) may also be an acceptable solution.) 有必要能够用一些等效替换来替代默认数据结构,理想情况下是在编译时针对选定的字段/消息进行替换,但是全局(例如在生成代码期间)也可能是可接受的解决方案.
(Excluding Exceptions and Dynamic Memory Allocation) 排除异常和动态内存分配
(Many constrained embedded environments make it difficult to use exceptions as well as dynamic memory allocation (especially bare-metal ones). There is a need for an ability to generate protocol definition code that don’t use either of them.) 许多受约束的嵌入式环境使得难以使用异常以及动态内存分配(尤其是裸机).需要一种能够生成不使用任何一个的协议定义代码的能力.
(Efficient Built-In Mapping of Message ID to Type) 消息ID到类型的高效内置映射
(Usually, when new encoded message arrives over I/O link, encoded as raw data, it has some transport framing containing numeric message ID. Unfortunately, most (if not all) of the available protocol code generation solution leave the task of mapping numeric ID into the actual message type (or appropriate handling function) to be written by the developer who integrates the generated code into the business logic. Usually, such code is boilerplate, efficiency of which depends on the competence of the developer.) 通常,当新的编码消息通过I/O链接到达并被编码为原始数据时,它将具有一些包含数字消息ID的传输框架.不幸的是,大多数(如果不是全部)可用的协议代码生成解决方案留下了将数字ID映射到实际消息类型(或适当的处理功能)的任务,该开发人员将把所生成的代码集成到业务逻辑中来编写该消息.通常,这样的代码是样板,其效率取决于开发人员的能力.
(Third Party Protocol Support) 第三方协议支持
(Many of the available protocol generation solutions have their own encoding and framing without any ability to modify it. Using of such tools may be acceptable for new protocols being defined from scratch. However, there are heaps of already defined third party protocols, code for which cannot be generated with such tools.) 许多可用的协议生成解决方案具有其自己的编码和成帧,而无能力对其进行修改.对于从头开始定义的新协议,可以使用此类工具.但是,有许多已经定义的第三方协议,无法使用此类工具为其生成代码.
(Injecting Custom Code) 注入自定义代码
(This requirement complements the) 此要求补充了 (Third Party Protocol Support) 第三方协议支持 (one. Even if a chosen code generation solution supports definition of the third party protocol and its grammar for schema files is very rich, there will always be a protocol containing some small nuance, which cannot be represented correctly using the available grammar. As a result, the generated code may be incorrect and/or incomplete. Proper protocol code generation solution should allow injection of snippets of custom, manually written code.) 一.即使所选择的代码生成解决方案支持第三方协议的定义,并且其用于模式文件的语法非常丰富,但总会有一个协议包含一些细微差别,而这些细微差别无法使用可用语法正确表示.结果,生成的代码可能不正确和/或不完整.正确的协议代码生成解决方案应允许注入定制的,手动编写的代码片段.
(Working Solution) 工作方案
(Unfortunately, I could not find any third party solution that implements most of the) 不幸的是,我找不到能实现大多数 (required features) 所需功能 (listed above. I had no other choice but to implement something of my own. I’d like to present to you the) 以上所列.我别无选择,只能实施自己的东西.我想向您介绍 (CommsChampion Ecosystem) CommsChampion生态系统 (that implements all of the mentioned earlier features. It’s been in development as my side project for the last several years. Now it has a stable API and is ready to be used by a wider public. Further below, I’ll go through the list of the) 实现了前面提到的所有功能.过去几年,它一直在作为我的副项目进行开发.现在,它具有稳定的API,可以为广大公众所使用.在下面,我将详细介绍 (required features) 所需功能 (again and give examples on how my solution provides the required functionality.) 再次给出示例,说明我的解决方案如何提供所需的功能. (At first, there was a) 起初,有一个**(*headers-only*) 仅标头**(*, cross-platform, and very flexible*) ,跨平台且非常灵活 (COMMS library) COMMS库 (*, which allows implementation of the protocol messages, fields and transport framing using simple declarative statements of types and classes definition. Such statements define*) ,它允许使用类型和类定义的简单声明性语句来实现协议消息,字段和传输框架.这样的陈述定义**(*WHAT*) 什么**(*needs to be implemented, the library internals handle the*) 需要实现,库内部处理**(*HOW*) 怎么样**(*part. The internals of the*) 部分.内部的 (COMMS library) COMMS库 (*is mostly template, highly compile-time configurable classes which use multiple meta-programming techniques. As a result, the C++ compiler itself becomes a code generation tool, which brings in only the functionality required by the application, providing the best code size and speed performance.*) 主要是模板,高度可编译时可配置的类,它使用多种元编程技术.结果,C ++编译器本身成为了代码生成工具,它仅引入了应用程序所需的功能,从而提供了最佳的代码大小和速度性能. (*After the library, the*) 经过图书馆, (test tools) 测试工具 (*followed, which allow analysis, debugging, and visualization of the developed protocol, that was implemented using the*) 接下来,可以分析,调试和可视化已开发协议,该协议是使用 (COMMS library) COMMS库 (*. The test tools are generic and provide a common testing environment for all the protocols. All the applications are plug-in based, i.e., plug-ins are used to define I/O socket, data filters, and the custom protocol itself. Such architecture allows easy assembly of various protocol communication stacks. The tools use Qt5 framework for GUI interfaces as well as loading and managing plug-ins. They are intended to be used on the development PC and are not part*) .测试工具是通用的,可为所有协议提供通用的测试环境.所有应用程序都是基于插件的,即,插件用于定义I/O套接字,数据过滤器和自定义协议本身.这样的体系结构允许容易地组装各种协议通信栈.这些工具将Qt5框架用于GUI界面以及加载和管理插件.它们旨在在开发PC上使用,而不是一部分 (COMMS library) COMMS库 (*, although hosted in the same repository.*) ,尽管托管在同一存储库中. (*With time, the*) 随着时间的流逝, (COMMS library) COMMS库 (*grew in features and started requiring more cognitive effort to remember things. It became easier to make mistakes and/or implement the protocol in not a very generic way. As a result, the*) 功能的增长,开始需要更多的认知努力来记住事物.出错和/或以一种不太通用的方式实现协议变得更加容易.结果, (code generator) 代码生成器 (*has followed. It allows definition of the protocol using easy*) 跟着.它允许使用简单的协议定义 (XML based domain specific language) 基于XML的领域特定语言 (*(DSL) and generates*) (DSL)并生成**(*headers-only*) 仅标头**(*library of protocol definition (which in turn uses the types and classes from the mentioned earlier*) 协议定义库(依次使用前面提到的类型和类 (COMMS library) COMMS库 (*), as well as code required to build the protocol plugin for the*) ),以及为构建协议插件所需的代码 (test tools) 测试工具 (*.*) . (*Now, let’s repeat the*) 现在,让我们重复 (required features) 所需功能 (*mentioned earlier and see the solution provided by the*) 前面提到过,请参阅 (CommsChampion Ecosystem) CommsChampion生态系统 (*.*) .
(Polymorphic Interfaces Configuration) 多态接口配置
(The protocol implementation library defines a common interface class for all the messages in the following way.) 协议实现库通过以下方式为所有消息定义了公共接口类.(Note) 注意(: All the types and classes from the) :来自的所有类型和类 (COMMS library) COMMS库 (reside in the) 居住在 comms
(namespace, while the definition of the custom protocol in the examples below will be defined in) 命名空间,而以下示例中的自定义协议的定义将在 my_prot
(namespace, and application code that uses the protocol definition will be in global namespace.) 名称空间,并且使用协议定义的应用程序代码将位于全局名称空间中.
namespace my_prot
{
// Numeric IDs of the protocol messages
enum MsgId : std::uint8_t
{
MsgId_Message1,
MsgId_Message2,
MsgId_Message3,
...
};
// Common interface class for the protocol messages
template <typename... TOptions>
using Interface =
comms::Message<
comms::option::MsgIdType<MsgId>,
TOptions...
>;
} // namespace my_prot
(The default definition passes) 默认定义通过 comms::option::MsgIdType
(to the) 到 comms::Message
((class provided by the) (由 COMMS
(library). It is used to define type used for storing message ID. The code above is equivalent to having it defined like this:) 图书馆).用于定义用于存储消息ID的类型.上面的代码等同于像这样定义它:
namespace my_prot
{
class Interface
{
public:
// Type used for message ID
typedef MsgId MsgIdType;
};
} // namespace my_prot
(The definition of the common interface class uses variadic template parameters, which are used to pass various default functionality extension) 通用接口类的定义使用可变参数模板参数,该参数用于传递各种默认功能扩展**(*options*) 选项**(*. The internals of the*) .内部的 COMMS
(*library parse the provided options and generate extra requested functionality. For example, adding polymorphic retrieval of message ID, required for the custom application, may look like this:*) 库解析提供的选项并生成所需的其他功能.例如,添加自定义应用程序所需的消息ID的多态检索可能如下所示:
using MyAppInterface =
my_prot::Interface<
comms::option::IdInfoInterface // Add extra polymorphic ID retrieval interface
>;
(The code above is equivalent to having the following definition:) 上面的代码等效于具有以下定义:
class MyAppInterface
{
public:
// Type used for message ID (defined by using comms::option::MsgIdType option)
typedef MsgId MsgIdType;
// NVI of polymorphic retrieval of ID
// (defined by using comms::option::IdInfoInterface option)
MsgIdType getId() const
{
return getIdImpl();
}
protected:
// Polymorphic retrieval of ID (defined by using comms::option::IdInfoInterface option)
virtual MsgIdType getIdImpl() const = 0; // implemented in derived class
};
(Note that the) 请注意 COMMS
(library uses) 图书馆用途 (Non-Virtual Interface Idiom (NVI)) 非虚拟接口惯用语(NVI) (to define polymorphic behavior. The non virtual interface wrapper function is there to check various pre- and post-conditions the virtual function might require.) 定义多态行为.非虚拟接口包装器功能可以检查虚拟功能可能需要的各种前提条件.
(The) 的 (COMMS library) COMMS库 (provides multiple extension) 提供多种扩展**(*options*) 选项**(*, which allow definition of various other polymorphic functions. The (current) full list is below:*) ,它允许定义各种其他多态函数. (当前)完整列表如下:
using MyAppInterface =
my_prot::Interface<
comms::option::IdInfoInterface, // Add polymorphic ID
// retrieval interface
comms::option::ReadIterator<const std::uint8_t*>,// Add polymorphic read interface
comms::option::WriteIterator<std::uint8_t*>, // Add polymorphic write interface
comms::option::LengthInfoInterface, // Add polymorphic serialization
// length retrieval
comms::option::ValidCheckInterface, // Add polymorphic contents
// validity check
comms::option::Handler<MyHandler>, // Add polymorphic dispatch to
// handling function interface
comms::option::NameInterface // Add polymorphic retrieval of
// message name
>;
(The detailed explanation of every listed above option is a bit beyond the scope of this article. The) 上面列出的每个选项的详细说明都超出了本文的范围.的 (COMMS library) COMMS库 (has a very detailed documentation that lists and explains every one of them. The magic behind the extension options and how they end up generating extra types and functions is also outside the scope of this article. It is explained in detail in the free e-book I’ve written called) 有一个非常详细的文档,列出并解释了每个文档.扩展选项背后的魔力以及它们最终如何生成额外的类型和函数,这也不在本文的讨论范围之内.在我写的免费电子书中对此进行了详细说明 (Guide to Implementing Communication Protocols in C++) 在C ++中实现通信协议的指南 (.) . (The definition of an actual message may look like this:) 实际消息的定义可能如下所示:
namespace my_prot
{
// Definition of the fields used by Message1
struct Message1Fields
{
// 32 bit unsigned integer, initialized to 0
using F1 =
comms::field::IntValue<
comms::Field<comms::option::BigEndian>, // use big endian serialization
std::uint32_t
>;
// 16 bit signed integer, initialized to 10
using F2 =
comms::field::IntValue<
comms::Field<comms::option::BigEndian>, // use big endian serialization
std::int16_t,
comms::option::DefaultNumValue<10>
>;
// All the message fields bundled in std::tuple.
using All = std::tuple<F1, F2>;
};
// Definition of the actual Message1.
// Template parameter TMsgBase is common interface class required by the application
template <typename TMsgBase>
class Message1 : public
comms::MessageBase<
TMsgBase,
comms::option::StaticNumIdImpl<MsgId_Message1>, // Set message ID known at compile time
comms::option::FieldsImpl<Message1Fields::All>, // Define fields of the message
comms::option::MsgType<SimpleInts<TMsgBase, TOpt> > // Pass the actual message type
// being defined
>
{
public:
... // some small irrelevant at this moment code here
};
} // namespace demo1
(The definition of the actual custom protocol message) 实际的自定义协议消息的定义 my_prot::Message1
(is completely generic. It receives the application specific interface definition class as its template parameter, and based on the required polymorphic interface the) 是完全通用的.它接收特定于应用程序的接口定义类作为其模板参数,并根据所需的多态接口 comms::MessageBase
(, provided by the) ,由 COMMS
(library, does all the magic of determining the required polymorphic interface and implementing the necessary virtual functions, while also inheriting from the provided interface class. How such magic of determining the required polymorphic functionality is implemented is beyond the scope of this article and also explained in details in my free) 库不仅可以确定所需的多态接口并实现必要的虚函数,而且还可以从提供的接口类继承.如何实现确定所需的多态功能的魔术方法超出了本文的范围,并且还将在我的免费文章中详细解释 (Guide to Implementing Communication Protocols in C++) 在C ++中实现通信协议的指南 (e-book.) 电子书.
(So, the usage of such class in the actual application may look like this:) 因此,此类在实际应用程序中的用法可能如下所示:
// Define Message1 relevant to the application
using MyMessage1 = my_prot::Message1<MyAppInterface>;
// Definition of smart pointer holding any protocol message
using MyMessagePtr = std::unique_ptr<MyAppInterface>;
// Holding Message1 by the pointer to its interface
MyMessagePtr msg(new MyMessage1);
// Retrieving serialization length, works only if comms::option::LengthInfoInterface
// was passed as option to interface definition, if not compilation will fail.
std::size_t msgLen = msg->length();
assert(msgLen == 6U); // 4 byte of f1 + 2 bytes of f2
(The code the developer needs to write to integrate protocol definition into the business logic of the application is quite simple. All the complexity is handled by the C++ compiler behind the scenes. However, writing the generic code for the protocol definition may require a bit more of cognitive effort with a bit of knowledge about the) 开发人员将协议定义集成到应用程序的业务逻辑中所需编写的代码非常简单.所有复杂性都由C ++编译器在后台处理.但是,为协议定义编写通用代码可能需要更多的认知工作,并需要一些有关 (COMMS library) COMMS库 (internals. That’s the reason the separate) 内部.这就是分开的原因 (code generator) 代码生成器 (has been developed which takes schema file(s) as an input and generates proper generic protocol definition that is simple to customize and use.) 已经开发了将模式文件作为输入并生成易于定制和使用的适当通用协议定义的开发工具.
(The definition of the) 的定义 Message1
(message from the example above in) 上面示例中的消息 (CommsDSL) 通讯DSL (schema can look like this:) 模式可以如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<schema name="my_prot" endian="big">
<fields>
<enum name="MsgId" type="uint8">
<validValue name="Message1" val="0" />
<validValue name="Message2" val="1" />
...
</enum>
</fields>
<message name="Message1" id="MsgId.Message1">
<int name="f1" type="uint32" />
<int name="f2" type="int16" defaultValue="10"/>
</message>
...
</schema>
(The protocol definition code generated out such schema which will look similar to the code example above.) 在这种模式下生成的协议定义代码看起来类似于上面的代码示例.
(Extra Meta Information) 额外的元信息
(The) 的 (COMMS library) COMMS库 (has a built-in support for various units and conversion between them. To follow the example from above with distance, such field could be defined as below:) 具有对各种单元及其之间转换的内置支持.为了从上方跟随距离示例,可以如下定义该字段:
namespace my_prot
{
using Distance =
comms::field::IntValue<
comms::Field<comms::option::BigEndian>, // Big endian serialization
std::uint32_t, // 4 bytes serialization
comms::option::UnitsCentimeters // Contains centimeters
>;
} // namespace my_prot
(When such field is deserialized and its value in) 当该字段反序列化并且其值在**(*meters*) 米**(*is needed, the units retrieval functionality, provided by the*) 需要时,由 (COMMS library) COMMS库 (*can be used.*) 可以使用.
my_prot::Distance distanceField = ...; // Some value assigned
double distanceInMeters = comms::units::getMeters<double>(distanceField);
(In case the definition of the field needs to be changed to contain) 万一该字段的定义需要更改为包含**(*millimeters*) 毫米**(*instead, the client code doesn’t need to be changed, just recompiled. The compiler will change the generated math operation to convert between units.*) 相反,客户端代码无需更改,只需重新编译即可.编译器将更改生成的数学运算以在单位之间转换.
(*The*) 的 (COMMS library) COMMS库 (*also contains compile-time protection against conversion between incompatible units. For example, it is not possible to retrieve*) 还包含编译时保护,以防止不兼容单元之间的转换.例如,无法检索**(*seconds*) 秒**(*out of field containing*) 超出字段包含**(*millimeters*) 毫米**(*, there will be*) , 将有 static_assert
(*failure with appropriate error message.*) 失败并显示相应的错误消息.
(*The language of*) 的语言 (CommsDSL) 通讯DSL (*schema also contains an ability to specify units, which will result in usage of appropriate option passed to the generated field definition.*) 模式还包含指定单位的功能,这将导致使用传递给生成的字段定义的适当选项.
<?xml version="1.0" encoding="UTF-8"?>
<schema name="my_prot" endian="big">
<fields>
<int name="Distance" type="uint32" units="cm" />
</fields>
...
</schema>
(Both) 都 (COMMS library) COMMS库 (and) 和 (CommsDSL) 通讯DSL (support scaling floating point values and serializing them as integers. Such fields are defined as integers with scaling ratio option.) 支持缩放浮点值并将其序列化为整数.这些字段定义为带比例缩放选项的整数.
namespace my_prot
{
using ScaledField =
comms::field::IntValue<
comms::Field<comms::option::BigEndian>, // Big endian serialization
std::int32_t, // 4 bytes serialization
comms::option::ScalingRatio<1, 1000> // Divide by 1000 to get
// floating point value
>;
} // namespace my_prot
(Get/set of the floating point value from/to such field looks like this:) 从/到这样的字段获取/设置浮点值如下所示:
my_prot::ScaledField scaledField;
scaledField.setScaled(1.23f);
float val = scaledField.getScaled<float>();
(The definition of such field in) 此类字段的定义 (CommsDSL) 通讯DSL (may look like this:) 可能看起来像这样:
<?xml version="1.0" encoding="UTF-8"?>
<schema name="my_prot" endian="big">
<fields>
<int name="ScaledField" type="int32" scaling="1/1000" />
</fields>
...
</schema>
(Combining of units with scaling is also possible. Let’s define the distance in) 也可以将单位与缩放比例结合起来.让我们定义距离**(*1/10*) 1/10**(*of millimeters.*) 毫米
namespace my_prot
{
using NewDistance =
comms::field::IntValue<
comms::Field<comms::option::BigEndian>,
std::uint32_t,
comms::option::UnitsMilliimeters,
comms::option::ScalingRatio<1, 10>
>;
} // namespace my_prot
(The) 的 (CommsDSL) 通讯DSL (definition of such field looks like this:) 此类字段的定义如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<schema name="my_prot" endian="big">
<fields>
<int name="NewDistance" type="int32" scaling="1/10" units="mm" />
</fields>
...
</schema>
(The retrieval of distance in meters from such field still looks the same, the compiler generates appropriate math operations taking into account both units and scaling ratio.) 从该字段获取以米为单位的距离看起来仍然相同,编译器会在考虑单位和缩放比例的情况下生成适当的数学运算.
my_prot::NewDistance distanceField = ...; // Some value assigned
double distanceInMeters = comms::units::getMeters<double>(distanceField);
(Specification of special values also supported in) 特殊值的规范也受支持 (CommsDSL) 通讯DSL (schemas. Let’s define duration in seconds where value) 模式.让我们以秒为单位定义持续时间,其中值 0
(means) 手段**(*infinite*) 无限的**(*.*) .
<?xml version="1.0" encoding="UTF-8"?>
<schema name="my_prot" endian="big">
<fields>
<int name="Duration" type="uint32" units="sec" >
<special name="Infinite" val="0" />
</int>
</fields>
...
</schema>
(The code generated for such field will contain appropriate member functions to help check for the special value.) 为该字段生成的代码将包含适当的成员函数,以帮助检查特殊值.
namespace my_prot
{
struct Duration : public
comms::field::IntValue<
comms::Field<comms::option::BigEndian>,
std::uint32_t,
comms::option::UnitsSeconds
>
{
static constexpr std::uint32_t valueInfinite() { return 0U; }
bool isInfinite() const { /* checks that the stored value is 0U */ }
void setInfinite() { /* assigns 0 to the stored value */ }
};
} // namespace my_prot
(Specifying ranges of valid values is also supported. For example:) 还支持指定有效值的范围.例如:
<?xml version="1.0" encoding="UTF-8"?>
<schema name="my_prot" endian="big">
<fields>
<int name="SomeField" type="uint8" >
<validRange value="[0, 20]" />
<validRange value="[40, 60]" />
<validValue value="70" />
</int>
</fields>
...
</schema>
(The example above defines field of 1 byte with unsigned value, where all the values between) 上面的示例使用无符号值定义了1个字节的字段,其中所有值之间 0
(and) 和 20
(, between) 之间 40
(and) 和 60
(, as well as single value) ,以及单值 70
(are considered to be valid.) 被认为是有效的.
(Every field defined by the means of the) 通过以下方式定义的每个字段 (COMMS library) COMMS库 (has) 具有 valid()
(member function, which can be used to inquire whether the field contains a valid value. The example above will result in proper functionality of the) 成员函数,可用于查询该字段是否包含有效值.上面的示例将导致 valid()
(member function. It will return) 成员函数.它将返回 true
(for any value between) 对于介于 0
(and) 和 20
(, between) 之间 40
(and) 和 60
(, and for value) 和价值 70
(. All other values will result in return of) .所有其他值将导致返回 false
(.) .
my_prot::SomeField field = ...; // some value assigned
if (!field.valid()) {
... // do something
}
(Customization of Data Types) 数据类型的定制
(The) 的 (COMMS library) COMMS库 (was designed and implemented with a built-in ability to customize default choices of potentially problematic data structures and/or default behavior. Although the default choices for storage types of strings and lists are) 通过内置的功能来设计和实现该功能,以自定义可能存在问题的数据结构和/或默认行为的默认选择.尽管字符串和列表的存储类型的默认选择是 std::string
(and) 和 std::vector
(, it is possible to substitute it with something else as part of compile-time configuration. For example, some) ,则可以在编译时配置中将其替换为其他内容.例如,一些 string
(field may be defined like this:) 字段可以这样定义:
namespace my_prot
{
template <typename... TOptions>
using SomeString =
comms::field::String<
comms::Field<comms::option::bigendian>,
TOptions... // Extra configuration options
>;
} // namespace my_prot</comms::option::bigendian>
(Such definition of) 这样的定义 string
(field allows passing extra options, which can be used by the application. For example, if the application is being developed for bare-metal platform, which does not allow usage of dynamic memory allocation, it is recommended to pass) 字段允许传递额外的选项,供应用程序使用.例如,如果应用程序是为裸机平台开发的,则不允许使用动态内存分配,则建议通过 comms::option::FixedSizeStorage
(option to substitute usage of) 替代使用的选项 std::string
(by the) 由 string
(container implementation with fixed maximum size, provided by the) 具有固定最大大小的容器实现,由 (COMMS library) COMMS库 (itself (called) 本身(称为 comms::util::StaticString
().) ).
using MyAppString = my_prot::SomeString<comms::option::FixedSizeStorage<32> >;
(It is also possible to use any custom third party storage type, that has the same) 也可以使用具有相同的任何自定义第三方存储类型 public
(interface as) 接口为 std::string
(.) .
using MyAppString =
my_prot::SomeString<comms::option::CustomStorageType<boost::container::string> >;
(The) 的 (code generator) 代码生成器 (is capable (and does it by default) of generating protocol definition code, which allows such compile time customization of various potentially problematic types.) 能够(默认情况下)生成协议定义代码,从而可以对各种可能有问题的类型进行这种编译时定制.
(Excluding Exceptions and Dynamic Memory Allocation) 排除异常和动态内存分配
(The) 的 (COMMS library) COMMS库 (was designed specifically for environments with constrained resources. It does not use RTTI and/or exceptions: the violation of pre- and post-conditions are checked using assertions, while runtime errors are reported via returned status codes.) 专为资源有限的环境而设计.它不使用RTTI和/或异常:使用断言检查对前置条件和后置条件的违反,而通过返回的状态代码报告运行时错误. (The dynamic memory allocation) 动态内存分配**(*is*) 是**(*used by default when new serialized message arrives via I/O link and appropriate message object is created. However, there is an available*) 默认情况下,当新的序列化消息通过I/O链接到达并创建了适当的消息对象时,将使用该消息.但是,有一个可用**(*compile time*) 编译时间**(*configuration*) 组态**(*option*) 选项**(*that replaces dynamic memory allocation with “in-place” one (uses placement new on pre-allocated area),*) 将动态内存分配替换为"就地"分配(在预分配区域上使用新的分配),
(Efficient Built-In Mapping of Message ID to Type) 消息ID到类型的高效内置映射
(The) 的 (COMMS library) COMMS库 (provides multiple built-in options and helper functions which allow efficient mapping of message ID to appropriate type, as well as dispatching the message object to its appropriate handling type. They are described and documented in detail in the available documentation.) 提供了多个内置选项和帮助程序功能,这些功能允许将消息ID有效映射到适当的类型,以及将消息对象分派到其适当的处理类型.在可用文档中对它们进行了详细描述和记录.
(Third Party Protocol Support) 第三方协议支持
(The) 的 (COMMS library) COMMS库 (was designed specifically to provide building blocks and facilitate implementation of the third party protocols. Its architecture allows an easy way to complement the default behavior with protocol specific nuances.) 专门设计用于提供构建块并促进第三方协议的实施.它的体系结构提供了一种简单的方法,以协议特定的细微差别来补充默认行为.
(Injecting Custom Code) 注入自定义代码
(The available) 可用 (code generator) 代码生成器 (also allows injection of custom code snippets instead of or in addition to the code that might have been generated by default.) 还允许注入自定义代码段,以代替默认情况下可能生成的代码或在默认情况下生成的代码之外.
(Summary) 概要
(The main intended audience of) 的主要目标受众 (CommsChampion Ecosystem) CommsChampion生态系统 (is) 是**(*embedded C++*) 嵌入式C ++**(*developers, who have to implement third party (or their own) protocols to communicate to various sensors, other devices or outside world. The non-embedded C++ developers, who don’t have to worry about constrained environment and who don’t require sophisticated code customization, may still find the provided solution convenient and useful, thanks to available constructs for reducing amount of integration boilerplate code.*) 开发人员,他们必须实施第三方(或自己的)协议才能与各种传感器,其他设备或外界进行通信.无需担心受限环境且不需要复杂代码自定义的非嵌入式C ++开发人员,仍然可以使用所提供的解决方案方便而有用,这要归功于可用的结构可以减少集成样板代码的数量. (*Those developers who already have their own implementation of some protocol(s) integrated into their product, may still use the available*) 那些已经将自己的某些协议实现集成到其产品中的开发人员,仍然可以使用可用的 (code generator) 代码生成器 (*as well as*) 以及 (test tools) 测试工具 (*to easily test and debug their work.*) 轻松测试和调试他们的工作.
(Where to Start) 从哪儿开始
(If the available solution caught your interest, please start your learning process by reading “) 如果可用的解决方案引起了您的兴趣,请通过阅读” (Where to Start) 从哪儿开始 (" section of the) 的" (CommsChampion Ecosystem) CommsChampion生态系统 (page.) 页.
(Licensing) 授权
(All the available components of the) 的所有可用组件 (CommsChampion Ecosystem) CommsChampion生态系统 (are free and open source, each one has a separate license. Please refer to the) 是免费和开源的,每个人都有单独的许可证.请参考 (LICENSES) 授权 (page for the details.) 详细信息页面.
(History) 历史
-
(4) 4(th) 日(March, 2019: Initial post) 2019年3月:初始帖子
-
(14) 14(th) 日(August, 2020: License information update) 2020年8月:许可证信息更新
-
(8) 8(th) 日(October, 2020: Fixed links to relocated repositories) 2020年10月:固定链接到重新定位的存储库
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
embedded C++ Advanced 新闻 翻译