Tom wrote:
Well I guess what I mean is, if I don't know what the size of a
certain variable is? Say like a string? Does the Palm know that
when it hits a NULL point in a string that its the end?

Or do I have to custom write a routine for this? I just am a
little confused on either have SET variable size and wasting
some space or making it more complex so that I can add
variables of unknown lengths?

Well, of course the records don't have variables in them.  The
records are just a sequence of bytes.

If you'd like the in-memory representation of your variables
to correspond to the sequence of bytes in a record, then one
way to do that is to make each record a struct, and then put
no pointers in the struct.  Then that gives you a fixed-length
struct which can correspond to a fixed-length record.

However, there are some problems with this approach.  The most
obvious is what you mentioned:  that you are wasting space and
also placing arbitrary constraints on the lengths of things.
(And those two things are at odds with each other:  increase
the arbitrary limits, and you waste more space.)  A second
problem is that the machine representation can change.  Integers
are different endianness on different processors (like ARM
vs. 68k).  Structure packing can vary depending on compiler
or compiler options.

So, the other way to approach the problem is to put something
in the records which doesn't exactly correspond to the way
your structure is stored in memory, then convert it both
ways: one type of conversion when you save a record, and a
different when you load it.  There are many ways you can go
about this.  Essentially, any approach amounts to defining
a simple grammar (probably for a regular language,
incidentally...) and a parser and a generator for the grammar.

I personally generally use the latter approach.  I have a
set of functions that can read and write simple data types
(integers, strings, booleans), and then when I define a struct
that needs to get stored in persistent storage, I write
functions to read and write a whole struct; and these functions
are implemented in terms of the primitive data types.
Because it's best to know how big a record will be before you
start writing to it, I also have a function that computes the
length that the record needs to be.

I handle strings by putting a null-terminator into the PDB
record, just like what the C language does with strings in
memory.

Example primitive functions for reading and writing integers:

        Int32 ReadInt32 (const UInt8 **position)
        {
            Int32 result;

            result = ((Int32) *(*position)++);
            result <<= 8;
            result |= ((Int32) *(*position)++);
            result <<= 8;
            result |= ((Int32) *(*position)++);
            result <<= 8;
            result |= ((Int32) *(*position)++);
        }

        void WriteInt32 (UInt8 **position, Int32 value)
        {
            *(*position++) = value >> 24;
            *(*position++) = value >> 16;
            *(*position++) = value >> 8;
            *(*position++) = value;
        }

        Int32 GetInt32Len ()
        {
            return 4;
        }

And for reading/writing strings:

        Char * ReadString (const UInt8 **position)
        {
            Int32 len;
            Char *result;

            len = StrLen ((Char *) *position);

            result = (Char *) MemPtrNew (1 + len);
            if (result == NULL) { *position += 1 + len; return NULL; }

            // since we store a null-terminator, StrCopy() will work
            StrCopy (result, (Char *) *position);
            *position += 1 + len;
        }

        void WriteString (UInt8 **position, const Char *str)
        {
            // write a null-terminator even if string is NULL pointer
            if (str == NULL) { *(*position)++ = 0; return; }

            // store string with a null-terminator
            StrCopy ((Char *) *position, str);
            *position += 1 + StrLen (str);
        }

        void GetStringLen (const Char *str)
        {
            return
                str == NULL
                    ? 1
                    : 1 + StrLen (str);
        }

The imagine a simple struct:

        typedef struct
        {
            Char *name;
            Int32 age;
        } Person;

And some functions for reading/writing it:

        void ReadPerson (const UInt8 **position, Person *person)
        {
            person->name = ReadString (position);
            person->age = ReadInt32 (position);
        }

        void WritePerson (UInt8 **position, const Person *person)
        {
            WriteString (position, person->name);
            WriteInt32 (position, person->age);
        }

        void GetPersonLen (const Person *person)
        {
            return (GetStrLen (person->name) + GetInt32Len());
        }

Now you can save a Person record in a PDB with a sequence like this:

        1.  Put some data into the Person struct.
        2.  Call GetPersonLen() on that struct to find out how big
            the record will need to be.
        3.  Allocate a memory buffer of the required size
            with MemPtrNew().
        4.  Write into that memory with WritePerson().
        5.  Create a PDB record of the appropriate size.
        6.  Copy the data from the memory buffer to the PDB record.
        7.  Delete the memory buffer.

Actually, the WriteString() and WriteInt32() functions can be re-written
to handle offsets and pointers and use DmWrite() to write directly into
the PDB record.  That's a bit of a pain, but it does save a step at
execution time and saves a little temporary memory.  Whether it actually
speeds things up is another question though, since it means you're making
lots of little DmWrite() calls instead of one big one.

Hope that helps.

  - Logan

--
For information on using the PalmSource Developer Forums, or to unsubscribe, 
please see http://www.palmos.com/dev/support/forums/

Reply via email to