• Advertisement
Sign in to follow this  

Reading/Writing to a binary file

This topic is 2774 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

Hey guys!
I've recently been experimenting with reading and loading binary files. I've written simple tile loading/saving functions which, to my complete and utter amazement, don't work.

The functions are as follows:

void LoadTiles(){
ifstream File("Data/Maps/map.bin", ios::in | ios::binary);
File.read ((char*)&Tiles, sizeof (TileSystem));
File.close();
}

void SaveTiles(){
ofstream File("Data/Maps/map.bin", ios::out | ios::binary);
File.write ((char*)&Tiles, sizeof (TileSystem));
File.close();
}


TileSystem is a class btw. and Tiles is a TileSystem.

Saving results in a 1kb file which may or may not contain my class. Loading *seems* to work but then, apparently, a call to "Tiles.size()" gives me an access violation error.

Gee, programming sure is full of bumpy bumps...
What am I doing wrong?

Thanks in advance :)

Share this post


Link to post
Share on other sites
Advertisement
Whether or not that is the right code depends on what TileSystem looks like. If TileSystem is non-POD then this probably completely wrong.

Share this post


Link to post
Share on other sites
Moved to For Beginners.

Share this post


Link to post
Share on other sites
Here is TileSystem:


struct sTile{
int x, y;
int tileFrame;
};

class TileSystem{
public:
vector<sTile> Tiles;
sSprite Tilesheet;

void InitTiles(int TilesX, int TilesY, string TileSrc, int TilesWidth, int TilesHeight);
void DrawTiles(int camX, int camY, int layer0R, int layer0G, int layer0B);
void FreeMem();
};

Share this post


