Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Persistent Objects in Turbo Pascal


SEP90: PERSISTENT OBJECTS IN TURBO PASCAL

Create a storable object type to share objects between applications

Scott is a free-lance writer; he has more than 15 years of experience in a variety of programming languages. Scott can be reached at 705 W. Virginia, Gunnison, CO 81230.


There are four fundamental features of any true object-oriented programming language: data abstraction, encapsulation, inheritance, and polymorphism. These are the core concepts necessary for object-oriented programming. However, they aren't the only concepts that are useful to the object-oriented programmer.

With most programming languages, objects are created, manipulated, and destroyed within the context of a single program. Each program is a community of objects that do not communicate with other communities of objects. Put yourself in the place of an object. Think of inter-object communication as a network of roads and each program as a city. You can go anywhere inside your own city, but there are no roads leading to other cities. This is frustrating, because it limits the ability of different programs within a system to share objects.

Supporting Persistence

Turbo Pascal, C++, and Smalltalk do not directly support the saving of objects from one invocation of a program to another. This is like building a car every morning, driving it to work, and then having it disintegrate when you get back home at night. Every time you commute to work, you need to build a new car. Similarly, whenever you create an object in a program, it exists only until the program's work is done. Every time the program starts, it has to begin by building new objects.

Bertrand Meyer incorporated the concept of "persistent objects" into his Eiffel programming language. A persistent object can be stored external to a program, usually in a file. More than one program can share the same persistent object, and a program can save its objects for future executions.

Eiffel implements persistent objects via a special base class called STORABLE. Objects of a class derived from STORABLE have store and retrieve methods. When an object is stored, its entire data structure is written to a specified file. Later, that entire structure can be loaded into an object of the same class.

Does having built-in support for persistent objects make Eiffel a superior object-oriented programming language? In one sense, the answer is yes: Anything a programming language can provide for you is a definite advantage over having to develop a facility on your own. However, persistence can be accomplished in other programming languages with a minimal amount of work and the knowledge of object formats.

Persistence with Turbo Pascal

The Turbo Pascal object implementation is relatively simple when compared to Smalltalk and C++. Listing One (page 84) shows an example of a simple Turbo Pascal class hierarchy. Shape is an abstract base class, which defines the common characteristics of all Shape classes. Derived from Shape are two classes, Box and Ring. Box defines a rectangular shape, and Ring defines a circle. The Draw method is polymorphic, by virtue of the VIRTUAL keyword.

Listing Two (page 84) is a short program which uses the polymorphic nature of Draw to implement a generic procedure called DrawShape. The argument to DrawShape is a pointer to the common base class (Shape) of Box and Ring. The call to the Draw method in DrawShape invokes the implementation of Draw for the specific class of the object whose pointer was used as an argument. In other words, passing a pointer to a Box to DrawShape will cause the Box.Draw method to be called.

Making Turbo Pascal objects persistent requires that we understand how objects are stored in memory. A Turbo Pascal object is basically the same thing as a record: The instance variables of an object are stored contiguously in memory, aligned on 16-bit word boundaries. A derived object class will contain all of the members defined for its base class, plus all of the members unique to itself. Shape defines a single instance variable, Color. Because Box is derived from Shape, it inherits Color. You can think of Box, for persistence purposes, as the Pascal RECORD structure shown in Example 1.

Example 1: Representing the Box object as a Turbo Pascal record.

  BoxRec = RECORD
       BEGIN
            Color : INTEGER;
            UpperLeftX, UpperLeftY: INTEGER;
            LowerRightX, LowerRightY: INTEGER;
       END;

The obvious approach to writing an object to disk is to use the same method used for writing a record. Were Box a record as shown in Example 1, a FILE OF Box could be declared to hold Box objects. Alas, a file component type in Turbo Pascal cannot be an object type; FILE OF Box generates an error to this effect.

