Jump to content
  • Advertisement
Sign in to follow this  
chockydavid1983

Reading/writing from/to binary files

This topic is 3609 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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());
}

Share this post


Link to post
Share on other sites
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 :-).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!