www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - What it the preferred method to write a class to a file?

reply Charles D Hixson <charleshixsn earthlink.net> writes:
I'm thinking that I should build two methods, possibly
called write and print where write would export a "binary"
version of the class that is not intended to be human
readable and print would export something with, e.g.,
numbers translated into strings.

It appears as if the methods should be instance methods of
the class.  It looks as if write should be implemented
something like:
void write(Stream stream)
{  stream.writeExact(&this, this.sizeof);	}

Though probably in this version I'd want to include
delimiters, a type id, and a length to facilitate writing
the corresponding read routing (which would need to be a
class method).

It this the best approach?  Also are there any suggestions
as to a reasonable way to specify the type id, so that it
would be the same from run to run?  Should I keep track of
the ids myself (manually)?  If not, how would I know on an
attempt to read which class the type id referred to?
Jul 22 2006
next sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Charles D Hixson wrote:
 I'm thinking that I should build two methods, possibly
 called write and print where write would export a "binary"
 version of the class that is not intended to be human
 readable and print would export something with, e.g.,
 numbers translated into strings.
 
 It appears as if the methods should be instance methods of
 the class.  It looks as if write should be implemented
 something like:
 void write(Stream stream)
 {  stream.writeExact(&this, this.sizeof);	}

That won't work at all. Because classes have reference semantics, all it'll do is write out the memory address of the object. Even so, the arrangement of members within a class isn't guaranteed: http://www.digitalmars.com/d/class.html "The D compiler is free to rearrange the order of fields in a class to optimally pack them in an implementation-defined manner. Consider the fields much like the local variables in a function - the compiler assigns some to registers and shuffles others around all to get the optimal stack frame layout. This frees the code designer to organize the fields in a manner that makes the code more readable rather than being forced to organize it according to machine optimization rules. Explicit control of field layout is provided by struct/union types, not classes." Moreover, every object includes a pointer to the vtable, which will screw things up when the program is run again and the data is read back in. Add to that any pointers, dynamic arrays or object references that your class may contain....
 Though probably in this version I'd want to include
 delimiters, a type id, and a length to facilitate writing
 the corresponding read routing (which would need to be a
 class method).

