C Programming/Serialization
From Wikibooks, the open-content textbooks collection
[edit] Serialization
It is often necessary to send or receive complex datastructures to or from another program that may run on a different architecture or may have been designed for different version of the datastructures in question. A typical example is a program that saves its state to a file on exit and then reads it back when started.
The 'send' function will typically start by writing a magic identifier and version to the file or network socket and then proceed to write all the data members one by one (i.e. in serial). If variable length arrays are encountered (e.g. strings), it will either write a length followed by the data or it will write the data followed by a special terminator. The format is often XML or binary in which case the htonl() set of macros may come in handy.
The 'receive' function will be nearly identical : It will read all the items on by one. Variable length arrays are either handled by reading the count followed by the data, or by reading the data until the special terminator is reached.
Since these two functions often follow the same pattern as the declaration of the data(structures), it would be nice if they could all be generated from a common definition.
[edit] X-Macros
X-Macros uses the preprocessor to force the compiler to compile the same piece of text more than once. Sometimes a special file (with extension .def) is included multiple times. For example variables.def may look like this :
INT(value) INT(shift)
In this example the C programming will then look like this :
...
#define INT(var) int var;
#include "variables.def"
#undef INT
...
printf ("version=1\n");
#define INT(var) printf (#var "=%d\n", var);
#include "variables.def"
#undef INT
...
If including a separate file multiple times is undesirable, another macro can be used. For example :
#define VARIABLES INT(value) \
INT(shift)
The #includes can then be replaced with calls to the macro.
[edit] Serialization with versioning
Suppose we want to add additional variables to the above example, but we still want the program to be able to read the old version 1 files. Then we would add a version parameter and a default value parameter to the INT() macro :
#define VARIABLES INT(value,1,0) \
INT(shift,1,0) \
INT(mask,2,0xffff)
...
int inputVer;
#define INT(var,varVer,default) int var;
VARIABLES
#undef INT
...
scanf ("version=%d", &inputVer);
#define INT(var,varVer,default) if (varVer <= inputVer) scanf (#var "=%d", &var); else var = default;
VARIABLES
#undef INT
...
printf ("version=2\n"); /* Always output at highest known version */
#define INT(var,varVer,default) printf (#var "=%d\n", var);
VARIABLES
#undef INT
...