Reading/writing from/to binary files

Started by
8 comments, last by chockydavid1983 15 years, 4 months ago
Hi everyone I have written a simple database creator program but get an exception when I switch to release mode in vc++ 2008 express and run it (it's fine in debug mode). Here is my code (unsure how to put it in a block): Excerpt from main.cpp:


int main()
{
   vector<Player*> database;
   bool quit = false;

   // Welcome
   cout << "Welcome to Fantasy Football Database Creator!" << endl;
   cout << "You can start a new database or load an existing one for editing" << endl << endl;

   while( !quit )
   {   
      int choice = 0;

      //////////////
      // MAIN MENU//
      //////////////

      choice = MenuSelection("Database- 1)Add, 2)Delete, 3)Save, 4)Load, 5)View, 6)Clear, 7)Exit: ", 1, 7);

      // Call function depending on user's main menu selection
      switch(choice)
      {
      case 1:
         AddPlayer(database);
         break;
      case 2:
         RemovePlayer(database);
         break;
      case 3:
         SaveDatabase(database);
         break;
      case 4:
         LoadDatabase(database);
         break;
      case 5:
         ViewDatabase(database);
         break;
      case 6:
         ClearDatabase(database);
         break;
      default:
         ClearDatabase(database);
         quit = true;
         break;
      }
   }
}

// Save database with error checking
void SaveDatabase(vector<Player*>& database)
{
   if(database.empty())
   {
      cout << "Database is empty!" << endl << endl;
      return;
   }

   string saveFileName = "";
   cout << "File name to save to: ";
   cin >> saveFileName;

   ofstream outFile(saveFileName.c_str(), ios_base::binary);

   if(outFile)
   {
      cout << "Size of database is " << database.size() << endl;
      cout << "Saving..." << endl << endl;

      for(unsigned int i = 0; i < database.size(); ++i)
      {
         outFile.write((char*)&(*database), sizeof(Player));
      }
   }
   else
   {
      cout << "Save failed!" << endl;
   }
}

// Load database with error checking
void LoadDatabase(vector<Player*>& database)
{
   string loadFileName = "";
   cout << "File name to load from: ";
   cin >> loadFileName;

   ifstream inFile(loadFileName.c_str(), ios_base::binary);

   Player player;
   Player* playerPointer;

   if(inFile)
   {
      while(!inFile.eof())
      {
         inFile.read((char*)&player, sizeof(Player));
         playerPointer = new Player(player);
         database.push_back(playerPointer);
      }

      int size = database.size();
      delete database[size - 1];
      database[size - 1] = 0;
      database.pop_back();
      cout << "Load succeeded!" << endl;
   }
   else
   {
      cout << "Load failed!" << endl;
   }
}
Player.h/cpp

#ifndef PLAYER_H
#define PLAYER_H

#include <string>

class Player
{
public:
   Player();
   Player(std::string name, std::string position, int value);
   ~Player();

   void View();

   std::string mName;
   std::string mPosition;
   int      mValue;
   int         mWeekPoints;
   int         mOverallPoints;
};

#endif // PLAYER_H

#include "Player.h"
#include <iostream>
using namespace std;

Player::Player()
: mName("Default"), mPosition("Default"), mValue(0), mWeekPoints(0), mOverallPoints(0)
{}

Player::Player(std::string name, std::string position, int value)
: mName(name), mPosition(position), mValue(value), mWeekPoints(0), mOverallPoints(0)
{}

Player::~Player()
{}

void Player::View()
{
   cout << "Name: " << mName << endl;
   cout << "Position: " << mPosition << endl;
   cout << "Value: " << mValue << endl << endl;
}
When I run the program in release mode, the debugger stops in the LoadDatabase function on the line: playerPointer = new Player(player); And the following message displays: An unhandled exception of type 'System.Runtime.InteropServices.SEHException' occurred in Fantasy Football.exe Additional information: External component has thrown an exception. When I look around for solutions, they seem to be related to Windows forms stuff that I'm not using and may also be a problem specific to Windows XP but that doesn't help me much :(. I think it must have to do with my load function or the way I originally save but I don't know. Does anyone know why this problem occurs and if there are any known workarounds please? Cheers, Chris.
Advertisement
The "root" directory for the application in Debug and in Release is not the same, and it also depends if you are launching it from inside VC++ or directly by double clicking the executable. It might not find the database input file correctly, so it does not read the player structure from the file and cannot pass it properly to the player constructor.
Hi Ben

Thank you for your reply, it helped me figure out what was going wrong. I was trying to run the application in release mode and load a database that was created when the application was in debug mode. Now everything is working correctly and I am able to load this in a similar way as part of another application I made.

Thanks again, Chris.
If you save and load a database, are the name and position correct?

What happens if you enter a name that is longer than 16 characters (make it 30 just to be sure)?

Bonus points for figuring out why it can actually work for short names, when it really shouldn't.
Antheus, if he could figure out what was wrong from that, he wouldn't be posting in For Beginners. ;)

OP: You can't just read and write any old structure by casting its address to a char*. Even under the best conditions, that won't work with anything that is not a 'POD' struct. In particular, it won't work if the structure contains any pointers (because the file contains the address where the pointed-at thing is *this* time, and *next* time the desired pointed-at thing could be elsewhere, or even not-yet-created!) or std::string instances (or really, any structure that you didn't create yourself to be POD - because *it* could very easily contain pointers and other such things). In general, you really shouldn't try to write *anything* to file this way (or read it that way), but instead learn something about serialization.
Thanks everyone for your inputs :-).