You need to define a data format that includes all the information that is needed in order to reconstruct the object. Start with a type ID of your own devising (if there's any chance that more than one type fits the context), and write out each member. For primitive types, static arrays and structs that have no reference-semantics members, this is trivial. For dynamic arrays, write out the length followed by the contents. For object references within the class, follow this principle recursively. Be careful of any potential circularity.
 It this the best approach?   Also are there any suggestions
 as to a reasonable way to specify the type id, so that it
 would be the same from run to run? 

Only the one you suggest next:
 Should I keep track of the ids myself (manually)?

Yes. Stewart. -- -----BEGIN GEEK CODE BLOCK----- Version: 3.1 GCS/M d- s:- C++ a->--- UB P+ L E W++ N+++ o K- w++ O? M V? PS- PE- Y? PGP- t- 5? X? R b DI? D G e++++ h-- r-- !y ------END GEEK CODE BLOCK------ My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit.
Jul 23 2006
parent Charles D Hixson <charleshixsn earthlink.net> writes:
Stewart Gordon wrote:
 Charles D Hixson wrote:
 I'm thinking that I should build two methods, possibly
 called write and print where write would export a "binary"
 version of the class that is not intended to be human
 readable and print would export something with, e.g.,
 numbers translated into strings.

 It appears as if the methods should be instance methods of
 the class.  It looks as if write should be implemented
 something like:
 void write(Stream stream)
 {  stream.writeExact(&this, this.sizeof);    }

That won't work at all. Because classes have reference semantics, all it'll do is write out the memory address of the object. Even so, the arrangement of members within a class isn't guaranteed: http://www.digitalmars.com/d/class.html "The D compiler is free to rearrange the order of fields in a class to optimally pack them in an implementation-defined manner. Consider the fields much like the local variables in a function - the compiler assigns some to registers and shuffles others around all to get the optimal stack frame layout. This frees the code designer to organize the fields in a manner that makes the code more readable rather than being forced to organize it according to machine optimization rules. Explicit control of field layout is provided by struct/union types, not classes." Moreover, every object includes a pointer to the vtable, which will screw things up when the program is run again and the data is read back in. Add to that any pointers, dynamic arrays or object references that your class may contain....
 Though probably in this version I'd want to include
 delimiters, a type id, and a length to facilitate writing
 the corresponding read routing (which would need to be a
 class method).

You need to define a data format that includes all the information that is needed in order to reconstruct the object. Start with a type ID of your own devising (if there's any chance that more than one type fits the context), and write out each member. For primitive types, static arrays and structs that have no reference-semantics members, this is trivial. For dynamic arrays, write out the length followed by the contents. For object references within the class, follow this principle recursively. Be careful of any potential circularity.
 It this the best approach?   Also are there any suggestions
 as to a reasonable way to specify the type id, so that it
 would be the same from run to run? 

Only the one you suggest next:
 Should I keep track of the ids myself (manually)?

Yes. Stewart.

Jul 23 2006
prev sibling next sibling parent reply Chad J <gamerChad _spamIsBad_gmail.com> writes:
Charles D Hixson wrote:
 I'm thinking that I should build two methods, possibly
 called write and print where write would export a "binary"
 version of the class that is not intended to be human
 readable and print would export something with, e.g.,
 numbers translated into strings.
 
 It appears as if the methods should be instance methods of
 the class.  It looks as if write should be implemented
 something like:
 void write(Stream stream)
 {  stream.writeExact(&this, this.sizeof);	}
 
 Though probably in this version I'd want to include
 delimiters, a type id, and a length to facilitate writing
 the corresponding read routing (which would need to be a
 class method).
 
 It this the best approach?  Also are there any suggestions
 as to a reasonable way to specify the type id, so that it
 would be the same from run to run?  Should I keep track of
 the ids myself (manually)?  If not, how would I know on an
 attempt to read which class the type id referred to?

There was a discussion about this a while ago that had some suggestions, at least for the binary approach: http://www.digitalmars.com/d/archives/digitalmars/D/37739.html Hope it helps.
Jul 23 2006
parent reply Charles D Hixson <charleshixsn earthlink.net> writes:
Chad J wrote:
 Charles D Hixson wrote:
...class method).

 It this the best approach?  Also are there any suggestions
 as to a reasonable way to specify the type id, so that it
 would be the same from run to run?  Should I keep track of
 the ids myself (manually)?  If not, how would I know on an
 attempt to read which class the type id referred to?

There was a discussion about this a while ago that had some suggestions, at least for the binary approach: http://www.digitalmars.com/d/archives/digitalmars/D/37739.html Hope it helps.

const uint eor = 0x19191919; class nnv { protected float[] vec; const char[4] sig = "nvec"; ulong write(Stream s) in { assert (s.isOpen()); assert (s.seekable); } body { ulong oStart = s.position; s.write(cast(ulong)0); s.write(sig[0]); s.write(sig[1]); s.write(sig[2]); s.write(sig[3]); s.write(cast(ulong)(this.vec.length)); foreach(float f; this.vec) s.write(f); s.write(eor); ulong oEnd = s.position; s.position = oStart; s.write(cast(ulong)(oEnd - oStart) ); s.position = oEnd; } } So I guess that we're heading in the same directions (except that for this class a separate struct didn't make much sense). I should probably calculate the overhead so that I don't need to go back and forth to write the length. I return the position to make it easy to create an index, and include length, type, and eor to make it feasible to rebuild an index if the current one becomes corrupt. (Theoretically I shouldn't need the eor...but I grew up with tape parity errors, and anyway any compression method would reduce that. The problems with this solution come in scaling. Consider what would happen if one were dealing with many different kinds of record...and some were composite. Do-able doesn't mean elegant. This basic design is limited in the number of kinds of record it can handle...but long before that limit is reached it would be paralyzed by the clumsiness. P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)
Jul 23 2006
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Charles D Hixson" <charleshixsn earthlink.net> wrote in message 
news:ea0rll$5h9$1 digitaldaemon.com...

 P.S.:  Is there a standard library routine for converting
 between strings of length 4 and uint-s?  If so I wasn't able
 to find it.  If not, I wasn't able to determine that it
 didn't exist.  (That would have made writing the sig more
 efficient.)