Link to post
Share on other sites
As SiCrane predicted, you're not outputting Plain Ol' Data. Your TileSystem structure is comprised of a vector<> instance and sSprite (don't know what that is - a pointer to a texture?).

The sizeof(TileSystem) is (guess) 20-30 bytes. That's all you're writing out from the address you pass to Write(..). It's not writing out the contents of the vector (just a pointer structure for an initialized vector<>=just some pointers), and it's writing out some pointer value that you probably got by creating a sprite or something.

When you read it back in, you're not creating a new vector, nor are you creating whatever sSprite is.

To write out the contents of the vector, think more in terms of:

for(size_type i=0; i<Tiles.size(); i++) File << Tiles;

Don't know what sSprite is, but if it's a pointer, you may want to output information that will allow you to create it after you read that info back in.

Share this post


Link to post
Share on other sites
As an example, whenever I want to serialize a structure I add two functions that do the heavy work for me:

std::ostream& Engine::Debugger::operator<<(std::ostream& stream, const Callstack& callstack)
{
stream << callstack.m_stack.size();
for(auto i = callstack.m_stack.begin(); i != callstack.m_stack.end(); ++i)
stream << *i;

return stream;
}

std::ostrea& Engine::Debugger::operator>>(std::ostrea& stream, Callstack& callstack)
{
std::size_t n;
stream >> n;
callstack.m_stack.resize(n);
for(std::size_t i = 0; i < n; ++i)
{
int value;
stream >> value;
callstack.m_stack.push_back(value);
}

return stream;
}



Of course, this code will only ever compile if the type of the variable "value" defines an operator as well.

Share this post


Link to post
Share on other sites
sSprite is a struct.

Anyways, I decided to test with this:

void LoadTiles(){
ifstream File("Data/Maps/map.bin", ios::in | ios::binary);

for(int i=0; i<Tiles.Tiles.size(); i++){
File >> (char*)&Tiles.Tiles.tileFrame;
}
File.close();
}

void SaveTiles(){
ofstream File("Data/Maps/map.bin", ios::out | ios::binary);

for(int i=0; i<Tiles.Tiles.size(); i++){
File << (char*)&Tiles.Tiles.tileFrame;
}
File.close();
}



Seems to get me really random results for some reason.
Then I thought, why am I putting a & sign in front of my tiles thingies (I got the method from a tutorial of sorts)? Does that automagically give me a refrence to my variable or is it merely the location of it in the memory which I do not want by any means? Then again, I have no idea how else I'd typecast that into a char.
Please don't beat me with a sledgehammer for my noobtalk.

Share this post


Link to post
Share on other sites
When I faced the issue of saving/loading things, I did not want to waste too much time on non-game related programming so I opted to use a library for this.

I used (and still do) Boost Serialization to serialize data structures and boost/archive/... to archive the data to/from file.

This, to me, seems a decent, reliable, and easy solution, so I thought I'd mention it.

Share this post


Link to post
Share on other sites
Thanks but still, I'd like to learn the proper way before thinking of using external libraries for this :)

Share this post


Link to post
Share on other sites
For your code:

for(int i=0; i<Tiles.Tiles.size(); i++){
File >> (char*)&Tiles.Tiles.tileFrame;

Did you try just:

File >> Tiles.Tiles.tileFrame;

and do similarly for the File output.

I'm not sure why you're typecasting it. Is there a reason for that? tileFrame is just an int, correct?

Share this post


Link to post
Share on other sites
I was typecasting because the Read/Write functions needed typecasting. I didn't think that << and >> wouldn't.

Still, when I removed typecasting, stuff got even weirder. Now it seems to save the tiles but when it reads, it only reads the tiles that were previously set to NULL.

...Or so it would seem...

After checking, it seems that it reads them in a weird way, gradually reading less and less with each save/load. o.O

Share this post


Link to post
Share on other sites
Quote:
it seems that it reads them in a weird way

Can't help you there. "weird" is a pretty untechnical term, I'm afraid.

So simplify until you've got a handle on what's going on. Experiment with something like:
open file for output
file << 0 << 1 << 2;
close file
open file for input
int a, b, c;
file >> a >> b >> c;
close file

check the values of a, b and c.

If you have a hexdump program of some sort, open your output file. The 4 byte integer values should be easy to see. Or you can, at least, check that the filesize is 12 bytes.

Share this post


Link to post
Share on other sites
i'd do it kind of like this:


struct sTile
{
int x, y;
int tileFrame;
};

class TileSystem
{
public:

vector<sTile> Tiles;

void LoadTiles(std::string filename)
{
std::ifstream File(filename.c_str(), ios::binary);

UINT numTiles;

File.read((char*)&numTiles, sizeof(UINT));

Tiles.resize(numTiles);

for(int i = 0; i < numTiles; ++i)
{
File.read((char*)&Tiles, sizeof(sTile));
}

File.close();
}

void SaveTiles(std::string filename)
{
std::ofstream File(filename.c_str(), ios::binary);

UINT numTiles = Tiles.size();

File.write((char*)&numTiles, sizeof(UINT));

for(int i = 0; i < numTiles; ++i)
{
File.write((char*)&Tiles, sizeof(sTile));
}

File.close();
}

sSprite Tilesheet;

.......

};







But tbh i would just write some sort of serialization class so that all you write is something like s.serialize(Tiles); s.serialize(TileSheet); (or TileSheet.serialize(&s);) and so on (or use some external serialization lib).

[Edited by - scope on July 16, 2010 4:10:00 PM]

Share this post


Link to post
Share on other sites
Just to make sure you heard this part...

you can't just blindly read and write complex types such as vectors or classes.

you may get lucky with classes but if your class uses virtual functions you are going to get hosed (ie stay away from serializing em i say!)

Also you can't serialize pointers because the address will probably change each time you run your program.

So what CAN you serialize?

Simple types such as ints, floats, character arrays, etc.

So how do you serialize complex objects?

Complex objects are made up of simple types, so make a function to serialize your complex object, and serialize it by serializing the simple types.

IE don't serialize a vector blindly, or a class object. Instead, serialize each element of the vector along with how many items there are, and serialize the simple types instead of the class.

Hope this helps!

Share this post


Link to post
Share on other sites
yeah, deserializing a vector<BYTE> with millions of elements can get rather slow tho if you do one by one :)

in my serialization class i handle special cases like that:


template <>
void serialize(std::vector<BYTE>* v)
{
UINT size;

serialize(&size);

v->resize(size);

___read((char*)&(*v)[0], size);
}

template <class T>
void serialize(std::vector<T>* v)
{
UINT size;
serialize(&size);

v->resize(size);

for(UINT i = 0; i < size; ++i)
{
serialize(&(*v));
}
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Atrix256IE don't serialize a vector blindly, or a class object. Instead, serialize each element of the vector along with how many items there are, and serialize the simple types instead of the class.

That's what I was doing, pretty much (in the last code posted).


@scope:
Damnit... I don't know what half of that code does (like the triple underscore before 'read') :(

Share this post


Link to post
Share on other sites
Quote:
Original post by Dandi8
@scope:
Damnit... I don't know what half of that code does (like the triple underscore before 'read') :(


That tripple underscore ___read is just a macro that looks like this:


#define ___read check(); stream->read



i use it to read/write a control value before each read/write operation

Share this post


Link to post
Share on other sites
Quote:
Original post by scope
yeah, deserializing a vector<BYTE> with millions of elements can get rather slow tho if you do one by one :)

Reading millions of bytes from the hard disk is going to be slower than the time it takes to copy them in a vector, anyways. Hard disk bandwidth is quite small compared to processing speed. Remember that the disk driver will most likely read ahead and cache the following bytes on the assumption that you'll keep reading (which is true), so you aren't losing much speed there, if at all.

Share this post


Link to post
Share on other sites
yeah agreed. i felt it was mostly slow with stl debugging

Share this post


Link to post
Share on other sites
Correct me if I'm wrong, but the stream operator << converts the data into a plain text format, where write() will output raw bits to the file. I *think* the ios::binary flag used in the stream constructor just tells it not to insert newline characters.

Anyway a quick and dirty way to output a vector is to use:

ofstream File("Data/Maps/map.bin",ios::binary); // ios::out is automatic in ofstream objects
std::size_t numInVec = vec.size();
File.write(reinterpret_cast<const char*>(&numInVec),sizeof(std::size_t));
File.write(reinterpret_cast<const char*>(&vec[0]),sizeof(VectorType)*numInVec); // Where VectorType is the datatype being stored

Share this post


Link to post
Share on other sites
I've tried doing it this way:

void LoadTiles(){
ifstream File("Data/Maps/map.bin", ios::in | ios::binary);
size_t numInVec;// = Tiles.Tiles.size();
File.read(reinterpret_cast<char*>(&numInVec),sizeof(std::size_t));
Tiles.Tiles.resize(numInVec);
for(int i=0; i<Tiles.Tiles.size(); i++){
File.read(reinterpret_cast<char*>(&Tiles.Tiles),sizeof(sTile)*numInVec); // Where VectorType is the datatype being stored
}
File.close();
}

void SaveTiles(){
ofstream File("Data/Maps/map.bin", ios::out | ios::binary);

size_t numInVec = Tiles.Tiles.size();
File.write(reinterpret_cast<const char*>(&numInVec),sizeof(std::size_t));
for(int i=0; i<Tiles.Tiles.size(); i++){
File.write(reinterpret_cast<const char*>(&Tiles.Tiles),sizeof(sTile)*numInVec); // Where VectorType is the datatype being stored
}
File.close();
}


But it gives me an access violation error when I try to load.

On the plus side, the map file is now about 1MB which I guess implies that *something* is happening when I save.

Share this post


Link to post
Share on other sites
Quote:
File.read(reinterpret_cast<char*>(&Tiles.Tiles),sizeof(sTile)*numInVec);

You're reading in the entire vector to a single Tile. Try what taz suggested. Read in the entire vector to the vector, not into a single element of the vector.

Share this post


Link to post
Share on other sites
How am I reading the entire vector into a single tile? The kind of implies I'm not, doesn't it?


o.O

Share this post


Link to post
Share on other sites
Quote:
The kind of implies I'm not, doesn't it?

Tiles is a single tile (sTile), correct? But, instead of reading in sizeof(sTile) you're reading in sizeof(sTile)*numInVec to every tile. That applies to writing out the data, also.

What happened when you changed it to see if it worked?

You can also estimate the size of your data and look at the filesize. Do they match?

EDIT: I think you misinterpreted taz' suggestion. He suggests doing 1 write instead of writing in a loop. You used his code but put it in a loop for all the tiles.

[Edited by - Buckeye on July 17, 2010 8:19:40 AM]

Share this post


Link to post
Share on other sites
Ah, now I get it!

Once again, I misinterpreted the code for some reason. The brackets have thrown me away. I didn't think of them as the index, like I should.

Everything seems to be working now and all is well in the world. Thanks guys!

I really don't know what I'd do if I couldn't leach on the forums from time to time ^^

Share this post


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

  • Advertisement