Board index » cppbuilder » a file handling Question...

a file handling Question...


2005-04-06 07:21:41 PM
cppbuilder37
Somehow i couldn't get bcb to take ofstream and all that, so i guess i have
to use that TFileStream class. It doesn't look too complicated, though I was
wondering if there is any way to save the contents of an entire class with
it, without having to save each property seperatly?
I tried playing around with it but it doesn't really work :p
Here's my code:
AnsiString filename = "easyweb.cfg" ;
TFileStream *FStream;
try{
FStream = new TFileStream(filename, fmCreate) ;
FStream->Write(&website.settings,sizeof(website.settings)) ;
}__finally{
delete FStream ;
}
it writes 20 bytes, but it's prolly not writing what i want it to write...
for reading i use this code:
AnsiString filename = "easyweb.cfg" ;
TFileStream *FStream;
try{
FStream = new TFileStream(filename, fmCreate) ;
FStream->Read(&website.settings,sizeof(website.settings)) ;
}__finally{
delete FStream ;
}
anyway, it doesn't really work :)
heck, i'p prolly way off, but if there is any way to save the entire class
in one go, any suggestions would be more than welcome :)
 
 

Re:a file handling Question...

Gothi[c] wrote:
Quote
anyway, it doesn't really work :)
Please tell exactly what does work and what not.
It will not work if you class for instance contains AnsiStrings.
Hans.
 

Re:a file handling Question...

Well, here's what my class looks like:
class EW_settings{
public:
AnsiString ftpHost ;
AnsiString ftpUser ;
AnsiString ftpPass ;
AnsiString ftpDir ;
int ftpPort ;
} ;
contents of that is dynamic at runtime, so the size is variable...
I might have to add stuff to that later though.
so yes it contains Ansistrings.
maybe i should use char * instead and use the c_str() thingie in ansistring?
"Hans Galema" < XXXX@XXXXX.COM >schreef in bericht
Quote
Gothi[c] wrote:

>anyway, it doesn't really work :)

Please tell exactly what does work and what not.

It will not work if you class for instance contains AnsiStrings.

Hans.
 

{smallsort}

Re:a file handling Question...

Gothi[c] wrote:
Quote
so yes it contains Ansistrings.

maybe i should use char * instead and use the c_str() thingie in
ansistring?
Ah yes. To POD or not to POD :)
POD=Plain Old Data.
For a practical definition of POD I always suggest "think 'C' types,
not 'C++'" and exclude pointers..or at least be aware of the problem.
Although AnsiString is a wonderful class and a fantastic alternative to
'char *' it is actually just a wrapper around a 'char *'. For the
purpose of writing a structure to disk 'char *' is not much better than
'AnsiString'.
If you write that structure to disk you will only get pointers stored
and of course those pointers will not be valid when you reload them.
Worse still although AnsiString will survive the experience of being
loaded back in that's just a coincedence of the way it's been
implemented. Treating class instances like they were POD is very poor
programming practice.
The only way to save an AnsiString to disk is to dereference its
pointer using c_str() and write those bytes out. You can apparently use
streams with AnsiString but that still doesn't address the issue of
structures containing AnsiStrings.
The best you can do there is to provide a method (or in the case of
streams overload '>>' and '<<') that writes each data member out in
turn. What I do in these cases is group all the POD (Plain old Data)
into a single structure so that I can at least write those out in one
go:
struct TNotPOD
{
AnsiString wibble,
wibble2;
struct TPOD
{
int grundle,
gribble;
char woot[ 100 ];
}POD;
};
You still need to write 'wibble' and 'wibble2' out individually but at
least the others can go just by dumping 'POD'. Writing a member
function or operator overload to do this isn't a waste of time. It will
help from a maintennance and development point of view by having the
structure take care of itself. OTOH it needs to be carefully thought
out and documented because someone might start adding virtual member
functions and then the structure is no longer POD.
An array of 'char' as shown above is the only way to store multiple
strings in a structure that is POD.
--
Andrue Cope [TeamB]
[Bicester, Uk]
info.borland.com/newsgroups/guide.html
 