An untyped file allows us to solve our problem. Listing Three (page 84) employs an untyped file and the BlockWrite and BlockRead functions to store and retrieve Ring and Box objects. The Reset and Rewrite functions declare the file record length to be one, letting us write any number of bytes to the untyped file. When we read and write an object, the SizeOf function is used to get the size of the object.

An object that has virtual methods must be initialized with a CONSTRUCT0R call before it can be loaded with an object value from disk. The CONSTRUCTOR assigns the address to virtual function pointers for the object. The values of the instance variables can then be updated via a call to BlockRead.

Using individual calls to BlockWrite and BlockRead will store the instance variables of an object in a disk file. This isn't the best method, though. For example, let's say that we wanted to create a function which writes any type of Shape to a file. An obvious first attempt at such a function might look like that shown in Example 2.

Example 2: A first attempt at writing a procedure to store a persistent object.

  PROCEDURE WriteShape(f : FILE; s : ^Shape);
  BEGIN
       BlockWrite (f, s, SizeOf(s^));
  END;

It looks good, but it won't do what you might expect. A Shape is defined with only one integer instance variable, so SizeOf(Shape) is only two. A Box has 10 bytes of instance variables, and a Ring has 8 bytes. So, passing the address of a Box (for example) to WriteShape will result in having only the first 2 bytes of the Box stored. Obviously, this is not going to work. A better system is to use methods to store and retrieve objects in files. By making these methods polymorphic across a class hierarchy, generic functions such as WriteShape can be created.

Listing Four (page 84) shows new versions of the Shape, Ring, and Box classes. The Shape object type defines a pair of virtual methods called FWrite and FRead. The Box and Ring classes define specific versions of these methods, which write and read the corresponding object type into the specified file. Listing Five (page 86) shows how these functions can be used in place of the BlockRead and BlockWrite calls used in Listing Three. Using these new methods, a generic WriteShape procedure would look like that shown in Example 3.

Example 3: A generic WriteShape procedure to store persistent objects.

  PROCEDURE WriteShape(f : FILE; s : ^Shape);
  BEGIN
       s->EWrite (f);
  END;

What about building a Turbo Pascal class (object type) that emulates the function of the STORABLE class included with Eiffel? Because Turbo Pascal supports only single-inheritance -- where a class may have only one base class -- STORABLE would have to be the only base class. Using a STORABLE base class with the Shape classes would change their definitions only slightly.

Consider the Storable object type defined in Example 4. The Shape object type and the object types derived from it inherit the basic I/O functions from Storable. Every object type for whom Storable is an ancestor defines its own implementation of the FWrite and FRead methods.

Example 4: A storable base class (object type) that emulates the features of the Eiffel storable class.

  Storable = OBJECT
       BEGIN
       PROCEDURE FWrite(f : FILE);
       PROCEDURE FRead(f : FILE);
       END;

  Shape = OBJECT (Storable)
       BEGIN
       Color : INTEGER;

       PROCEDURE Draw; VIRTUAL;
       END;

Another advantage of read/write methods customized to an object type can be seen with objects that represent dynamic data structures. Let's say that you've created a linked-list object type: Each linked-list object will have a pointer to the first node in the list; the other pointers and the actual data will be stored in dynamic memory. Simply writing a linked-list object directly with a BlockWrite statement will store the pointer only to the first element in the list.

Obviously, this pointer will be meaningless in another execution of the program (because addresses will change). Even worse, the other nodes in the list will be lost when the program terminates. Class-specific read/write methods can solve this problem by writing the data in the nodes of the linked list to the disk file in the same order as they appear in the list. Then a linked list is read from disk, and the dynamic tree can be recreated from the node data.

Conclusion

Persistent objects are useful in restoring the state of objects within a program from one run to the next. This is particularly important for objects that are created on-the-fly. In addition, persistent objects allow different programs to share common objects.

As object-oriented technology unfolds, persistent objects will play an increasingly important role. The benefits will be seen not only in programming, but in applications such as object-oriented databases and, ultimately, in operating systems.