You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;
Jul 23 2006
parent reply Charles D Hixson <charleshixsn earthlink.net> writes:
Jarrett Billingsley wrote:
 "Charles D Hixson" <charleshixsn earthlink.net> wrote in message 
 news:ea0rll$5h9$1 digitaldaemon.com...
 
 P.S.:  Is there a standard library routine for converting
 between strings of length 4 and uint-s?  If so I wasn't able
 to find it.  If not, I wasn't able to determine that it
 didn't exist.  (That would have made writing the sig more
 efficient.)

You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;

But one of the things that cause me to prefer D over C is the ability to avoid pointer manipulation, which to me seem extremely hazardous and, when one gets beyond simple cases, quite confusing. (And unmaintainable.)
Jul 23 2006
next sibling parent reply "Regan Heath" <regan netwin.co.nz> writes:
On Sun, 23 Jul 2006 17:26:57 -0700, Charles D Hixson  
<charleshixsn earthlink.net> wrote:
 Jarrett Billingsley wrote:
 "Charles D Hixson" <charleshixsn earthlink.net> wrote in message
 news:ea0rll$5h9$1 digitaldaemon.com...

 P.S.:  Is there a standard library routine for converting
 between strings of length 4 and uint-s?  If so I wasn't able
 to find it.  If not, I wasn't able to determine that it
 didn't exist.  (That would have made writing the sig more
 efficient.)

You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;

But one of the things that cause me to prefer D over C is the ability to avoid pointer manipulation, which to me seem extremely hazardous and, when one gets beyond simple cases, quite confusing. (And unmaintainable.)

If it works, then I say put it in a function and ignore 'how' it works. More than likely it will be inlined and you'll never need to worry about it again. Regan
Jul 23 2006
parent Charles D Hixson <charleshixsn earthlink.net> writes:
Regan Heath wrote:
 On Sun, 23 Jul 2006 17:26:57 -0700, Charles D Hixson
 <charleshixsn earthlink.net> wrote:
 Jarrett Billingsley wrote:
 "Charles D Hixson" <charleshixsn earthlink.net> wrote in message
 ...

You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;

But one of the things that cause me to prefer D over C is the ability to avoid pointer manipulation, which to me seem extremely hazardous and, when one gets beyond simple cases, quite confusing. (And unmaintainable.)

If it works, then I say put it in a function and ignore 'how' it works. More than likely it will be inlined and you'll never need to worry about it again. Regan

ugly because D won't allow functions to return char[4] and won't allow them as out parameters. (So I had to make it a char[], and that worked.)
Jul 24 2006
prev sibling parent reply "Regan Heath" <regan netwin.co.nz> writes:
On Sun, 23 Jul 2006 17:26:57 -0700, Charles D Hixson  
<charleshixsn earthlink.net> wrote:
 Jarrett Billingsley wrote:
 "Charles D Hixson" <charleshixsn earthlink.net> wrote in message
 news:ea0rll$5h9$1 digitaldaemon.com...

 P.S.:  Is there a standard library routine for converting
 between strings of length 4 and uint-s?  If so I wasn't able
 to find it.  If not, I wasn't able to determine that it
 didn't exist.  (That would have made writing the sig more
 efficient.)

You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;

But one of the things that cause me to prefer D over C is the ability to avoid pointer manipulation, which to me seem extremely hazardous and, when one gets beyond simple cases, quite confusing. (And unmaintainable.)