Re:a file handling Question...

I changed my code now, using char* but it's still not writing what i want it
to... hmm i'm prolly not using the pointers correctly either, i always get
confused
when i should use an & or not. usually i just see if it gives me an error or
not LOL i know one returns the memory address and the other returns the
actual contents, i'm just not sure when a function needs what :p ->Write()
only takes it with an & so i guess i should use that, unless i have to do
somekind of casting. Well, here's what it looks like now.. it writes 20
bytes of {*word*99}... if i remove the & in sizeof() it returns 4 bytes... so i
guess 20 bytes makes more sense :p gee what a sense of logic i have hehe...
here's what it looks like:
***************** in html_gen.h *********************
class EW_settings{
public:
char* ftpHost ;
char* ftpUser ;
char* ftpPass ;
char* ftpDir ;
int ftpPort ;
} ;
class EW_site {
public:
DynamicArray<EW_page>page ;
DynamicArray<EW_menuItem>menuItem ;
EW_settings settings ;
};
************** in my form cpp file: **************
EW_site website ;
void __fastcall TFrmMain::cmdWriteTestClick(TObject *Sender)
{
website.settings.ftpDir = "/" ;
website.settings.ftpHost = "somehost.org" ;
website.settings.ftpPass = "letmein" ;
website.settings.ftpUser = "root" ;
website.settings.ftpPort = 21 ;
AnsiString filename ;
filename = "easyweb.cfg" ;
TFileStream *FStream;
try{
FStream = new TFileStream(filename, fmCreate) ;
FStream->Write(&website.settings,sizeof(website.settings)) ;
}__finally{
delete FStream ;
}
}
void __fastcall TFrmMain::cmdReadTestClick(TObject *Sender)
{
website.settings.ftpDir = "" ;
website.settings.ftpHost = "" ;
website.settings.ftpPass = "" ;
website.settings.ftpUser = "" ;
website.settings.ftpPort = 0 ;
AnsiString filename ;
filename = "easyweb.cfg" ;
TFileStream *FStream;
try{
FStream = new TFileStream(filename, fmCreate) ;
FStream->Read(&website.settings,sizeof(website.settings)) ;
}__finally{
delete FStream ;
}
ShowMessage(website.settings.ftpDir) ;
ShowMessage(website.settings.ftpHost) ;
ShowMessage(website.settings.ftpPass) ;
ShowMessage(website.settings.ftpUser) ;
ShowMessage(website.settings.ftpPort) ;
}
 

Re:a file handling Question...

Gothi[c] wrote:
Quote
I changed my code now, using char* but it's still not writing what i want it
Well that is because you should not use pointers but buffers.
A pointer is only four bytes. So if you write the class then those
four bytes will be written but not the -many- bytes of the
buffer where they point to.
Quote
class EW_settings{
public:
char* ftpHost ;
char* ftpUser ;
char* ftpPass ;
char* ftpDir ;
int ftpPort ;
} ;
Make that:
class EW_settings{
public:
char ftpHost[100] ;
char ftpUser[100] ;
char ftpPass[100] ;
char ftpDir[256] ;
int ftpPort ;
};
Well decide if you need 100 and/or 256.
Hans.
 

Re:a file handling Question...

Thanks, this was very helpfull... yes i was thinkig about doing it like
that, i had that as my emergency sollution...
The problem is that, this way i have to define the length of the strings in
advance, and it isn't very dynamic like that, though I think I can
find a way around that by using the AnsiString's .Length thingie, and have
it write the lenght of the string, and then the actual string...
I just hoped there was a simpler way to do this :) I was hoping BCB would do
all that for me LOL I guess i'll just do it the POD way :)
so the output file will look like
[int stringLength]
[char *stringData]
...
[int stringLength]
[char *stringData]
...
etc .
and get the length and data out of an AnsiString's .Length and .c_str()
:)
Thanks! this was helpfull.
"Andrue Cope [TeamB]" < XXXX@XXXXX.COM >schreef in bericht
Quote
Gothi[c] wrote:

