Binary I/O

Jun 14, 2008 at 2:36pm
The program i've created is essentially a calendar. You add events, delete, search for etc. when you close out the program, it opens a txt file and writes the events into it in binary. When you open the program again it loads the events from the same file. Now because it reads/writes in binary i don't know if the problem is in the reading or writing process but essentially..some data is missing when it loads the file.

An 'event' consists of a date(3 integers), category(string), and description(string). The category and description are blank when loaded back into the program. Below is the code for the reading and writing portions as well as what the output should look like.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void saveList()
   {
   ofstream outFile;
   
   outFile.open("Events.txt", ios::binary);
   if(outFile.fail())
      {
      cout << "\nCannot open file 'Events.txt'!\n";
      system("pause");
      return;
      }
        
   //write to file
   for(int i = 0; i <=CD.getLast(); i++)
      {
      if(Array[i].getDay() == 0 && Array[i].getMonth() == 0);
      else
         outFile.write((char*) &Array[i], sizeof(Event));     
      }  
   outFile.close();
   }



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void loadList()
   {
   ifstream inFile;
   Event current;
   int nday,
       nmonth,
       nyear;
   string ncategory,
          ndescription;
   bool ncont;
   
   inFile.open("Events.txt", ios::in | ios::binary);
   if(inFile.fail())
      {
      cout << "\nCannot open file 'Events.txt'!\n\n";
      system("pause");
      return;
      }
    
   inFile.read((char*) &current, sizeof(Event));
   while(!inFile.eof())
      {
      Array[CD.getCount()] = current;
      CD.plusCount();
      CD.plusLast();
      inFile.read((char*) &current, sizeof(Event));                
      }
   inFile.close();
   }



When loaded, the output should look like this:

Date of event: 12/25/2008
Category: Holiday
Description: Christmas

but it's showing up as follows:

Date of event: 12/25/2008
Category:
Description:


Any ideas?
Jun 15, 2008 at 9:28am
Here's an idea :
open the file using a text editor (like notepad, or notepad++, or Wordpad).
Tou should be able to see the text for the category or Description fields if they were actually saved to the file.

Jun 15, 2008 at 1:44pm
No...if you open the file it is saved to, it's just symbols and unrecognizable characters. It's saved in binary format. It can't be read without opening the file in binary. I open up that file with notepad all the time. But thanks, i appreciate the suggestion
Jun 15, 2008 at 4:26pm
closed account (z05DSL3A)
aeronet,
Get yourself a hex editor, somthing like Free Hex Editor Neo.
http://www.hhdsoftware.com/Products/home/hex-editor-free.html
Last edited on Jun 15, 2008 at 4:27pm
Jun 15, 2008 at 4:32pm
ooh - That's the one i've got..
Jun 15, 2008 at 7:38pm
You never list your Event type, but if it looks something like this
1
2
3
4
5
6
struct Event
  {
  int day, month, year;
  string category;
  string description;
  };

Then saying
1
2
3
ofstream outFile( "events.dat" );
Event some_event;
outFile.write( &some_event, sizeof( Event ) );

will not work.

The reason is that a string is an object that maintains a dynamically allocated char buffer. You can imagine it to look something like:
1
2
3
4
5
6
7
8
class string
  {
  private:
    char*    f_data;
    unsigned f_length;
  public:
    ...
  };

Simply writing the bits of the string object doesn't output anything useful.


Binary file I/O is straight-forward, but you must account for the caveats.


In general you should prepare a pair of functions that write and read a specific binary structure. For a class they might as well be member functions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct Event
  {
  int day, month, year;
  string category;
  string description;

  void write( std::ostream& outs );
  void read( std::istream& ins );
  };

void write_int( ostream& outs, int i )
  {
  // Always write an integer in (for this example) network byte order
  // Integers are 4-byte quantities!
  signed char bytes[ 4 ];
  for (int n = 3; n >= 0; n--, i >>= 8)
    bytes[ n ] = i & 0xFF;
  outs.write( bytes, 4 );
  }

void Event::write( ostream& outs )
  {
  // Write the ints
  write_int( outs, day );
  write_int( outs, month );
  write_int( outs, year );
  // Write strings as ASCIIZ
  outs.write( category   .c_str(), category   .length() +1 );
  outs.write( description.c_str(), description.length() +1 );
  }

void Event::read( std::istream& ins )
  {
  // left as an exercise for you
  }


