views:

93

answers:

3

Hi all,

I have a C function that writes some data to a text file. The data consists of floats, ints and strings. it looks something like this:

writeAsTextFile(  mystruct_t* myStructWithIntsFloatsAndStrings , const char* fileName);

for doing this I use calls to fprintf;

Now I would like to write the same data but as binary. I could write a second function writeAsBinaryFile and use calls to fwrite instead. But then everytime I will make change the design of mystruct_t I will have to modify both writeAsTextFile and writeAsBinaryFile. And Of course the corresponding readAsTextFile and readAsBinaryFile. On top of this this will increase codesize. Therefore I would like to have one single generic function with one bin-or-text argument that would look like this:

writeToFile( mystruct_t* myStructWithIntsFloatsAndStrings , const char* fileName, myEnumType_t eOption)

where option would be an enum eBin = 0 and eTxt =1 for instance. Depending on eOption, the function would write binary or text data.

I am not sure what would be the best way to achieve this. Should I use fwrite also for writing as text, Should I try to use macros? (I have seen use of the ## directive somewhere but never used it ), or switch/ifs statements everywhere I need to write to file? Or shall I write a generic function like myWriteFunction( void *data, char const type, myEnumType_t eOption)

that would be called by writeToFile?

I am not very familiar with using fread/fwrite and macros so any best practice comments, ideas etc is welcome,

Thanks

Baba

+1  A: 

For your situation, simply make a wrapping function:

writeToFile(...,bool isBinary) {
  if (isBinary) {
    // write as binary file
  } else {
    // write as text file
  }
}

As far as MACROS go, they are only useful if you want ALL operations to be of either binary or text:

#ifdef __BINARY
#define WriteToFile(a,b,c,d,e) WriteToBinary(a,b,c,d,e)
#else
#define WriteToFile(a,b,c,d,e) WriteToText(a,b,c,d,e)
#endif

This is used in the winAPI to switch between ascii functions and wide character functions.

BTW: If your struct contains char* or std::string, then the string contents won't be copied, just it's address. This applies to any other pointers aswell, such as int* or a std::vector.

Alexander Rafferty
A: 

First, don't combine the functions, there is practical no savings.

Second, you are going to have to write a new write as text function every time you use the structure, there is no way around that (except some non standard serialization library).

Third, as long as you never change the order of the struct members, and only add new members to the end of the struct, you will never need to write a new binary writer or reader. In order to accomplish this, you should write the size of the structure to the file and then write the structure to the file.

This way, your read function will read the size of the structure (When it was written) and will know how many bytes to read. If your structure changes, your program will then be able to read the portions of the from the old struct version, and the new members at the end of the struct will be uninitialized.

EDIT

Writing a struct with pointers will write the value of the pointer. You have to be very careful when you read the structure, because the pointer will point to an essentially random peace of memory.b

If you want to maintain the relationships between structs you will have to come up with your own mechanism. This varies in difficulty, you will have to come up with some predefined order for writing the structs in, and the rebuilding all of the pointers when you read the struct.

mikerobi
Thanks. I should have mentioned that my struct contains pointers to arrays of floats, and various other structs containing arrays of floats, ints etc. I could keep track of the total size of mystruct but even then, when I call fwrite to write the entire struct my guess is that only the pointers to my allocated arrays will be copie d and not the content of the array. Am I right?
Baba
@Baba, updated my response to address that.
mikerobi
+1  A: 

You could create a couple of functions for writing various types of data in your struct:

writeInt(File *f, myEnumType_t eOption, int data);
writeFloat(File *f, myEnumType_t eOption, float data);
writeFloatArray(File *f, myEnumType_t eOption, float *data, size_t n_data);

.. then the binary-or-text test is hidden in each of those. Your main struct-writing function would look like (with error checking omitted):

writeToFile(mystruct_t *myStruct, const char *fileName, myEnumType_t eOption)
{
    const char *fmode = eOption == EOPT_BIN ? "wb" : "w";
    FILE *f = fopen(filename, fmode);

    writeInt(f, eOption, myStruct->a);
    writeInt(f, eOption, myStruct->b);
    writeFloatArray(f, eOption, myStruct->values, myStruct->n_values);
    /* ... */
 }

So a change to the structure's layout only has to change one place.

You can also implement different writing functions for different application-level "types" - for example writeTemperature() might be distinct from a generic writeFloat().

caf