binary I/O

Started by
28 comments, last by Fruny 14 years, 12 months ago
I have a structure called an Anibox. It just plays cartoons. All it contains is the number of images (int isize), the type of Anibox (int itype), where to start looping (int iloop), and an array of integers which are the names of images loaded into OpenGL (int ipics[isize]). That's it. Just those four things. Three ints and one array of ints. (oh and also it contains the string name which is the name of itself and the file it will write to) I want to write the Anibox to a file and be able to retrieve it later. I want to write the names of the images though, not the OpenGL number which of course will be useless later. I have a separate class that holds the names of all the images loaded into OpenGL and the int that OpenGL named it. In this case, it's *picbox. picbox has a function called: string NameGrab2(int); which will return the name of the image if you send it the OpenGL name (int). So I did this:

void Anibox::Writebox()
{
   ofstream outfile;
   int ix;
   int length;
   string saver;
   char *copy;

   outfile.open(name.c_str(),ios::out|ios::binary);
   outfile.write(reinterpret_cast<char *>(&itype),sizeof(int));
   outfile.write(reinterpret_cast<char *>(&isize),sizeof(int));
   outfile.write(reinterpret_cast<char *>(&iloop),sizeof(int));
   for(ix=0;ix<isize;ix++)
   {
      saver=picbox->NameGrab2(ipics[ix]);
      length=saver.length();
      copy=new char[length+1];
      strcpy(copy,saver.c_str());
      outfile.write(reinterpret_cast<char *>(&length),sizeof(int));
      outfile.write(copy,length);
      delete[] copy;
   }
   outfile.close();
}


It works! It does exactly what I want it to do. (I think) But isn't there a better way to do it? Is there no way I can just use string.length() and the string itself directly in outfile.write? Dynamically allocating a char array just seems kind of silly.
Advertisement
You can just pass c_str() to write().
...oh yeah. I guess that is a char*. What am I smoking. I don't remember why I made a separate int to record length() either. Man.

Oh wait. I have to record string.length() into an int because write() wants everything cast as a char*. I mean this doesn't make any sense does it?:
outfile.write(reinterpret_cast<char *>(&picbox->NameGrab2(ipics[ix]).length()),sizeof(int));

I don't think you can reference a return value.
Alright, here it is, new and improved. I don't see any way to get around copying length() into int length.

void Anibox::Writebox(){   ofstream outfile;   int ix;   int length;   outfile.open(name.c_str(),ios::out|ios::binary);   outfile.write(reinterpret_cast<char *>(&itype),sizeof(int));   outfile.write(reinterpret_cast<char *>(&isize),sizeof(int));   outfile.write(reinterpret_cast<char *>(&iloop),sizeof(int));   for(ix=0;ix<isize;ix++)   {      length=picbox->NameGrab2(ipics[ix]).length();      outfile.write(reinterpret_cast<char *>(&length),sizeof(int));      outfile.write(picbox->NameGrab2(ipics[ix]).c_str(),length);   }   outfile.close();}
Okay I wrote this as the opposite of above. It reads from one of the files I outputted with the function above and recontructs the Anibox:
bool Anibox::Spawnbox(string anifile, ImageVault *picbox){   ifstream infile;   int ix;   string strtemp;   int length;   char *temp;   bool breturn=true;   infile.open(anifile.c_str(),ios::in|ios::binary);   if(infile.fail()==false)   {      if(ipics!=NULL)         delete[] ipics;      name=anifile;      infile.read(reinterpret_cast<char *>(&itype),sizeof(int));      infile.read(reinterpret_cast<char *>(&isize),sizeof(int));      infile.read(reinterpret_cast<char *>(&iloop),sizeof(int));      ipics=new GLuint[isize];            for(ix=0;ix<isize;ix++)      {         infile.read(reinterpret_cast<char *>(&length),sizeof(int));         temp=new char[length+1];         temp[length]='\0';         infile.read(temp,length);         strtemp="Data/Images/";         strtemp+=temp;         picbox->ImageLoader(strtemp,ipics[ix]);         delete[] temp;      }   }   else   {      breturn=false;   }   return breturn;}


Again, it works perfectly. But I don't like it. Is there a way to avoid the dynamic char array again? I'm gonna read about strings but I don't know if there's a c_str() trick for reading into strings.
You're really terribly misusing stream objects here.

For your serialization (writing class to a sequence of bytes):

void Anibox::Writebox(){   ofstream outfile(name.c_str(), ios::out | ios::binary);   outfile << itype << L"\n";   outfile << isize << L"\n";   outfile << iloop << L"\n";   for(int i = 0; i < isize; ++i)   {      std::string thename = picbox->NameGrab2(ipics);      outfile << thename << L"\n";   }}



And to deserialize:

bool Anibox::Spawnbox(string anifile, ImageVault *picbox){   ifstream infile(anifile.c_str(), ios::in | ios::binary);      if(infile.fail())      return false;   delete[] ipics;   name=anifile;   infile >> itype;   infile >> isize;   infile >> iloop;   ipics = new GLuint[isize];   for(int i = 0; i < isize; ++i)   {      std::string temp;      getline(infile, temp);      temp = L"Data/Images/" + temp;      picbox->ImageLoader(temp, ipics);   }   return true;}



Note that we have no need to store the name string's length anymore. Also observe that we need no temporary buffers; in general if you are writing C++ and you start using char* for anything, you are probably making a huge mistake. std::string is incredibly well supported in the C++ standard library, and chances are you can use it directly for better performance and better safety.

You may also want to think of a way to avoid hard coding that "Data/Images/" path into your Anibox code. Maybe ImageLoader() should add it internally?

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

What are all those capital L's before your string literals? I've never seen those before.

I read that when doing binary I/O I was supposed to use read() and write()

I don't think there's any way for string to use read(). I guess I'll try your way.
Here's a sample of what your write function created. I thought I tried using the << >> operators with ints but I guess I didn't. So I guess it works this way.

00x80628c4100x80628c400x80628c4water1.jpg0x80628c4water2.jpg0x80628c4water3.jpg
0x80628c4water4.jpg0x80628c4water5.jpg0x80628c4water6.jpg0x80628c4water7.jpg
0x80628c4water8.jpg0x80628c4water9.jpg0x80628c4water10.jpg0x80628c4

So is this THE WAY to do it? Serialize. ios::binary. << >> operators. '\n' as delimiter and use getline()? And every other way is the dumb way? Just making sure this is the last time I have to rewrite this.

Does anybody know what that capital L is?
I can't make it work Apoch's way. It seems like the serializing part is working. But when I try to deserialize it doesn't work. It opens the infile okay. But then it runs these three:

infile >> itype;
infile >> isize;
infile >> iloop;

And afterwards all three are still zero. It's not reading anything in. I don't get it.

*******

I tried just declaring a string and reading >> the entire file into a string and that worked. So it's reading the file. It's just not reading the serial info into ints for some reason.
This page seems pretty sure that << and >> are for text format and read() write() are for binary format but it doesn't say why:

http://www.parashift.com/c++-faq-lite/serialization.html#faq-36.3

I'm starting to wonder if maybe I should go with text format, anyway. I just realized all I have to do is put spaces between ints and then I can use << >>. ...I didn't know that. Spaces. That's so easy. Yeah I'll probably be going with text format. This isn't a massive project and every struct makes it's own little files. I can probably master serialization some other day.

That still bugs me why I couldn't deserialize those ints. I can't figure it out.

This topic is closed to new replies.

Advertisement