Some alternatives to consider... import std.stdio; uint char_to_uint(char[] str) { return *cast(uint*)str.ptr; } uint char_to_uint_a(char[] str) { uint i = 0; for(int j = 3; j >= 0; j--) { i <<= 8; i |= str[j]; } return i; } /* Option #1 Note: .dup is required the local 'i' becomes invalid after the function returns char[] uint_to_char(uint i) { char[] str = new char[4]; *cast(uint*)str.ptr = i; return str.dup; } Option #2 Note: no dup required, we're copying the data to the new array. char[] uint_to_char(uint i) { char[] str = new char[4]; str[] = (cast(char*)&i)[0..4]; return str; } */ //Note: same as #1 but involves 1 less temporary array. char[] uint_to_char(uint i) { return (cast(char*)&i)[0..4].dup; } char[] uint_to_char_a(uint i) { char[] str = new char[4]; foreach(inout c; str) { c = i&0xFF; i >>= 8; } return str; } void main() { char[] str = "abcd"; writefln("%b (%d)",char_to_uint(str),char_to_uint(str)); writefln("%b (%d)",char_to_uint_a(str),char_to_uint_a(str)); writefln(uint_to_char(char_to_uint(str))); writefln(uint_to_char_a(char_to_uint(str))); } Also, have you considered the 'endian' issues of storing data in a binary format. See: http://en.wikipedia.org/wiki/Endian Specifically, if you plan to use a file saved on a machine which is little endian, i.e. your typical x86 pentium/amd and transfer it to a big endian machine i.e. a solaris sparc server or powerPC and load it. If you do then you will need to decide on an endian format to save to and load from and therefore perform conversion on one of the systems (and not the other). To perform conversion you would simply modify the _a functions above to process the characters in reverse order. Regan
Jul 23 2006
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Regan Heath" <regan netwin.co.nz> wrote in message 
news:optc56yyzv23k2f5 nrage...

 Option #1
 Note: .dup is required the local 'i' becomes invalid after the function 
 returns
 char[] uint_to_char(uint i)
 {
 char[] str = new char[4];
 *cast(uint*)str.ptr = i;
 return str.dup;
 }

I don't think a .dup is required here; when you write "*cast(uint*)str.ptr = i", it's copying the data from i to the array's memory, not setting the array's pointer to point to the integer. Option 2 is actually semantically the same. Would be kind of interesting to see what machine code each version produces :)
Jul 24 2006
parent "Regan Heath" <regan netwin.co.nz> writes:
On Mon, 24 Jul 2006 20:15:03 -0400, Jarrett Billingsley  
<kb3ctd2 yahoo.com> wrote:
 "Regan Heath" <regan netwin.co.nz> wrote in message
 news:optc56yyzv23k2f5 nrage...

 Option #1
 Note: .dup is required the local 'i' becomes invalid after the function
 returns
 char[] uint_to_char(uint i)
 {
 char[] str = new char[4];
 *cast(uint*)str.ptr = i;
 return str.dup;
 }

I don't think a .dup is required here; when you write "*cast(uint*)str.ptr = i", it's copying the data from i to the array's memory, not setting the array's pointer to point to the integer.

Oops, you're right.
 Option 2 is actually semantically
 the same.  Would be kind of interesting to see what machine code each
 version produces :)

Regan
Jul 24 2006
prev sibling parent reply xs0 <xs0 xs0.com> writes:
 P.S.:  Is there a standard library routine for converting
 between strings of length 4 and uint-s?  If so I wasn't able
 to find it.  If not, I wasn't able to determine that it
 didn't exist.  (That would have made writing the sig more
 efficient.)

union { uint asUInt; char[4] asChars; } or something to that effect :)
Jul 24 2006
parent reply Charles D Hixson <charleshixsn earthlink.net> writes:
xs0 wrote:
 
 P.S.:  Is there a standard library routine for converting
 between strings of length 4 and uint-s?  If so I wasn't able
 to find it.  If not, I wasn't able to determine that it
 didn't exist.  (That would have made writing the sig more
 efficient.)

