|
| 1 | +# Tutorial 21 |
| 2 | +Cast between different field types. |
| 3 | + |
| 4 | +There maybe a case when the the same serialized bytes need to be interepreted in different ways. This tutorial |
| 5 | +implements the following framing: |
| 6 | + |
| 7 | +``` |
| 8 | +SIZE (2 bytes) | ID (1 byte) | FLAGS (1 byte) | PAYLOAD |
| 9 | +``` |
| 10 | + |
| 11 | +The [schema](dsl/schema.xml) file defines two different messages `Msg1` and `Msg2`. The contents |
| 12 | +of the messages are not important. The important part is that the `FLAGS` byte in |
| 13 | +the framing needs to be interpreted differently when handling the `Msg1` and `Msg2`. |
| 14 | + |
| 15 | +As was already described in one of the previous tutorials the value which resides in the framing, but needs |
| 16 | +to be available when message object is handled has to be propagated to the message **<interface>**. |
| 17 | + |
| 18 | +```xml |
| 19 | +<fields> |
| 20 | + <int name="BasicFlags" type="uint8" displayName="Flags" /> |
| 21 | +</fields> |
| 22 | + |
| 23 | +<interface name="Interface" description="Common Interface for all the messages."> |
| 24 | + <ref field="BasicFlags" name="Flags" /> |
| 25 | +</interface> |
| 26 | + |
| 27 | +<frame name="Frame"> |
| 28 | + <size name="Size"> |
| 29 | + <int name="Field" type="uint16" displayName="Size" /> |
| 30 | + </size> |
| 31 | + <id name="Id" field="MsgId" /> |
| 32 | + <value name="Flags" field="BasicFlags" interfaceFieldName="Flags" /> |
| 33 | + <payload name="Data" /> |
| 34 | +</frame> |
| 35 | +``` |
| 36 | + |
| 37 | +The interpretation of the flags for each message is defined as separate fields: |
| 38 | +```xml |
| 39 | +<set name="Msg1Flags" length="1" forceGen="true"> |
| 40 | + <bit name="B0" idx="0" /> |
| 41 | + <bit name="B1" idx="1" /> |
| 42 | + <bit name="B2" idx="2" /> |
| 43 | +</set> |
| 44 | + |
| 45 | +<bitfield name="Msg2Flags" forceGen="true"> |
| 46 | + <enum name="EnumMem" type="uint8" bitLength="2"> |
| 47 | + <validValue name="V0" val="0" /> |
| 48 | + <validValue name="V1" val="1" /> |
| 49 | + <validValue name="V2" val="2" /> |
| 50 | + <validValue name="V3" val="3" /> |
| 51 | + </enum> |
| 52 | + <int name="IntMem" type="uint8" bitLength="6" /> |
| 53 | +</bitfield> |
| 54 | +``` |
| 55 | +Note the presence of **forceGen** property. It forces the code generator to |
| 56 | +generate the definition of the field when it's not referenced anywhere in the |
| 57 | +schema. |
| 58 | + |
| 59 | +When sending `Msg1` and `Msg2` the [ClientSession](src/ClientSession.cpp) uses |
| 60 | +[comms::field_cast()](https://commschamp.github.io/comms_doc/cast_8h.html) stand alone function |
| 61 | +to cast between the fields values: |
| 62 | +```cpp |
| 63 | +void ClientSession::sendMsg1() |
| 64 | +{ |
| 65 | + Msg1 msg; |
| 66 | + |
| 67 | + // prepare flags of Msg1 |
| 68 | + using Msg1Flags = tutorial21::field::Msg1Flags<ClientProtocolOptions>; |
| 69 | + Msg1Flags flags; |
| 70 | + flags.setBitValue_B1(true); |
| 71 | + |
| 72 | + // Assign the flags |
| 73 | + msg.transportField_flags() = comms::field_cast<Message::TransportField_flags>(flags); |
| 74 | + |
| 75 | + sendMessage(msg); |
| 76 | +} |
| 77 | + |
| 78 | +void ClientSession::sendMsg2() |
| 79 | +{ |
| 80 | + Msg2 msg; |
| 81 | + |
| 82 | + // prepare flags of Msg2 |
| 83 | + using Msg2Flags = tutorial21::field::Msg2Flags<ClientProtocolOptions>; |
| 84 | + Msg2Flags flags; |
| 85 | + flags.field_enumMem().value() = Msg2Flags::Field_enumMem::ValueType::V1; |
| 86 | + flags.field_intMem().value() = 111; |
| 87 | + |
| 88 | + // Assign the flags |
| 89 | + msg.transportField_flags() = comms::field_cast<Message::TransportField_flags>(flags); |
| 90 | + |
| 91 | + sendMessage(msg); |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +When the same messages are received back from the echo server, the cast is performed in opposite |
| 96 | +direction: |
| 97 | +```cpp |
| 98 | +void ClientSession::handle(Msg1& msg) |
| 99 | +{ |
| 100 | + ... |
| 101 | + using Msg1Flags = tutorial21::field::Msg1Flags<ClientProtocolOptions>; |
| 102 | + auto flags = comms::field_cast<Msg1Flags>(msg.transportField_flags()); |
| 103 | + printSetField(flags); |
| 104 | + ... |
| 105 | +} |
| 106 | + |
| 107 | +void ClientSession::handle(Msg2& msg) |
| 108 | +{ |
| 109 | + ... |
| 110 | + using Msg2Flags = tutorial21::field::Msg2Flags<ClientProtocolOptions>; |
| 111 | + auto flags = comms::field_cast<Msg2Flags>(msg.transportField_flags()); |
| 112 | + printEnumField(flags.field_enumMem()); |
| 113 | + printIntField(flags.field_intMem()); |
| 114 | + ... |
| 115 | +} |
| 116 | +``` |
| 117 | +
|
| 118 | +The **comms::field_cast()** function is defined in [comms/cast.h](https://commschamp.github.io/comms_doc/cast_8h.html) |
| 119 | +file. Relevant include statement may be required: |
| 120 | +```cpp |
| 121 | +#include "comms/cast.h" |
| 122 | +``` |
| 123 | + |
| 124 | +**IMPORTANT**: For the cast operation to be successful the field types must have the |
| 125 | +same length. |
| 126 | + |
| 127 | +According to the function documentation, the [COMMS Library](https://github.com/commschamp/comms) |
| 128 | +performs some compile time analysis and will do a simple `static_cast` between the contained values |
| 129 | +if their value types are convertible. Otherwise the write + read operations will be performed, i.e. the source field |
| 130 | +will be written into a temporary buffer, and the target field will perform a read operation from that buffer. |
| 131 | + |
| 132 | +In case of this tutorial the conversion between `BasicFlags` and `Msg1Flags` flags is simple `static_cast` |
| 133 | +(because their inner `ValueType` types are convertible), |
| 134 | +while conversion between `BasicFlags` and `Msg2Flags` will be performed using write to and read from |
| 135 | +a temporary buffer (because their inner `ValueType` types are NOT convertible). |
| 136 | + |
| 137 | +## Summary |
| 138 | + |
| 139 | +- Cast between fields of different types is performed using **comms::field_cast()** |
| 140 | + stand alone function defined in [comms/cast.h](https://commschamp.github.io/comms_doc/cast_8h.html) |
| 141 | +- For the cast to be successful the fields must be of the same length. |
| 142 | +- The [COMMS Library](https://github.com/commschamp/comms) implements the field cast using |
| 143 | + `static_cast` when field's inner `ValueType` types are convertible and uses temporary buffer |
| 144 | + with write / read operations when such conversion is not possible. |
| 145 | + |
| 146 | +[Read Previous Tutorial](../tutorial20) <----------------------- |
0 commit comments