>so yes it contains Ansistrings.
>
>maybe i should use char * instead and use the c_str() thingie in
>ansistring?

Ah yes. To POD or not to POD :)

POD=Plain Old Data.
For a practical definition of POD I always suggest "think 'C' types,
not 'C++'" and exclude pointers..or at least be aware of the problem.

Although AnsiString is a wonderful class and a fantastic alternative to
'char *' it is actually just a wrapper around a 'char *'. For the
purpose of writing a structure to disk 'char *' is not much better than
'AnsiString'.

If you write that structure to disk you will only get pointers stored
and of course those pointers will not be valid when you reload them.
Worse still although AnsiString will survive the experience of being
loaded back in that's just a coincedence of the way it's been
implemented. Treating class instances like they were POD is very poor
programming practice.

The only way to save an AnsiString to disk is to dereference its
pointer using c_str() and write those bytes out. You can apparently use
streams with AnsiString but that still doesn't address the issue of
structures containing AnsiStrings.

The best you can do there is to provide a method (or in the case of
streams overload '>>' and '<<') that writes each data member out in
turn. What I do in these cases is group all the POD (Plain old Data)
into a single structure so that I can at least write those out in one
go:

struct TNotPOD
{
AnsiString wibble,
wibble2;
struct TPOD
{
int grundle,
gribble;
char woot[ 100 ];
}POD;
};

You still need to write 'wibble' and 'wibble2' out individually but at
least the others can go just by dumping 'POD'. Writing a member
function or operator overload to do this isn't a waste of time. It will
help from a maintennance and development point of view by having the
structure take care of itself. OTOH it needs to be carefully thought
out and documented because someone might start adding virtual member
functions and then the structure is no longer POD.

An array of 'char' as shown above is the only way to store multiple
strings in a structure that is POD.
--
Andrue Cope [TeamB]
[Bicester, Uk]
info.borland.com/newsgroups/guide.html
 

Re:a file handling Question...

Gothi[c] wrote:
Quote
I just hoped there was a simpler way to do this :) I was hoping BCB
would do all that for me LOL I guess i'll just do it the POD way :)
so the output file will look like
Yup - we have a similar problem but on a larger scale. We have:
Linked list A
Linked list B
Linked list C
Linked list D
Linked list E
And several of the list items have strings or even just blocks of
bytes. All of this needed to be loaded/saved :-/
In the end we wrote several support functions and although someone
still had to write them it makes life easier now. At the core we have
Save/Load data block.
This takes care of prefixing a block length.
Everything else then calls down to that. This still leaves us with
functions for each structure but once written they stay written :)
The only things we'd do differently now are use methods instead of
functions and add a type to the load/save data block function. The
latter would lend itself to generating XML.
--
Andrue Cope [TeamB]
[Bicester, Uk]
info.borland.com/newsgroups/guide.html
 

Re:a file handling Question...

