Simple Example of Class Encoding

15 min read

15 min read

15 min read

15 min read

15 min read

15 min read

15 min read

15 min read

15 min read

We have separately discussed the primary components of the class encoding: slices, references, and type IDs. To make the preceding discussions more concrete, consider the following class definitions:

Slice
class Base
{
int baseInt;
string baseString;
}
class Derived extends Base
{
bool derivedBool;
string derivedString;
double derivedDouble;
}

Suppose the sender marshals two instances of Derived (for example, as two in-parameters in the same request) with these field values:

First instance:

FieldTypeValueMarshaled size (in bytes)
baseInt
int
99
4
baseString
string
"Hello"
6
derivedBool
bool
true
1
derivedString
string
"World!"
7
derivedDouble
double
3.14
8

Second instance:

FieldTypeValueMarshaled size (in bytes)
baseInt
int
115
4
baseString
string
"Cave"
5
derivedBool
bool
false
1
derivedString
string
"Canem"
6
derivedDouble
double
6.32
8

We describe how to marshal these instances using versions 1.0 and 1.1 of the encoding in separate sections below.

The sender arbitrarily assigns a non-zero identity to each instance. Typically, the sender will simply consecutively number the instances starting at 1. For this example, assume that the two instances have the identities 1 and 2. The marshaled representation for the two instances (assuming that they are marshaled immediately following each other) is shown below:

Marshaled valueSize in bytesTypeByte offset
1 (identity)
4
int
0
0 (marker for class type ID)
1
bool
4
"::Derived" (class type ID)
10
string
5
20 (byte count for slice)
4
int
15
1 (derivedBool)
1
bool
19
"World!" (derivedString)
7
string
20
3.14 (derivedDouble)
8
double
27
0 (marker for class type ID)
1
bool
35
"::Base" (type ID)
7
string
36
14 (byte count for slice)
4
int
43
99 (baseInt)
4
int
47
"Hello" (baseString)
6
string
51
0 (marker for class type ID)
1
bool
57
"::Ice::Object" (class type ID)
14
string
58
5 (byte count for slice)
4
int
72
0 (number of dictionary entries)
1
size
76
2 (identity)
4
int
77
1 (marker for class type ID)
1
bool
81
1 (class type ID)
1
size
82
19 (byte count for slice)
4
int
83
0 (derivedBool)
1
bool
87
"Canem" (derivedString)
6
string
88
6.32 (derivedDouble)
8
double
94
1 (marker for class type ID)
1
bool
102
2 (class type ID)
1
size
103
13 (byte count for slice)
4
int
104
115 (baseInt)
4
int
108
"Cave" (baseString)
5
string
112
1 (marker for class type ID)
1
bool
117
3 (class type ID)
1
size
118
5 (byte count for slice)
4
int
119
0 (number of dictionary entries)
1
size
123

Note that, because classes (like exceptions) are sent as a sequence of slices, the receiver of a class can slice off any derived parts of a class it does not understand. Also note that (as shown in the above table) each class instance contains three slices. The third slice is for the type ::Ice::Object, which is the base type of all classes. The class type ID ::Ice::Object has the number 3 in this example because it is the third distinct type ID that is marshaled by the sender. (See entries at byte offsets 58 and 118 in the above table.) All class instances have this final slice of type ::Ice::Object.

Note that if a class has no fields, a type ID and slice for that class is still marshaled. The byte count of the slice will be 4 in this case, indicating that the slice contains no data.

A leading size value of 1 marks the beginning of an instance, followed by one or more slices.

The marshaled representation for the two instances (assuming that they are marshaled immediately following each other) in the sliced format is shown below:

