#1
  1. No Profile Picture
    Junior Member
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2002
    Location
    MA, USA
    Posts
    11
    Rep Power
    0

    Binary io of a typedef struct in C++


    Hi,
    I've been writing a small little test app to make a highscores file, and once i get it working I would stick it into my game, but i've hit a snag, my binary output isn't being cooperative.

    First off I have some typedef structs:

    typedef struct HScore{
    int score;
    int nLen;
    string name;
    };

    typedef struct HSList{
    int nHs;
    HScore *scores;
    };

    So basically there is one HSList, which has a few HScores in it, and nHs is the number of them.

    There is also a class, which will be pretty much just a way to connect this stuff to my graphics library, but for now just tries to load a HSList from a file:

    class HSChart{
    public:
    HSChart();
    ~HSChart();
    private:
    HSList highscores;
    };

    I have hardcoded a few entries into the constructor so that the file will contain some info when I go to read it, but I can't seem to 1) output the info properly 2) read it properly or 3) both

    so Here is the code I used to make a few hardcoded entries(from inside the constructor):

    highscores.nHs = 2;
    highscores.scores = new Score[highscores.nHs];
    highscores.scores[0].score = 5;
    highscores.scores[1].score = 15;
    highscores.scores[0].name = "Bill";
    highscores.scores[1].name = "Bill";

    Then this is what I used to write the data:

    ofstream fout(HIGHSCORE_FILE, ios_base::out | ios_base::binary | ios_base::trunc);

    if (!fout.is_open()){
    cout << "Error opening highscore file, make sure it exists." << endl;
    exit(1);
    }

    fout.write((char*)(&highscores.nHs),sizeof(int));

    for(int i = 0; i < highscores.nHs; i++){
    //highscores.scores.namelength = strlen(highscores.scores[i].name);
    fout.write((char*)&highscores.scores[i].score,
    sizeof(int));
    fout.write((char*)&highscores.scores[i].name,
    sizeof(string));
    }
    fout.close();

    Then I read the file using:

    ifstream fin(HIGHSCORE_FILE, ios_base::in | ios_base::binary);

    if (!fin.is_open()){
    cout << "Error opening highscore file, make sure it exists." << endl;
    exit(1);
    }

    fin.read((char*)(&highscores.nHs), sizeof(int));
    highscores.scores = new HScore[highscores.nHs];
    for(int i = 0; i < highscores.nHs; i++){
    fin.read((char*)&highscores.scores[i].score,
    sizeof(int));
    fin.read((char*)&highscores.scores[i].name,
    sizeof(string));
    }
    fin.close();


    But when I go to output the data with a simple cout, the ints of the score are there, but the name is not. I guess what I'm looking for is a suggestion of where I am messing something up, or a URL to an explanation of something similar or maybe even a more effiecent way to do this.

    I'm sorry for the length of the post, but I wanted to try and be as clear as possible.

    Thanks,
    Eugene.
  2. #2
  3. I'm Baaaaaaack!
    Devshed God 1st Plane (5500 - 5999 posts)

    Join Date
    Jul 2003
    Location
    Maryland
    Posts
    5,538
    Rep Power
    244
    I am far from a C++ I/O guru, but it looks to me as if you are writing POINTERS to your data file, not the actual values. Unless you are really stuck on storing the data as binary, why not write it out in human readable, that way you can debug it much easier.

    BTW: Please enclose your code in "code" tags (see http://forums.devshed.com/misc.php?action=bbcode&s=).

    My blog, The Fount of Useless Information http://sol-biotech.com/wordpress/
    Free code: http://sol-biotech.com/code/.
    Secure Programming: http://sol-biotech.com/code/SecProgFAQ.html.
    Performance Programming: http://sol-biotech.com/code/PerformanceProgramming.html.
    LinkedIn Profile: http://www.linkedin.com/in/keithoxenrider

    It is not that old programmers are any smarter or code better, it is just that they have made the same stupid mistake so many times that it is second nature to fix it.
    --Me, I just made it up

    The reasonable man adapts himself to the world; the unreasonable one persists in trying to adapt the world to himself. Therefore, all progress depends on the unreasonable man.
    --George Bernard Shaw
  4. #3
  5. No Profile Picture
    Junior Member
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2002
    Location
    MA, USA
    Posts
    11
    Rep Power
    0
    Ahh, I'll go see about that pointer buissiness, thanks.

    I wanted to avoid human readable, mostly so that the average user wouldn't be able to just quickly edit their scores

    Sorry about the code, I'll try and remember next time.

    Thanks again,
    Eugene.
  6. #4
  7. Contributing User

    Join Date
    Aug 2003
    Location
    UK
    Posts
    5,111
    Rep Power
    1803
    BTW: you need not "typedef struct" in C++ like you do in C. In C++ struct is the same as a class with all the members public by default. You can even have member functions just like a class. So:
    Code:
    struct HScore
    { 
        int score; 
        int nLen; 
        string name; 
    };
    Is all you need, and it is identical semantically to:
    Code:
    class HScore
    { 
        public :
            int score; 
            int nLen; 
            string name; 
    };
    Clifford
  8. #5
  9. Contributing User

    Join Date
    Aug 2003
    Location
    UK
    Posts
    5,111
    Rep Power
    1803
    Originally posted by Mr_PiCaLo
    I wanted to avoid human readable, mostly so that the average user wouldn't be able to just quickly edit their scores
    You could use a checksum or CRC to detect file tampering, so that even an above average user would struggle.

    Clifford.
  10. #6
  11. Contributing User

    Join Date
    Aug 2003
    Location
    UK
    Posts
    5,111
    Rep Power
    1803
    PHP Code:
    fout.write((char*)&highscores.scores[i].scoresizeof(int)); 
    fout.write((char*)&highscores.scores[i].namesizeof(string)); 
    The first is correct but has issues if you chnge the type of the score variable. The second you are storing the class rather than just the string. This is unlikly to work, and is certainly dependent on the implementation of the string class. Typically string contains a pointer to dynamically allocated memory which contains the actual characters. By saving the string class, you save only the pointer, which when read back, is meaningless. Try,
    PHP Code:
    fout.write((char*)&highscores.scores[i].scoresizeof(highscores.scores[i].score)); 
    fout.writehighscores.scores[i].name.c_str(),  highscores.scores[i].name.length() + ) ; 
    In both cases write the size of the object rather than the size of the class. Then if the type changes, you need not modify this code. When storing the name, the string is converted to a C style NUL terminated string. 1 is added to the length for the NUL terminator.

    To read back the code, you'll need to read into a char array of adequate length, and assign the C style string to the string class member. I've got to go now, so I'll leave that for you to figure out!


    Clifford.
  12. #7
  13. No Profile Picture
    Junior Member
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2002
    Location
    MA, USA
    Posts
    11
    Rep Power
    0
    Wow, thanks clifford.

    I didn't know about the typedef being useless in c++, but I did relize I could accomplish basically the same effect with a class... would it be better to use a class for any sort of specific reason, or is it just a matter of if i'm using c++ why not use c++ stuff...

    I thought about some sort of checksum thing, but I really had no clue how to do that at all, so I went with the thing i knew a little about.

    If I changed my int types to the Uint32 type supplied by SDL (the graphics lib i use) I shouldn't have to worry about different platforms and sizes because it's supposed to be the same size on all platforms, so i think that's one thing I will do.
    For the string, I figured it was something about not actually writing the chars of the string, but I didn't relize how to do it. One question is, am i going to have to also write the size of the string to the file so i can tell read how much I'm going to be expecting?

    Thanks again for all the input,
    Eugene.
  14. #8
  15. No Profile Picture
    Junior Member
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2002
    Location
    MA, USA
    Posts
    11
    Rep Power
    0
    I have just spent some time cranking away at this, and i've got it working, though probably could be improved.


    So to create a highscore file I use this code(Uint32 is the type I was talking about in my last post)
    PHP Code:
        fout.write((char*)(&highscores.nHs),sizeof(Uint32));
        
        for(
    Uint32 i 0highscores.nHsi++){
            
    highscores.scores[i].nLen highscores.scores[i].name.length();
            
    fout.write( (char*) &highscores.scores[i].score
                        
    sizeof(Uint32));
            
    fout.write( (char *)&highscores.scores[i].nLen
                        
    sizeof(Uint32));
            
    fout.writehighscores.scores[i].name.c_str(),  
                        
    sizeof(char)*(highscores.scores[i].nLen 1));
        } 
    then when I read the stuff I use:
    PHP Code:
        fin.read((char*)(&highscores.nHs), sizeof(Uint32));
        
    highscores.scores = new HScore[highscores.nHs];
        for(
    Uint32 i 0highscores.nHsi++){
            
    fin.read((char*)&highscores.scores[i].scoresizeof(Uint32));
            
    fin.read((char*)&highscores.scores[i].nLensizeof(Uint32));
            
    tmp = new char[highscores.scores[i].nLen];
            
    fin.read(tmpsizeof(char)*(highscores.scores[i].nLen+1));
            
    highscores.scores[i].name tmp;
            
    delete tmp;
        } 
    It seems kind of a waste to use the temp varriable, so I was kind of wondering if there was some way to load the char data into the string directly, some sort of memory allocation for strings or something...

    For now this is just exactly what I needed it to do though.

    Thanks for all of your time and input,
    Eugene.
  16. #9
  17. Contributing User

    Join Date
    Aug 2003
    Location
    UK
    Posts
    5,111
    Rep Power
    1803
    Originally posted by Mr_PiCaLo
    One question is, am i going to have to also write the size of the string to the file so i can tell read how much I'm going to be expecting?
    I was going to suggest that solution, but you have worked it out for yourself. If you do that, you need not also write the NUL terminator, but you would need to add it when the string was read back, so it is your choice.

    Clifford
  18. #10
  19. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    There is another advantage to using character arrays. You probably already have a maximum length for the name in the high score, so just use characters arrays of that length plus one and do high score stuff with c style strings. Then reading and writing to file is greatly simplified, because you also have a constant record size. You wouldn't even need to write how many there are-- just write them all out and read until end of file.
    Code:
    struct HScore{ 
    int score; 
    char name[MAX_NAME_LEN];  //MAX_NAME_LEN is either #define or const int
    };
    Code:
        for(Uint32 i = 0; i < highscores.nHs; i++)
            fout.write( (const char*) &highscores.scores[i], sizeof(HScore));
    Code:
      highscores.nHs=0;
      while (!fin.eof() && fin.good())
      {
         fin.read((char *)&highscores.scores[highscores.nHs], sizeof(HSore));
         highscores.nHs++;
      }
    As you can see, it lets you read or write the entire struct at one time, and saves using a temporary variable for reading and writing the name. There will be some lost space in the static arrays, but it should be minimal.
    Last edited by f'lar; September 22nd, 2003 at 01:26 PM.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn
  20. #11
  21. No Profile Picture
    Junior Member
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2002
    Location
    MA, USA
    Posts
    11
    Rep Power
    0
    I was thinking about just using char arrays, but, I'm not the best with them, prolly cause I was never actually taught how to use them :)

    So I will also assume there is no simple way to allocate a strings size, so I could do the same, just with a string type.

    I will see if I can get it working using the char arrays though, cause that method of just writing the whole struct seems more streamlined than writing each thing out.

    Thanks for the suggestions,
    Eugene
    Last edited by Mr_PiCaLo; September 22nd, 2003 at 07:13 PM.
  22. #12
  23. ASP.Net MVP
    Devshed Specialist (4000 - 4499 posts)

    Join Date
    Aug 2003
    Location
    WI
    Posts
    4,378
    Rep Power
    1511
    Hmm, noticed a bug in my read loop. highscores.nHs would end up one number to high with that implementation. Do the first read before the first test, or use a post-test loop, so that the loop will exit before updating highscores.nHs an extra time.
    Primary Forum: .Net Development
    Holy cow, I'm now an ASP.Net MVP!

    [Moving to ASP.Net] | [.Net Dos and Don't for VB6 Programmers]

    http://twitter.com/jcoehoorn

IMN logo majestic logo threadwatch logo seochat tools logo