Thank you all for your help! I solved it... here's how it looks now. It
could probably be optimised more, but hey, it works ;)
///////////////////////////////// HTML_GEN.H
/////////////////////////////////////
class EW_settings{
public:
EW_settings() ;
~EW_settings() ;
AnsiString ftpHost ;
AnsiString ftpUser ;
AnsiString ftpPass ;
AnsiString ftpDir ;
int ftpPort ;
int save() ;
int load() ;
private:
struct TPOD{
int ftpHost,ftpUser,ftpPass,ftpDir,ftpPort ;
char data[1120] ;
} POD ;
} ;
class EW_site {
public:
DynamicArray<EW_page>page ;
DynamicArray<EW_menuItem>menuItem ;
EW_settings settings ;
};
///////////////////////////////// HTML_GEN.CPP
/////////////////////////////////////
EW_settings::load(){
TFileStream *FStream ;
try{
FStream = new TFileStream("EasyWeb.cfg", fmOpenRead) ;
FStream->Read(&this->POD,sizeof(this->POD)) ;
}__finally{
delete FStream ;
}
AnsiString temp = this->POD.data ;
int pos = 0 ;
this->ftpDir = temp.SubString(pos,this->POD.ftpDir) ;
pos += this->POD.ftpDir + 1 ;
this->ftpHost = temp.SubString(pos,this->POD.ftpHost) ;
pos += this->POD.ftpHost ;
this->ftpPass = temp.SubString(pos,this->POD.ftpPass) ;
pos += this->POD.ftpPass ;
this->ftpUser = temp.SubString(pos,this->POD.ftpUser) ;
this->ftpPort = this->POD.ftpPort ;
}
EW_settings::save(){
this->POD.ftpDir = this->ftpDir.Length() ;
this->POD.ftpHost = this->ftpHost.Length() ;
this->POD.ftpPass = this->ftpPass.Length() ;
this->POD.ftpUser = this->ftpUser.Length() ;
this->POD.ftpPort = this->ftpPort ;
sprintf(this->POD.data,"%s%s%s%s",this->ftpDir.c_str(),this->ftpHost.c_str(),this->ftpPass.c_str(),this->ftpUser.c_str())
;
TFileStream *FStream ;
try{
FStream = new TFileStream("EasyWeb.cfg", fmCreate) ;
FStream->Write(&this->POD,sizeof(this->POD)) ;
}__finally{
delete FStream ;
}
}
///////////////////////////////// FRMMAIN.CPP
/////////////////////////////////////
#include "html_gen.h"
EW_site website ;
void __fastcall TFrmMain::cmdTestLoadClick(TObject *Sender)
{
website.settings.ftpDir = "" ;
website.settings.ftpHost = "" ;
website.settings.ftpPass = "" ;
website.settings.ftpPort = 0 ;
website.settings.ftpUser = "" ;
website.settings.load() ;
ShowMessage(website.settings.ftpDir) ;
ShowMessage(website.settings.ftpHost) ;
ShowMessage(website.settings.ftpPass) ;
ShowMessage(website.settings.ftpUser) ;
ShowMessage(website.settings.ftpPort) ;
}
void __fastcall TFrmMain::cmdTestSaveClick(TObject *Sender)
{
website.settings.ftpDir = "/" ;
website.settings.ftpHost = "127.0.0.1" ;
website.settings.ftpPass = "somePass" ;
website.settings.ftpPort = 21 ;
website.settings.ftpUser = "someUser" ;
website.settings.save() ;
}
////////////////////////////// end of code //////////////////////////
the file output looks like this:
    /127.0.0.1somePasssomeUser
Thank you all :)
 

Re:a file handling Question...

Gothi[c] wrote:
Quote
Thank you all :)
You're welcome. just bear in mind that arrays of fixed length usually
come back to haunt you later on :)
--
Andrue Cope [TeamB]
[Bicester, Uk]
info.borland.com/newsgroups/guide.html
 

Re:a file handling Question...

Gothi[c] wrote:
Quote
Thank you all for your help! I solved it... here's how it looks now. It
could probably be optimised more, but hey, it works ;)
Yes. But what a terrible overkill of code. And you are not saving
a used structure but constructing a new one solely for saving
some AnsiStrings.
You could as well have used an TIniFile.
Or a TStringList:
Do away with your POD structure.
Quote
EW_settings::save(){
TStringList *StringList = new TStringList;
StringList->Add ( ftpHost );
StringList->Add ( ftpUser );
StringList->Add ( ftpPass );
StringList->Add ( ftpDir );
StringList->SaveToFile ( filename );
delete StringList;
Quote
}
EW_settings::load(){
TStringList *StringList = new TStringList;
StringList->LoadFromFile ( filename );
ftpHost = StringList->Strings [0];
ftpUser = StringList->Strings [1];
ftpPass = StringList->Strings [2];
ftpDirr = StringList->Strings [3];
// errorchecking up to you.
delete StringList;
Quote
}
Hans.
 