Marshaled valueSize in bytesTypeByte offset
1 (instance marker)
1
size
0
17 (slice flags: string type ID, size is present)
1
byte
1
"::Derived" (type ID - assigned index1)
10
string
2
20 (byte count for slice)
4
int
12
1 (derivedBool)
1
bool
16
"World!" (derivedString)
7
string
17
3.14 (derivedDouble)
8
double
24
49 (slice flags: string type ID, size is present, last slice)
1
byte
32
"::Base" (type ID - assigned index2)
7
string
33
14 (byte count for slice)
4
int
40
99 (baseInt)
4
int
44
"Hello" (baseString)
6
string
48
1 (instance marker)
1
size
54
18 (slice flags: index type ID, size is present)
1
byte
55
1 (type ID index forDerived)
1
size
56
19 (byte count for slice)
4
int
57
0 (derivedBool)
1
bool
61
"Canem" (derivedString)
6
string
62
6.32 (derivedDouble)
8
double
68
50 (slice flags: index type ID, size is present, last slice)
1
byte
76
2 (type ID index forBase)
1
size
77
13 (byte count for slice)
4
int
78
115 (baseInt)
4
int
82
"Cave" (baseString)
5
string
86

The sliced format allows the receiver of a class to slice off any derived parts of a class it does not understand, as in version 1.0 of the encoding. Although the sliced format provides equivalent functionality to that of version 1.0, it is significantly more efficient, requiring only 91 bytes to encode our example compared to the 124 bytes required by version 1.0. We could reduce the encoded size even further, while still retaining the ability to slice off unknown types, by using compact type IDs.

Note that if a class has no fields, a type ID and slice for that class is still marshaled. The byte count of the slice will be 4 in this case, indicating that the slice contains no data.

The marshaled representation for the two instances (assuming that they are marshaled immediately following each other) in the compact format is shown below:

Marshaled valueSize in bytesTypeByte offset
1 (instance marker)
1
size
0
1 (slice flags: string type ID)
1
byte
1
"::Derived" (type ID - assigned index1)
10
string
2
1 (derivedBool)
1
bool
12
"World!" (derivedString)
7
string
13
3.14 (derivedDouble)
8
double
20
32 (slice flags: last slice)
1
byte
28
99 (baseInt)
4
int
29
"Hello" (baseString)
6
string
33
1 (instance marker)
1
size
39
2 (slice flags: index type ID)
1
byte
40
1 (type ID index forDerived)
1
size
41
0 (derivedBool)
1
bool
42
"Canem" (derivedString)
6
string
43
6.32 (derivedDouble)
8
double
49
32 (slice flags: last slice)
1
byte
57
115 (baseInt)
4
int
58
"Cave" (baseString)
5
string
62

In an effort to conserve bandwidth, the compact format omits certain details that would allow a receiver to slice off derived parts of a class, such as the slice size and the type IDs for base classes. The result is an encoding that requires only 67 bytes for the two sample instances.

Note that if a class has no fields, a type ID and slice for that class is still marshaled. The byte count of the slice will be 4 in this case, indicating that the slice contains no data.

Compact type IDs can be used regardless of the sender's chosen format. For the sake of example, we will use compact type IDs together with the compact format to produce the smallest encoding possible. The Slice definitions below reflect the addition of the compact type IDs:

Slice
class Base(10)
{
int baseInt;
string baseString;
}
class Derived(11) extends Base
{
bool derivedBool;
string derivedString;
double derivedDouble;
}

We assign the compact type ID 10 to Base and 11 to Derived. Note however that assigning a compact type ID to Base does not affect the size of the encoded data in our example because the compact format omits type IDs altogether for base types.

The marshaled representation for the two instances (assuming that they are marshaled immediately following each other) in the compact format is shown below:

Marshaled valueSize in bytesTypeByte offset
1 (instance marker)
1
size
0
3 (slice flags: compact type ID)
1
byte
1
11 (compact type ID forDerived)
1
size
2
1 (derivedBool)
1
bool
3
"World!" (derivedString)
7
string
4
3.14 (derivedDouble)
8
double
11
32 (slice flags: last slice)
1
byte
19
99 (baseInt)
4
int
20
"Hello" (baseString)
6
string
24
1 (instance marker)
1
size
30
3 (slice flags: compact type ID)
1
byte
31
11 (compact type ID forDerived)
1
size
32
0 (derivedBool)
1
bool
33
"Canem" (derivedString)
6
string
34
6.32 (derivedDouble)
8
double
40
32 (slice flags: last slice)
1
byte
48
115 (baseInt)
4
int
49
"Cave" (baseString)
5
string
53

Substituting a compact type ID for its string equivalent reduces the encoded size for the two instances by another nine bytes to 58, less than half the size of version 1.0.