By explicitly defining functions on how to read and write your binary data, you maintain exacting compliance with your file specification no matter what platform or compiler you use. This implies that you have a specific file specification (which you didn't).

For my example, the specification is:

A file is a packed list of records.
Each record is a variable length, byte-aligned object.
Multibyte integer values are stored in network-byte order (MSB first, LSB last).
String values are stored as ASCIIZ. Unless otherwise noted, they are variable-length and terminated by a single zero byte.
BYTE
OFFSET .. SIZE .. TYPE .. MEANING
0...... .. 4... .. sint .. day
4...... .. 4... .. sint .. month
8...... .. 4... .. sint .. year
12.... .. ?... .. strz .. category
?..... .. ?... .. strz .. description (immediately follows 'category')


Hope this helps.
Last edited on Jun 15, 2008 at 7:39pm
Jun 16, 2008 at 2:39am
Wow! i'm clearly way in over my head seeing as how i understood about half of that..haha. I knew that a string was it's own object so i figured that had something to do with it not writing properly. Now if i simply use character arrays instead of strings, would it understand how to write those into binary seeing as they aren't objects? I appreciate the time and detail you put into that explanation by the way. It actually helped me understand why it wasn't work. Thanks
Jun 25, 2008 at 7:42am
I realize i'm reviving a semi-old thread (10 days), but I was looking this over, as I need to do something similar.. and while you are using the code
1
2
  for (int n = 3; n >= 0; n--, i >>= 8)
    bytes[ n ] = i & 0xFF;

to split the int up into 4 chars, i was wondering if it would be just as simple to use a union in the struct?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Event
  {
  union
    {
      int i;
      char c[4];
    } day, month, year;

  // int day, month, year;

  string category;
  string description;

  void write( std::ostream& outs );
  void read( std::istream& ins );
  };

Then you can simply write:
out.write( event.day.c , 4 );

would that work?

Edit: I mean, the only thing that kinda scares me a bit is
The exact alignment and order of the members of a union in memory is platform dependant. Therefore be aware of possible portability issues with this type of use.
from http://www.cplusplus.com/doc/tutorial/other_data_types.html
Last edited on Jun 25, 2008 at 7:45am
Jun 25, 2008 at 9:21am
You certainly can use a union like that (that is what they are for). Unfortunately, the union has nothing to do with the problem. Saying:
out.write( event.day.c, 4 );

is exactly the same as saying:
out.write( &event.day, 4 );

which defeats the whole purpose of the loop:
// Always write an integer in (for this example) network byte order


There exist a set of functions specifically for converting between network byte order and whatever your local machine uses:
htonl() and htons(): convert host to network byte order, 32 bits and 16 bits, respectively
ntohl() and ntohs(): convert network to host, 32 and 16 again.

So you can say
1
2
3
int i = event.day;
i = htonl( i );
out.write( &i, 4 );


Unfortunately, while convenient, these functions are not found in any standard location. The first three links on Google for "ntohl", for example, lists three different platforms, all with these functions #included from a different spot. They usually are found with some platform-specific socket/network library. In the case of windows, you actually have to link in an extra DLL to use them.


That little loop I gave you has none of those problems, and works with any integer type less than or equal to 32 bits (4 bytes), on any platform --no matter how screwy the native (host) byte-order.

With the file specified in an explicit order, you can compile your code on a PC and write a binary file, take both your sourcecode and binary file to another machine (like a SunSparc PC, which is big-endian), recompile and read the binary file without any problems.

Hope this helps.
Jun 25, 2008 at 9:53am
ok.. I had assumed (i know, bad word) that since you were bit shifting to the right by 8, that you were grabbing the bits in the same order that they were stored in memory, (since you started with n=3 and went backwards) and thus the same order that they would have appeared in a union.

Obviously from what you're saying, depending on the platform, the bits may not be stored in the memory in the same order that they appear in the integer.

This should have occured to me, considering I've seen that pixels in a bmp file are stored in reverse order. So why not bits in memory? there's no guarantee that even a union would be from lowest to highest memory address.

Thanks for the info.
Last edited on Jun 25, 2008 at 9:55am
Jun 26, 2008 at 4:33am
wouldn't you know it.. the code won't compile..

invalid conversion from signed char to const char

From the line where I try to out.write(bytes,4);

Does it have to be signed?

compiled when I made the bytes array unsigned.. Since in my case they are all positive numbers, this should still work. I hope.

Doing this in a small test program to make sure it works before I add it into the class I'm working on.
Last edited on Jun 26, 2008 at 4:36am
Jun 26, 2008 at 4:53pm
Hmm, I'm not sure why I wrote "signed". Maybe I just forgot the "un"...

I usually specify "unsigned char" when I mean to handle individual bytes from a file. But I don't think it really matters as far as reading/writing. I think the iostream::read() and iostream::write() functions take char*, so anything that converts to that should work...

It won't make any difference with the shifting (not the way we are doing it, anyway...)

:-)
Topic archived. No new replies allowed.