union { uint asUInt; char[4] asChars; } or something to that effect :)

using "illegal utf8 characters". But Jarrett's approach worked fine.
Jul 24 2006
parent reply Derek <derek psyc.ward> writes:
On Mon, 24 Jul 2006 13:34:12 -0700, Charles D Hixson wrote:

 xs0 wrote:
 
 P.S.:  Is there a standard library routine for converting
 between strings of length 4 and uint-s?  If so I wasn't able
 to find it.  If not, I wasn't able to determine that it
 didn't exist.  (That would have made writing the sig more
 efficient.)

union { uint asUInt; char[4] asChars; } or something to that effect :)

using "illegal utf8 characters". But Jarrett's approach worked fine.

How about union { uint asUInt; ubyte[4] asChars; } Are you really using *characters* or *bytes*? -- Derek Parnell Melbourne, Australia "Down with mediocrity!"
Jul 24 2006
parent Charles D Hixson <charleshixsn earthlink.net> writes:
Derek wrote:
 On Mon, 24 Jul 2006 13:34:12 -0700, Charles D Hixson wrote:
 
 xs0 wrote:
 P.S.:  Is there a standard library routine for converting
 between strings of length 4 and uint-s?  If so I wasn't able
 to find it.  If not, I wasn't able to determine that it
 didn't exist.  (That would have made writing the sig more
 efficient.)

uint asUInt; char[4] asChars; } or something to that effect :)

using "illegal utf8 characters". But Jarrett's approach worked fine.

How about union { uint asUInt; ubyte[4] asChars; } Are you really using *characters* or *bytes*?