Yeah, I tried saving/ loading with a name that was longer than 16 characters and saw the results. I've searched around a bit and am still not quite sure why that's the case. I realise I should be using getline to read a full name or just cin if I was seperating into first and last name.

So the problem is reading/ writing a structure containing a string right? So in this case, if I converted the string to a char array it should be OK? This would then be a POD class along with the rest of the members of the Person struct.

Thanks for the tip, I will definitely look into serialisation.

Cheers, Chris.
Yes, if you made a char array it would simplify the reading and writing. Be aware that this has its own limitations too. A better - and more general (you can't always use a fixed size array) - solution is to do proper serialisation, as Zahlman linked.
Quote:Original post by Zahlman
Antheus, if he could figure out what was wrong from that, he wouldn't be posting in For Beginners. ;)


The bonus question wasn't intended for OP.

Quote:Yeah, I tried saving/ loading with a name that was longer than 16 characters and saw the results. I've searched around a bit and am still not quite sure why that's the case. I realise I should be using getline to read a full name or just cin if I was seperating into first and last name.


As long as string will never contain more than 16 characters, the MVC implementation behaves like POD. Even with that, as soon as you load it from disk in this way, internal data of that instance gets corrupted, it just so happens corrupted members are not used with less than 16 chars. But the string is irrecoverably broken.

Quote:So the problem is reading/ writing a structure containing a string right? So in this case, if I converted the string to a char array it should be OK? This would then be a POD class along with the rest of the members of the Person struct.


Normally, I'd advise against that, but considering that developing basic serialization framework is somewhat trickier, operating on C strings is likely the simplest solution.

If you need to read/write custom structures, or anything non-pod, always write individual members, never just raw type-case.
Quote:Original post by chockydavid1983
So the problem is reading/ writing a structure containing a string right? So in this case, if I converted the string to a char array it should be OK? This would then be a POD class along with the rest of the members of the Person struct.


Don't replace the string, but instead serialize. In your case you can do very basic serialization, because you don't have pointers that link your Person objects together (you only have pointers hidden within the std::string instances).

The serialization of a struct consists simply of the serialization of each data member, one after the other (hence "serial"). The member functions don't need to be written at all; they're not actually "part of" the struct in the sense of being represented in memory. (If you were using an inheritance hierarchy with virtual functions, you would want to write some kind of code to indicate which derived class was in use.)

To serialize the ints, you can just write them. For this you *can* cast the address to char*, to do binary serialization. (But keep in mind that your output file will not be portable.)
To serialize strings, the easiest way is to write the length of the string (as an int, using the above technique), followed by the data of the string (as a char array). That way, when you read the data in, you read the length first, and then you know how much of the file to read into the string. "Reading into a std::string" is tricky, though; you'll want to do something like this:

std::string read_serialized_string(std::istream& is) {  int length = read_serialized_int(is);  std::vector<char> buffer(length);  is.read(&buffer.front(), length);  return std::string(buffer.begin(), buffer.end());}

Thanks for all your replies. I definitely have another topic to add to my (very long!) 'to study' list. I love how there are always better, more efficient ways to do all aspects of programming, it's an interesting journey :-).

This topic is closed to new replies.

Advertisement