The greatest benefits to programmers will come when persistent objects can be shared between programs written in different languages. Wouldn't it be nice to grab a Pascal object from C++?

A common specification for the creation and management of persistent objects, such as that being proposed by Microsoft, will be needed. As you have seen in this article, it is relatively easy to implement persistent objects in Turbo Pascal. With slight modifications, the ideas presented here can be applied to both Smalltalk and C++.

_PERSISTENT OBJECTS IN TURBO PASCAL_ by Scott Robert Ladd

[LISTING ONE]

<a name="01d4_000c">

UNIT Shapes;

INTERFACE

    USES Graph, Crt;

    TYPE
        {---------- Shape class ----------}
        Shape = OBJECT
            Color : BYTE;

            PROCEDURE Draw; VIRTUAL;
            END;

        ShapePtr = ^Shape;

        {---------- Box class ----------}
        Box = OBJECT (Shape)
            LowerRightX, LowerRightY : INTEGER;
            UpperLeftX,  UpperLeftY  : INTEGER;

            CONSTRUCTOR Create(ulx, uly, lrx, lry, c : INTEGER);
            PROCEDURE Draw; VIRTUAL;
            END;

        {---------- Ring class ----------}
        Ring = OBJECT (Shape)
            Xcenter, Ycenter : INTEGER;
            Radius : INTEGER;

            CONSTRUCTOR Create(x, y, rad, c : INTEGER);
            PROCEDURE Draw; VIRTUAL;
            END;


