Reading Binary Files

Sage
Posts: 1,066
Joined: 2004.07
Post: #1
I'm trying to develop a small, portable, custom binary file to store things for my game. I found a site that shows how to write out structs to a binary file quite easily. It also showed an easy way to read this structure. Here's what it shows:
http://www.gamedev.net/reference/article...le1127.asp Wrote:struct OBJECT
{
int number;
char letter;
} obj;

obj.number = 15;
obj.letter = ëM�*;

fout.write((char *)(&obj), sizeof(obj));

That would write the entire structure for you! Let�*s move on to input now. Input will be a cinch now because the read() function takes exactly the same parameters as write(), and it works exactly the same.

ifstream fin("file.dat", ios::binary);

fin.read((char *)(&obj), sizeof(obj));

Is there a way to write out a struct like this but then read in the individual parts? For instance I save the example struct but I want to recall the letter and assign it to one thing but the number to another. I don't want to just read in to an identical struct. How would I go about this.
Quote this message in a reply
Member
Posts: 370
Joined: 2002.04
Post: #2
You probably should allocate a temporary instance of your structure and read it, grab the values from that, and then copy them to where you want them to go. If you try to read the numbers in separately you have to deal with not only the byte order but also you have to guess at how the structure is padded and aligned. And by portable I hope you don't mean that you can copy the data files across computers, because for the same reasons you probably can't using this method. YMMV

Did you ever wonder why we had to run for shelter when the promise of a brave new world unfurled beneath the clear blue sky?
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #3
Why wouldn't I be able to use the file on multiple computers? Even if I load with the temporary struct, it still wouldn't work? Why not?
Quote this message in a reply
Moderator
Posts: 691
Joined: 2002.04
Post: #4
@Nick: I'd presume that by "multiple computers" Steven is really talking about multiple architectures/OS (your usual "fun with endianess" issues...)

The code would also likely break on even only the one OS if, at an indeterminate point in the future, you switched compilers* with the new compiler re-ordering or padding the members of the struct to perform it's own, different, optimisations...

This is the reason why you can't just read an individual member from the file – as you don't know what changes the compiler has made to the struct, you don't know where the member you're looking for might be in the file – you're forced to "fin.read((char *)(&obj), sizeof(obj));" the entire object, and then retrieve objects as "char myChar = obj.letter;"



(*although I suppose not that likely on the Mac unless you're already using CodeWarrior.)

Mark Bishop
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #5
So is it a bad way to go to save my files this way? I want the most cross-platform way to save files. I'm going to save my maps and all sorts of things. I'm using just SDL and C/C++ so there are no Cocoa classes to help me. I'm just sorting out some of the "finer" details of programming.
Quote this message in a reply
Moderator
Posts: 133
Joined: 2008.05
Post: #6
It's not a bad way to save files, unless you want it to be cross platform. Now if you are designing a class that does all the work for you, you could write functions that would take into account the endianness of the system you are on and properly write/read the file.

It's definetly a fast and easy way to save files, and I prefer it to writing out long text files of information. Steven's advice is a little misleading, you can copy them across computers, just not ones with different architectures or OS's.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #7
Hm. Interesting. And here I thought using straight C/C++ would be perfect. Any tips on where to look as to writing a class that does all the work for me? Seems slightly daunting.
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #8
"different architectures" being "the mac" now that we're running on both PowerPC and Intel. Never, ever, ever, do what the code in the OP does. Not only is it guaranteed to fail on whichever of PowerPC and Intel didn't write the original file, but it's likely to fail in a move to a different ABI when the alignment of various types changes.

You should always read a specific number of bytes into a buffer of memory, then manually extract the bytes you want, swap them as appropriate, then put them wherever you want them.

If you want a marginally easier way of doing that than lots of pointer math, you could try http://onesadcookie.com/svn/repos/libBinaryIO/ -- a printf/scanf-like API for reading and writing binary data.
Quote this message in a reply
Moderator
Posts: 508
Joined: 2002.09
Post: #9
Back in the good old pre-Carbon days we used to GetHandle() and BlockMove()

I miss those days Rasp

"When you dream, there are no rules..."
Quote this message in a reply
Oldtimer
Posts: 834
Joined: 2002.09
Post: #10
What you need to do is design your own file format. Then you allocate a buffer with malloc or new[] and fill that out. That way you will sidestep all problems with padding, since there is none. (You designed it not to have any) Then, if you have multibyte data in it, you need to detect if you are on a big- or little-endian system. If you're on a system different than the one that made the file, you have to swap the bytes to match. Google for endianness or byteswapping.

As for the first part, it's easy.
Write:
Code:
char *buffer = (char *) malloc (100); // Or whatever size you need
char *bufPtr = buffer; // A pointer to the first byte

memcpy (bufPtr, thePlayer->numOfLives, sizeof (int));
bufPtr += sizeof (int);
memcpy (bufPtr, thePlayer->money, sizeof (int));
bufPtr += sizeof (int);
fwrite (as usual, just dump the buffer)

Reading:
Code:
char *buffer = (char *) malloc (100); // Or whatever size you need
char *bufPtr = buffer; // A pointer to the first byte

fread (as usual, just read the entire file)
memcpy (thePlayer->numOfLives, bufPtr, sizeof (int));
bufPtr += sizeof (int);
memcpy (thePlayer->mone, bufPtr, sizeof (int));
bufPtr += sizyeof (int);
I'm sure you get the idea Smile
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #11
Would something like this work? I get a bus error when I run it in the terminal. I'm probably doing the fwrite wrong. I haven't used that function much.

Code:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct TestClass
{
int age;
int lives;
};


int main()
{
    TestClass myObj;
    myObj.age = 24;
    myObj.lives = 4;
    
    
    char *buffer = (char *)malloc(100);
    char *bufPtr = buffer;
    
    memcpy(bufPtr, (int *)myObj.age, sizeof(int));
    bufPtr += sizeof(int);
    memcpy(bufPtr, (int *)myObj.lives, sizeof(int));
    bufPtr += sizeof(int);
    
    FILE *testFile;
    testFile = fopen("test.dat","wb");
    fwrite(bufPtr,sizeof(int),2,testFile);
    
    fclose(testFile);
    return 0;
}
Quote this message in a reply
Moderator
Posts: 691
Joined: 2002.04
Post: #12
I'd first put in some error detection to check whether the file you're trying to fwrite() to has actually been created; I'd then wonder long and hard about why you're creating a char* variable and using memcpy() to store ints in it... I haven't read through all the source, so this may be a silly question... but why?

Oh, and I haven't played with pointer arithmetic for quite a while, but from my memory you're passing 'bufPtr' to fwrite() without first reseting it's address; that will probably cause a few errors (but reading the code, you won't even get that far...), and you probably want to pass the original 'buffer' instead.

