One way to do this is to make static const tables of data that describe your structures so that an simple read/write engine can work with them.
You need to define a structure that can represent everthing you need to know to read or write a single field of a single structure.
typedef struct {
char * name;
size_t offset;
size_t size;
int format_as;
void* format_struct; // if format_as & IS_STRUCT, this is the structure type
} field_info_t
enum {
AS_CHAR =1,
AS_SHORT,
AS_LONG,
// add other types here
AS_MASK = 0xFF,
// these flags can be OR'd with type to refine the behavior
IS_POINTER = 0x100,
IS_STRUCT = 0x200,
};
Then build tables of these that describe all of your data structures.
#define FIELD_OFF(type, field) ((size_t)(LONG_PTR)&(((type *)0)->field))
#define FIELD_SIZE(type, field) (sizeof(((type *)0)->field))
static const field_info_t g_fs_directory_table_item_table[] = {
{ "inode",
FIELD_OFF(fs_directory_table_item_t, inode),
FIELD_SIZE(fs_directory_table_item_t, inode),
AS_LONG,
NULL
},
{ "filename",
FIELD_OFF(fs_directory_table_item_t, filename),
sizeof(filename_t),
AS_CHAR | IS_POINTER,
NULL
},
{ "other_field",
FIELD_OFF(fs_directory_table_item_t, other_field),
FIELD_SIZE(fs_directory_table_item_t, other_field),
AS_STRUCT,
&some_other_field_table,
},
};
And then read and write engines that take a pointer to a structure, and a pointer to the table describing the structure and read/write the various fields.
void ReadStructure(FILE * fh, void * pStruct, field_info_t * pFields, int num_fields)
{
// this is just a rough sketch of the code.
for (int ii = 0; ii < num_fields; ++ii)
{
int * field_size = pFields[ii].size;
char * pfield = (char*)pStruct + pFields[ii].offset;
if (pFields[ii].format_as & AS_POINTER)
pfield = *(char**)pfield;
switch (pFields[ii].format_as & AS_MASK)
{
case AS_CHAR:
....
}
}
}
void WriteStructure(FILE * fh, void * pStruct, field_info_t * pFields, int num_fields);
You still end up having to maintain a field_info_t
array for each of your data structures, but once you have it, you can read, write, validate and pretty-print your data with a set of fairly simple functions.