Documentation:
Serialization of Data and Objects [Remove Frame]
|
Often, your application needs to save and load data in a machine-independent, binary format. This data may be very simple, such as an array of numbers, or it may be a complex networks of objects arranged in some application-defined data structure.
FOX offers some tools to make implementation of such basic save and load facilities in an application fairly straighforward: Serialization and Deserialization. Serialization refers to the process of taking a network of objects and their member data, and turning it into a linear byte stream; deserialization of course refers to the opposite. This process is also sometimes referred to as streaming, flattening, or more prosaically, pickling.
The FXStream classes support streaming of objects and data in a type-safe and architecture-neutral manner; this means that a) your data will be read in the way you wrote it out, and b) streaming works as efficient on little-endian machines as it does on big-endian ones:- there is no byte-order preference.
The FXStream class are extremely flexible, in that you may subclass them ad libitum to implement esoteric applications ranging from compression to encryption, BSD sockets, UNIX pipes, clipboard, drag & drop, and what have you. Code you write to serialize your data may be reused to perform any of these functions simply by substituting the FXStream class upon which they operate.
Once code for an object's serialization has been written, this
streaming capability can be used for a variety of purposes:
Philosophy in
FOX Serialization
|
The FOX Stream classes have been designed with a number of goals in mind:
So How Does It
Work?
|
FXuint data[100],numdata;
// Save my stuff to a stream
void savemystuff(FXStream& stream){
stream << numdata; // Save the number of data values
stream.save(data,numdata); // Save the data
}
// Save stuff to a FILE stream
FXFileStream stream;
stream.open("datafile.dat",FXStreamSave);
savemystuff(stream);
stream.close();
As you see, this is pretty simple. Note that the code fragment doing the actual serialization does not depend on the type of FXStream being used; I recommend simply passing in an FXStream&, so that the same code may be used to serialize to FXFileStreams, FXMemoryStreams or other stream classes as yet to be invented.
From the stream's point of view, things are a bit more
complicated. Saving basic types (FXchar, FXshort, etc) into an FXStream
is done by tradional C++ insertion and extraction operators <<
and >>.
Note that all operators take a reference, rather than a value.
If we would save a value, regular C++ type promotions might be silenty
invoked, and more bytes might be saved than expected; by taking
reference arguments, one has to first store a value into a variable of known
type, then call the insertion operator.
For arrays of basic types, the FXStream class supplies a
few regular member functions called save() and load(), one for each
basic type. Note that FOX also supports a type FXlong; FXlong is
always 64 bits, or 8 bytes.
For objects, things are a more complex. A network of objects can
be saved into a stream, and should be restored upon a load. Of course,
upon load not all objects will occupy the same address as where they
were initially stored from, so pointer-values can not be simpy stored
in the stream:- a translation is necessary.
Also, objects may refer to each other, that is to say, the program's
data structures may have circular references.
Thus, care must be taken to ensure that each object will be saved only
once.
FOX currently implements the object save by means of a hash table to translate object pointers into reference numbers and vice versa. In a nutshell, here's how it works:
To save an object-pointer to the stream: To load an object-pointer from the stream:
In the current implementation, only those objects whose implementation has been compiled into the application can be [de-] serialized.
Future versions of FOX will use the escape code information for additional methods to localize the FXMetaClass objects. In particular, the thinking is that certain object-implementations may live in DLL's (Dynamic Link Libraries) and the escape code will help localize the DLL and pull it in to provide the object implementation. It is clear that this will be a very powerful mechanism, enabling for example drag and drop of objects whose implementations are not a-priori known at the time the application is compiled.
I added the escape code so as to not break people's streamed object files when this capability will be introduced.
Future FOX uses
of Serialization
|
Serialization is not only intended for features such as saving/restoring from files, and drag-and-drop of objects. Future versions of FOX will also allow FOX GUI Widgets to be serialized or deserialized; in fact, it is with this in mind that the two-step [Construct/Create] sequence is so religiously carried out throughout the Library. Once FOX Widgets have been deserialized from either an external file or perhaps from a compiled-in [reswrapped] resource, a GUI can be created in one fell swoop with a single call to FXApp::create().
A FOX GUI Builder will be a program that builds a nice-looking GUI, and then serializes it for incorporation into an application [using reswrap]. Using the escape-code mechanism, the FOX GUI builder will be able to build GUI's that contain Custom Controls or Widgets written by third parties.
Tips and Hints
for Serialization: Byte Swapping
|
Proper use of the serialization mechanism will allow serialized data to be read across different machines, with different byte orders. In the scope of ``predictability,'' FOX's stream mechanism does NOT contain any tags or markers, nor does it contain things like byte order and such, with the exception of course being the saving of object-pointers.
It does however try to help:
FXbool FXStream::isBigEndian();
returns TRUE if the stream is set to big-endian mode, i.e.
items are loaded or saved in most-significant byte first order.
The default is determined by the host machine; architectures like x86
are least significant byte first, and architectures like MIPS are most
significant byte first.
Note that FXbool is defined as FXuchar, NOT as C++ bool.
[I've never been able to find a statement that says how big the
standard type bool is, but I'm pretty sure a char is 1 byte!].
Thus, the following chunk of code may be executed before saving any actual application data:
FXbool endianness=FXStream::isBigEndian();
stream << endianness;
....
save the data
....
Then upon loading:
FXbool endianness;
stream >> endianness;
stream.setBigEndian(endianness);
....
load the data
....
In other words, the bytes are swapped on input, if and only if the byte order of the saving application differs from the loading one.
Tips and Hints
for Serialization: Container Object
|
Many applications have one so-called container object, which may not itself participate in serialization for one reason or another. For example, the FOX FXApp object is normally created by the main startup routine of an application, and will probably never be serialized [although its member data may be].
In order to accomodate references to such an object without saving it, the FXStream class allows you to specify a container object. During serialization, when a pointer to the container object is encountered, only a reference tag is saved to the stream; likewise, on deserialization a reference to the container object is translated into the pointer passed in with the FXStream constructor.
Tips and Hints
for Serialization: Use FX Types
|
FOX defines a number of typedefs for the basic types, such as FXchar, FXshort, and so on. The idea is that the size of these types is fixed, and the same on all implementations; there is an FXASSERT somewhere that will trip if this is not true.
Writing applications that should work on heterogeneous mixes of hardware becomes simpler if variables you intend to serialize are defined in terms of these basic types; for loop variables and such ephemeral things, you may want to use the ``suggested'' system-specific types, as these may be faster.
The type FXlong may NOT be natively supported on all platforms. It represents a 64 bit integer type. Usage of this type may be slower than the regular 32 bit integer types, unless you have a 64 bit computer like x86-64 or ALPHA.
Copyright © 1997-2022 Jeroen van der Zijp |