Re:a file handling Question...

"Gothi[c]" < XXXX@XXXXX.COM >wrote in message
Quote
I was wondering if there is any way to save the contents
of an entire class with it, without having to save each
property seperatly?
If the class is derived from TComponent, TStream has Read/WriteComponent()
methods available.
If the class is derived from TPersistent, you might be able to hack around
with the TReader/TWriter classes, but note that they were not really
designed to process classes that are not components or do not have component
owners.
If the class is neither of the above, then you will have to store the
propeties manually.
Gambit
 

Re:a file handling Question...

"Gothi[c]" < XXXX@XXXXX.COM >wrote in message
Quote
Well, here's what my class looks like:
One way to address the issue is to add Load() and Save() methods to the
class, ie:
AnsiString ReadString(TStream *Stream);
void WriteString(TStream *Stream, const AnsiString &Str);
class EW_settings
{
public:
//...
void Load(TStream *Stream);
void Save(TStream *Stream);
} ;
Then you can do the following:
AnsiString ReadString(TStream *Stream)
{
int Length = 0;
Stream->Read(&Length, sizeof(int));
AnsiString Result;
if( Length>0 )
Result.SetLength(Length);
Stream->Read(Result.c_str(), Length);
return Result;
}
void WriteString(TStream *Stream, const AnsiString &Str)
{
int Length = Str.Length();
Stream->Read(&Length, sizeof(int));
if( Length>0 )
Stream->Write(Result.c_str(), Length);
}
void EW_settings::Load(TStream *Stream)
{
ftpHost = ReadString(Stream);
ftpUser = ReadString(Stream);
ftpPass = ReadString(Stream);
ftpDir = ReadString(Stream);
Stream->Read(&ftpPort, sizeof(int));
}
void EW_settings::Save(TStream *Stream)
{
WriteString(Stream, ftpHost);
WriteString(Stream, ftpUser);
ReadString(Stream, ftpPass);
ReadString(Stream, ftpDir);
Stream->Write(&ftpPort, sizeof(int));
}
{
TFileStream *FStream = NULL;
try
{
FStream = new TFileStream(".\\easyweb.cfg", fmCreate);
website.settings.Save(FStream);
}__finally {
delete FStream ;
}
}
{
TFileStream *FStream = NULL;
try
{
FStream = new TFileStream("easyweb.cfg", fmOpenRead |
fmShareDenyWrite);
website.settings.Load(FStream));
}__finally {
delete FStream ;
}
}
Gambit
 

Re:a file handling Question...

True that.... ;)
I think i made the buffer large enough though, i'd be really out of luck if
someone uses an insanely long path name.... i guess i should add some error
checking etc for that too, we don't want no buffer overflows :)
"Andrue Cope [TeamB]" < XXXX@XXXXX.COM >schreef in bericht
Quote
Gothi[c] wrote:

>Thank you all :)

You're welcome. just bear in mind that arrays of fixed length usually
come back to haunt you later on :)

--
Andrue Cope [TeamB]
[Bicester, Uk]
info.borland.com/newsgroups/guide.html
 

Re:a file handling Question...

Gothi[c] wrote:
Quote
I think i made the buffer large enough though, i'd be really out of
luck if someone uses an insanely long path name....
And 640kB is enough for anyone, yes?
:)
Quote
i guess i should add some error checking etc for that too, we don't
want no buffer overflows :)
A very good idea.
--
Andrue Cope [TeamB]
[Bicester, Uk]
info.borland.com/newsgroups/guide.html