IMPLEMENTATION

    {---------- Shape methods ----------}

   PROCEDURE Shape.Draw;
   BEGIN
      { Does nothing; it's a virtual method place-holder }
   END;


    {---------- Box methods ----------}

    CONSTRUCTOR Box.Create(ulx, uly, lrx, lry, c : INTEGER);
   BEGIN
         UpperLeftX  := ulx;
         UpperLeftY  := uly;
         LowerRightX := lrx;
         LowerRightY := lry;
         Color       := c;
   END;

   PROCEDURE Box.Draw;
   BEGIN
      SetColor(Color);

      Rectangle(UpperLeftX,  UpperLeftY, LowerRightX, LowerRightY);
   END;

    {---------- Ring methods ----------}

   CONSTRUCTOR Ring.Create(x, y, rad, c : INTEGER);
   BEGIN
      Xcenter := x;
      Ycenter := y;
      Radius  := rad;
        Color   := c;
   END;

   PROCEDURE Ring.Draw;
   BEGIN
      SetColor(Color);

      Circle(Xcenter, Ycenter, Radius);
   END;

END. { UNIT Shapes }




<a name="01d4_000d"><a name="01d4_000d">
<a name="01d4_000e">
[LISTING TWO]
<a name="01d4_000e">

PROGRAM Program1;

USES Graph, Crt, Shapes;

VAR
    R : Ring;
    B : Box;

    GrDriver, GrMode, GrResult : INTEGER;

    ch : CHAR;

PROCEDURE DrawShape(s : ShapePtr);
BEGIN
    s^.Draw;
END;

BEGIN
    R.Create(100,100,90,2);
    B.Create(25,10,175,175,4);

    GrDriver := DETECT;
    InitGraph(GrDriver,GrMode,'d:\tp');

    GrResult := GraphResult;

    IF GrResult <> GrOK THEN
        BEGIN
        WriteLn('Unable to initialize BGI: error ',grResult);
        Halt(1);
        END;

    DrawShape(@R);
    DrawShape(@B);

   REPEAT UNTIL KeyPressed;

   Ch := ReadKey;

    CloseGraph;

END.



<a name="01d4_000f"><a name="01d4_000f">
<a name="01d4_0010">
[LISTING THREE]
<a name="01d4_0010">

PROGRAM Program2;

USES Graph, Crt, Shapes;

VAR
    R1, R2 : Ring;
    B1, B2 : Box;

    F : FILE;

    GrDriver, GrMode, GrResult : INTEGER;

    ch : CHAR;

BEGIN
    R1.Create(100,100,90,2);
    R2.Create(0,0,0,0);
    B1.Create(25,10,175,150,4);
    B2.Create(0,0,0,0,0);

    GrDriver := DETECT;
    InitGraph(GrDriver,GrMode,'d:\tp');

    GrResult := GraphResult;

    IF GrResult <> GrOK THEN
        BEGIN
        WriteLn('Unable to initialize BGI: error ',grResult);
        Halt(1);
        END;

    Assign(F,'shapes.fil');
    Rewrite(F,1);

    BlockWrite(F,R1,SizeOf(R1));
    BlockWrite(F,B1,SizeOf(B1));

    Close(F);

    Assign(F,'shapes.fil');
    Reset(F,1);

    BlockRead(F,R2,SizeOf(R2));
    BlockRead(F,B2,SizeOf(B2));

    R2.Draw;
    B2.Draw;

   REPEAT UNTIL KeyPressed;

   Ch := ReadKey;

    Close(F);

    CloseGraph;

END.



<a name="01d4_0011"><a name="01d4_0011">
<a name="01d4_0012">
[LISTING FOUR]
<a name="01d4_0012">

UNIT Shapes;

INTERFACE

    USES Graph, Crt;

    TYPE
        {---------- Shape class ----------}
        Shape = OBJECT
            Color : BYTE;

            PROCEDURE Draw;   VIRTUAL;

            PROCEDURE FWrite(VAR f : FILE); VIRTUAL;
            PROCEDURE FRead(VAR f : FILE);  VIRTUAL;
            END;

        ShapePtr = ^Shape;

        {---------- Box class ----------}
        Box = OBJECT (Shape)
            UpperLeftX,  UpperLeftY  : INTEGER;
            LowerRightX, LowerRightY : INTEGER;

            CONSTRUCTOR Create(ulx, uly, lrx, lry, c : INTEGER);
            PROCEDURE Draw; VIRTUAL;

            PROCEDURE FWrite(VAR f : FILE); VIRTUAL;
            PROCEDURE FRead(VAR f : FILE);  VIRTUAL;
            END;

        {---------- Ring class ----------}
        Ring = OBJECT (Shape)
            Xcenter, Ycenter : INTEGER;
            Radius : INTEGER;

            CONSTRUCTOR Create(x, y, rad, c : INTEGER);
            PROCEDURE Draw; VIRTUAL;

            PROCEDURE FWrite(VAR f : FILE); VIRTUAL;
            PROCEDURE FRead(VAR f : FILE);  VIRTUAL;
            END;


IMPLEMENTATION

    {---------- Shape methods ----------}

   PROCEDURE Shape.Draw;
   BEGIN
    END;

    PROCEDURE Shape.FWrite(VAR f : FILE);
    BEGIN
    END;

    PROCEDURE Shape.FRead(VAR f : FILE);
    BEGIN
    END;


    {---------- Box methods ----------}

    CONSTRUCTOR Box.Create(ulx, uly, lrx, lry, c : INTEGER);
   BEGIN
         UpperLeftX  := ulx;
         UpperLeftY  := uly;
         LowerRightX := lrx;
         LowerRightY := lry;
         Color       := c;
   END;

   PROCEDURE Box.Draw;
   BEGIN
      SetColor(Color);

      Rectangle(UpperLeftX,  UpperLeftY, LowerRightX, LowerRightY);
   END;

    PROCEDURE Box.FWrite(VAR f : FILE);
    BEGIN
        BlockWrite(f,Color,      SizeOf(INTEGER));
        BlockWrite(f,UpperLeftX, SizeOf(INTEGER));
        BlockWrite(f,UpperLeftY, SizeOf(INTEGER));
        BlockWrite(f,LowerRightX,SizeOf(INTEGER));
        BlockWrite(f,LowerRightY,SizeOf(INTEGER));
    END;

    PROCEDURE Box.FRead(VAR f : FILE);
    BEGIN
        BlockRead(f,Color,      SizeOf(INTEGER));
        BlockRead(f,UpperLeftX, SizeOf(INTEGER));
        BlockRead(f,UpperLeftY, SizeOf(INTEGER));
        BlockRead(f,LowerRightX,SizeOf(INTEGER));
        BlockRead(f,LowerRightY,SizeOf(INTEGER));
    END;

    {---------- Ring methods ----------}

    CONSTRUCTOR Ring.Create(x, y, rad, c : INTEGER);
   BEGIN
      Xcenter := x;
      Ycenter := y;
      Radius  := rad;
        Color   := c;
   END;

   PROCEDURE Ring.Draw;
   BEGIN
      SetColor(Color);

      Circle(Xcenter, Ycenter, Radius);
   END;

    PROCEDURE Ring.FWrite(VAR f : FILE);
    BEGIN
        BlockWrite(f,Color,   SizeOf(INTEGER));
        BlockWrite(f,Xcenter, SizeOf(INTEGER));
        BlockWrite(f,Ycenter, SizeOf(INTEGER));
        BlockWrite(f,Radius,  SizeOf(INTEGER));
    END;

    PROCEDURE Ring.FRead(VAR f : FILE);
    BEGIN
        BlockRead(f,Color,   SizeOf(INTEGER));
        BlockRead(f,Xcenter, SizeOf(INTEGER));
        BlockRead(f,Ycenter, SizeOf(INTEGER));
        BlockRead(f,Radius,  SizeOf(INTEGER));
    END;

END. { UNIT Shapes }



<a name="01d4_0013"><a name="01d4_0013">
<a name="01d4_0014">
[LISTING FIVE]
<a name="01d4_0014">

PROGRAM Program3;

USES Graph, Crt, Shapes;

VAR
    R1, R2 : Ring;
    B1, B2 : Box;

    F : FILE;

    GrDriver, GrMode, GrResult : INTEGER;

    ch : CHAR;

BEGIN
    R1.Create(100,100,90,2);
    R2.Create(0,0,0,0);
    B1.Create(25,10,175,150,4);
    B2.Create(0,0,0,0,0);

    GrDriver := DETECT;
    InitGraph(GrDriver,GrMode,'d:\tp');

    GrResult := GraphResult;

    IF GrResult <> GrOK THEN
        BEGIN
        WriteLn('Unable to initialize BGI: error ',grResult);
        Halt(1);
        END;

    Assign(F,'shapes.fil');
    Rewrite(F,1);

    R1.FWrite(F);
    B1.FWrite(F);

    Close(F);

    Assign(F,'shapes.fil');
    Reset(F,1);

    R2.FRead(F);
    B2.FRead(F);

    R2.Draw;
    B2.Draw;

   REPEAT UNTIL KeyPressed;

   Ch := ReadKey;

    Close(F);

    CloseGraph;

END.


[Example 1:  Representing the Box object as a Turbo Pascal record.]


     BoxRec = RECORD
          BEGIN
               Color : INTEGER;
               UpperLeftX, UpperLeftY : INTEGER;
               LowerRightX, LowerRightY : INTEGER;
          END;

[Example 2:  A first attempt at writing a procedure to store a
persistent object.]

     PROCEDURE WriteShape(f : FILE; s : ^Shape);
     BEGIN
          BlockWrite(f,s,SizeOf(s^));
     END;


[Example 3:  A generic WriteShape procedure to store persistent
objects.]


     PROCEDURE WriteShape(f : FILE; s : ^Shape);
     BEGIN
          s->FWrite(f);
     END;

[Example 4: A Storable base class (object type) that emulates the
features of the Eiffel STORABLE class.]

     Storable = OBJECT
          BEGIN
          PROCEDURE FWrite(f : FILE);
          PROCEDURE FRead(f : FILE);
          END;

     Shape = OBJECT (Storable)
          BEGIN
          Color : INTEGER;

          PROCEDURE Draw; VIRTUAL;
          END;










Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.