uint char4ToUint(char[4] item) { return *cast(uint *)item.ptr; } char[] uintToChar4(uint item) { char[] itmStr = new char[4]; *cast(uint *)itmStr.ptr = item; return itmStr[0..4]; } Note that even with the version the "illegal utf8 characters" sneaks in unless I return a range from the string. I have no idea as to why, I've just tried various things until something worked. (Note the heavy influence of Jarrett's code.) The test I used was to run a string through both of them in turn, and check that the output was the same as the input. I didn't check all possible strings, so there may be corner cases. E.g., there may well be numbers that will generate illegal utf-8 characters. (I can't see how, but then I don't know why this version works and several that appeared to me to be essentially equivalent didn't work.) I didn't save the version using a union, and I didn't try a byte array. (I don't know why...perhaps that seemed like a way of dodging the error message without dodging the error? Since currently a lot of what is happening feels like magic, I tend to be a bit conservative when an unexplained error message shows up.)
Jul 25 2006
prev sibling parent reply BCS <BCS pathlink.com> writes:
Charles D Hixson wrote:
 I'm thinking that I should build two methods, possibly
 called write and print where write would export a "binary"
 version of the class that is not intended to be human
 readable and print would export something with, e.g.,
 numbers translated into strings.
 
 It appears as if the methods should be instance methods of
 the class.  It looks as if write should be implemented
 something like:
 void write(Stream stream)
 {  stream.writeExact(&this, this.sizeof);	}
 
 Though probably in this version I'd want to include
 delimiters, a type id, and a length to facilitate writing
 the corresponding read routing (which would need to be a
 class method).
 
 It this the best approach?  Also are there any suggestions
 as to a reasonable way to specify the type id, so that it
 would be the same from run to run?  Should I keep track of
 the ids myself (manually)?  If not, how would I know on an
 attempt to read which class the type id referred to?

I would define a struct that contains all of the trivial types and placeholders for the non trivial types (arrays would be replaced by an Array struct, etc.) In the simple case all that is needed is to copy the elements into the struct and output it. If reference types are used, more complicated things need to be done. One option is to block out space for the struct and then output the referenced data (storing file pointers to the data in the struct), then backup and put the struct in the hole you left. Referenced objects might still be an issue but they may be handled recursively (watch out for circular references). Pulling data out of the file is much the same. I haven't tried anything like this with covariant classes. So I haven't any ideas on how to make that work. This has the advantage over sterilization that the file can be navigated like the original data without having to walk the entire file.
Jul 25 2006
parent reply Charles D Hixson <charleshixsn earthlink.net> writes:
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

Well, here's where I am now.
Granted the get and save aren't totally general...but it
demonstrates where I'm headed.  I plan to save several
different instances of different classes to the same file.
(And, of course, to keep an index...which I'll need to write
just as I close the file...or possibly to keep in a separate
file.)
Jul 25 2006
parent reply kris <foo bar.com> writes:
just an FYI ~ Mango has supported this for years now:

class Vec : IReadable, IWritable
{
    float[] vec;
    char[]  sig;

    void read (IReader input)
    {
       input (sig) (vec);
    }

    void write (IWriter output)
    {
       output (sig) (vec);
    }
}


void main()
{
    auto vec = new Vec;

    auto f = new FileConduit ("myfile", FileConduit.ReadWriteCreate);
    auto write = new Writer (f);
    auto read = new Reader (f);

    // write and flush
    write (vec) ();

    // rewind and read
    f.seek (0);
    read (vec);
}

Reader/Writer has endian versions, and text versions ...




Charles D Hixson wrote:
 Well, here's where I am now.
 Granted the get and save aren't totally general...but it
 demonstrates where I'm headed.  I plan to save several
 different instances of different classes to the same file.
 (And, of course, to keep an index...which I'll need to write
 just as I close the file...or possibly to keep in a separate
 file.)
 
 
 
 ------------------------------------------------------------------------
 
 class Vec
 {  protected   float[]  vec;
    const char[4]  sig   =  "nVec";
 
 // constructors
 
    this(int len, float val)
    {  initRand();
       vec.length  =  len;
       for   (int i = 0; i < vec.length;   i++)  vec[i]   =  val;
    }
 
 // a lot of stuff was removed here
 
 
   /**
    *  Export the object to a seekable stream.  The format is only
    *  intended for computers, but it should be lossless.
    *  The returned value is the stream address of the start of the object.
    */
    ulong write(Stream s)
       in
       {  assert (s.isOpen());
          assert (s.writeable);
          assert (s.seekable);
       }
       body
       {  ulong oStart   =  s.position;
          s.write(cast(uint)0);
          s.write(char4ToUint(sig));
          s.write(cast(uint)(this.vec.length));
          foreach(float f;  this.vec)   s.write(f);
          s.write(eor);
          ulong oEnd  =  s.position;
          s.position(oStart);
          s.write(cast(uint)(oEnd - oStart) );
          s.position(oEnd);
          return   oStart;
       }
 
   /**
    *  Import the object from a seekable stream.  The format is only
    *  intended for computers, but it should be lossless.
    */
    static   Vec   get(Stream s)
       in
       {  assert (s.isOpen());
          assert (s.readable);
          assert (s.seekable);
       }
       body
       {  ulong oStart   =  s.position;
          Vec   v  =  new   Vec();
          uint  len, vlen, veor, tmpSig2;
          char[]   sig2;
          s.read(len);
          s.read(tmpSig2);
          sig2  =  uintToChar4(tmpSig2);
          if (sig != sig2)
          {  s.position(oStart);
             writefln("original signature = <", sig, ">");
             writefln("read in signature  = <", sig2, ">");
             throw new   Exception ("Vec:get: Signature mismatch");
          }
          s.read(cast(uint)(vlen));
          v.length =  vlen;
          for(int i = 0; i < vlen;   i++)  s.read(v.vec[i]);
          s.read(veor);
          if (eor != veor)
          {  s.position(oStart);
             throw new   Exception ("Vec:get: eor not detected when expected");
          }
          ulong oEnd  =  s.position;
          if (len != cast(uint)(oEnd - oStart) )
          {  s.position(oStart);
             writefln("length calculated = %d", len);
             writefln("length read       = %d", cast(uint)(oEnd - oStart));
             throw new   Exception
                   ("Vec:get: length calculated does not match length read");
          }
          return   v;
       }  // get
 
 }

Jul 25 2006
parent Charles D Hixson <charleshixsn earthlink.net> writes:
kris wrote:
 just an FYI ~ Mango has supported this for years now:
 
 class Vec : IReadable, IWritable
 {
    float[] vec;
    char[]  sig;
 
    void read (IReader input)
    {
       input (sig) (vec);
    }
 
    void write (IWriter output)
    {
       output (sig) (vec);
    }
 }
 
 
 void main()
 {
    auto vec = new Vec;
 
    auto f = new FileConduit ("myfile", FileConduit.ReadWriteCreate);
    auto write = new Writer (f);
    auto read = new Reader (f);
 
    // write and flush
    write (vec) ();
 
    // rewind and read
    f.seek (0);
    read (vec);
 }
 
 Reader/Writer has endian versions, and text versions ...
 
 
 
 
 Charles D Hixson wrote:
 Well, here's where I am now.
 Granted the get and save aren't totally general...but it
 demonstrates where I'm headed.  I plan to save several
 different instances of different classes to the same file.
 (And, of course, to keep an index...which I'll need to write
 just as I close the file...or possibly to keep in a separate
 file.)



 ------------------------------------------------------------------------

 class Vec
 {  protected   float[]  vec;
    const char[4]  sig   =  "nVec";

 // constructors

    this(int len, float val)
    {  initRand();
       vec.length  =  len;
       for   (int i = 0; i < vec.length;   i++)  vec[i]   =  val;
    }

 // a lot of stuff was removed here


   /**
    *  Export the object to a seekable stream.  The format is only
    *  intended for computers, but it should be lossless.
    *  The returned value is the stream address of the start of the
 object.
    */
    ulong write(Stream s)
       in
       {  assert (s.isOpen());
          assert (s.writeable);
          assert (s.seekable);
       }
       body
       {  ulong oStart   =  s.position;
          s.write(cast(uint)0);
          s.write(char4ToUint(sig));
          s.write(cast(uint)(this.vec.length));
          foreach(float f;  this.vec)   s.write(f);
          s.write(eor);
          ulong oEnd  =  s.position;
          s.position(oStart);
          s.write(cast(uint)(oEnd - oStart) );
          s.position(oEnd);
          return   oStart;
       }

   /**
    *  Import the object from a seekable stream.  The format is only
    *  intended for computers, but it should be lossless.
    */
    static   Vec   get(Stream s)
       in
       {  assert (s.isOpen());
          assert (s.readable);
          assert (s.seekable);
       }
       body
       {  ulong oStart   =  s.position;
          Vec   v  =  new   Vec();
          uint  len, vlen, veor, tmpSig2;
          char[]   sig2;
          s.read(len);
          s.read(tmpSig2);
          sig2  =  uintToChar4(tmpSig2);
          if (sig != sig2)
          {  s.position(oStart);
             writefln("original signature = <", sig, ">");
             writefln("read in signature  = <", sig2, ">");
             throw new   Exception ("Vec:get: Signature mismatch");
          }
          s.read(cast(uint)(vlen));
          v.length =  vlen;
          for(int i = 0; i < vlen;   i++)  s.read(v.vec[i]);
          s.read(veor);
          if (eor != veor)
          {  s.position(oStart);
             throw new   Exception ("Vec:get: eor not detected when
 expected");
          }
          ulong oEnd  =  s.position;
          if (len != cast(uint)(oEnd - oStart) )
          {  s.position(oStart);
             writefln("length calculated = %d", len);
             writefln("length read       = %d", cast(uint)(oEnd -
 oStart));
             throw new   Exception
                   ("Vec:get: length calculated does not match length
 read");
          }
          return   v;
       }  // get

 }


I'll need to look into what else Mango provides...it may save me a lot of work.
Jul 25 2006