Try something like this...

Code:
#ifdef __cplusplus
    extern "C" {
#endif

#include <stdio.h>

#ifdef __cplusplus
    };
#endif

class TestClass
{
    public:
    int m_lives;

    void Save( void )
    {
    FILE *file;
    if(( file = fopen( "sealfin.bin", "wb" )) == NULL )
    {
    printf( "Scream! And die!" );
    return;
    }
    fwrite( &m_lives, sizeof( int ), 1, file );
    /* Yes, I know I should probably check that the int has actually been written to the file, but I'm in a rush... */
    fclose( file );
    return;
    };

    void Load( void )
    {
    FILE *file;
    if(( file = fopen( "sealfin.bin", "rb" )) == NULL )
    {
    printf( "Scream! And die!" );
    return;
    }
    fread( &m_lives, sizeof( int ), 1, file );
    /* ...and here I should be checking that an int has actually been read from the file. */
    fclose( file );
    return;
    };
};


int main()
{
    TestClass obj;
    obj.Load();
    printf( "\nlives: %d\n\n", obj.m_lives );
    obj.m_lives = 21;
    obj.Save();
        return 0;
}

Mark Bishop
Quote this message in a reply
Moderator
Posts: 691
Joined: 2002.04
Post: #13
Uh, I just took a second look; your bus error was caused by this:
Code:
memcpy(bufPtr, (int *)myObj.age, sizeof(int));
Which should have been this:
Code:
memcpy(bufPtr, &myObj.age, sizeof(int));

You were dereferencing the variable which you should have been passing the address of (i.e., you were passing it's value as the address); my points on the pointer arithmetic, and the error checking, still stand though Wink

Mark Bishop
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #14
I knew about error checking and such. Normally I do that but I was just trying to figure out writing the files. Thanks for the tips and the fix.

The char thing was just me being absent minded and just waking up, trying to make sense of things I hadn't ever really worked with.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #15
Using your technique, sealfin, how would I save multiple variables and recall them in the proper order?
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Binary Tree and Objective-c mnorton 0 3,294 Sep 7, 2009 10:44 AM
Last Post: mnorton
  Carbon - Reading / Writing Text Files dave05 10 4,323 Aug 1, 2006 05:42 PM
Last Post: dave05
  Reading/writing files in Carbon? ia3n_g 2 2,461 Jul 23, 2006 06:54 PM
Last Post: ia3n_g