Subsections


5. Classes

In the Delphi approach to Object Oriented Programming, everything revolves around the concept of 'Classes'. A class can be seen as a pointer to an object, or a pointer to a record.

Remark: In earlier versions of Free Pascal it was necessary, in order to use classes, to put the objpas unit in the uses clause of your unit or program. This is no longer needed as of version 0.99.12. As of version 0.99.12 the system unit contains the basic definitions of TObject and TClass, as well as some auxiliary methods for using classes. The objpas unit still exists, and contains some redefinitions of basic types, so they coincide with Delphi types. The unit will be loaded automatically if you specify the -S2 or -Sd options.

5.1 Class definitions

The prototype declaration of a class is as follows :

Class types

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \begin{displaymath}...
...s\ visibility\ specifier}
\end{rep} \lit*{end}
\end{displaymath}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{heritage} \lit*( \synt{class\ type\ identifier} \lit* )\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{component\ li...
...on} \\
\synt{property\ definition}
\)\\
\>
\end{displaymath}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{field\ definition} \synt{identifier\ list} \lit*: \synt{type} \lit*;\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{method\ defin...
...\begin{displaymath}\synt{call\ modifiers} \lit*; \end{displaymath}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{class\ visibi...
...ivate}\\
\lit*{protected}\\
\lit*{public}\\
\lit*{published}
\)\end{syntdiag}
Again, You can repeat as many private, protected, published and public blocks as you want. Methods are normal function or procedure declarations. As you can see, the declaration of a class is almost identical to the declaration of an object. The real difference between objects and classes is in the way they are created (see further in this chapter). The visibility of the different sections is as follows:
Private
All fields and methods that are in a private block, can only be accessed in the module (i.e. unit) that contains the class definition. They can be accessed from inside the classes' methods or from outside them (e.g. from other classes' methods)
Protected
Is the same as Private, except that the members of a Protected section are also accessible to descendent types, even if they are implemented in other modules.
Public
sections are always accessible.
Published
Is the same as a Public section, but the compiler generates also type information that is needed for automatic streaming of these classes. Fields defined in a published section must be of class type. Array peroperties cannot be in a published section.

5.2 Class instantiation

Classes must be created using their constructor. Remember that a class is a pointer to an object, so when you declare a variable of some class, the compiler just allocates a pointer, not the entire object. The constructor of a class returns a pointer to an initialized instance of the object. So, to initialize an instance of some class, you would do the following :
  ClassVar := ClassType.ConstructorName;
You cannot use the extended syntax of new and dispose to instantiate and destroy class instances. That construct is reserved for use with objects only. Calling the constructor will provoke a call to getmem, to allocate enough space to hold the class instance data. After that, the constuctor's code is executed. The constructor has a pointer to it's data, in self.

Remark:

5.3 Methods

5.3.1 invocation

Method invocaticn for classes is no different than for objects. The following is a valid method invocation:
Var  AnObject : TAnObject;
begin
  AnObject := TAnObject.Create;
  ANobject.AMethod;

5.3.2 Virtual methods

Classes have virtual methods, just as objects do. There is however a difference between the two. For objects, it is sufficient to redeclare the same method in a descendent object with the keyword virtual to override it. For classes, the situation is different: you must override virtual methods with the override keyword. Failing to do so, will start a new batch of virtual methods, hiding the previous one. The Inherited keyword will not jump to the inherited method, if virtual was used.

The following code is wrong:

Type ObjParent = Class
        Procedure MyProc; virtual;
     end;
     ObjChild  = Class(ObjPArent)
       Procedure MyProc; virtual;
     end;
The compiler will produce a warning:
Warning: An inherited method is hidden by OBJCHILD.MYPROC
The compiler will compile it, but using Inherited can produce strange effects.

The correct declaration is as follows:

Type ObjParent = Class
        Procedure MyProc; virtual;
     end;
     ObjChild  = Class(ObjPArent)
       Procedure MyProc; override;
     end;
This will compile and run without warnings or errors.

5.3.3 Message methods

New in classes are message methods. Pointers to message methods are stored in a special table, together with the integer or string cnstant that they were declared with. They are primarily intended to ease programming of callback functions in several GUI toolkits, such as Win32 or GTK. In difference with Delphi, Free Pascal also accepts strings as message identifiers.

Message methods that are declared with an integer constant can take only one var argument (typed or not):

 Procedure TMyObject.MyHandler(Var Msg); Message 1;
The method implementation of a message function is no different from an ordinary method. It is also possible to call a message method directly, but you should not do this. Instead use the TObject.Dispatch method.

The TOBject.Dispatch method can be used to call a message handler. It is declared in the system unit and will accept a var parameter which must have at the first position a cardinal with the message ID that should be called. For example:

Type
  TMsg = Record
    MSGID : Cardinal
    Data : Pointer;
Var
  Msg : TMSg;

MyObject.Dispatch (Msg);
In this example, the Dispatch method will look at the object and all it's ancestors (starting at the object, and searching up the class tree), to see if a message method with message MSGID has been declared. If such a method is found, it is called, and passed the Msg parameter.

If no such method is found, DefaultHandler is called. DefaultHandler is a virtual method of TObject that doesn't do anything, but which can be overridden to provide any processing you might need. DefaultHandler is declared as follows:

   procedure defaulthandler(var message);virtual;

In addition to the message method with a Integer identifier, Free Pascal also supports a messae method with a string identifier:

 Procedure TMyObject.MyStrHandler(Var Msg); Message 'OnClick';

The working of the string message handler is the same as the ordinary integer message handler:

The TOBject.DispatchStr method can be used to call a message handler. It is declared in the system unit and will accept one parameter which must have at the first position a string with the message ID that should be called. For example:

Type
  TMsg = Record
    MsgStr : String[10]; // Arbitrary length up to 255 characters.
    Data : Pointer;
Var
  Msg : TMSg;

MyObject.DispatchStr (Msg);
In this example, the DispatchStr method will look at the object and all it's ancestors (starting at the object, and searching up the class tree), to see if a message method with message MsgStr has been declared. If such a method is found, it is called, and passed the Msg parameter.

If no such method is found, DefaultHandlerStr is called. DefaultHandlerStr is a virtual method of TObject that doesn't do anything, but which can be overridden to provide any processing you might need. DefaultHandlerStr is declared as follows:

   procedure DefaultHandlerStr(var message);virtual;

In addition to this mechanism, a string message method accepts a self parameter:

  TMyObject.StrMsgHandler(Data : Pointer; Self : TMyObject);Message 'OnClick';
When encountering such a method, the compiler will generate code that loads the Self parameter into the object instance pointer. The result of this is that it is possible to pass Self as a parameter to such a method.

Remark: The type of the Self parameter must be of the same class as the class you define the method for.

5.4 Properties

Classes can contain properties as part of their fields list. A property acts like a normal field, i.e. you can get or set it's value, but allows to redirect the access of the field through functions and procedures. They provide a means to associate an action with an assignment of or a reading from a class 'field'. This allows for e.g. checking that a value is valid when assigning, or, when reading, it allows to construct the value on the fly. Moreover, properties can be read-only or write only. The prototype declaration of a property is as follows:

Properties

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{property\ def...
...{property\ interface} \end{displaymath}\synt{property\ specifiers}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{property\ int...
...isplaymath}
\lit*{index} \synt{integer constant}
\end{displaymath}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{property\ par...
...st} \lit*[
\<[b] \synt{parameter\ declaration} \\ \lit; \>
\lit*]\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{property\ spe...
...ath}\begin{displaymath}\synt{default\ specifier} \end{displaymath}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{read\ specifier} \lit*{read} \synt{field\ or\ method}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{write\ specifier} \lit*{write} \synt{field\ or\ method}\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{default\ spec...
...playmath}\synt{constant} \end{displaymath} \\
\lit*{nodefault}
\)\end{syntdiag}

\begin{syntdiag}\setlength{\sdmidskip}{.5em}\sffamily\sloppy \synt{field\ or\ method}
\(
\synt{field\ identifier} \\
\synt{method\ identifier}
\)\end{syntdiag}
A read specifier is either the name of a field that contains the property, or the name of a method function that has the same return type as the property type. In the case of a simple type, this function must not accept an argument. A read specifier is optional, making the property write-only. A write specifier is optional: If there is no write specifier, the property is read-only. A write specifier is either the name of a field, or the name of a method procedure that accepts as a sole argument a variable of the same type as the property. The section (private, published) in which the specified function or procedure resides is irrelevant. Usually, however, this will be a protected or private method. Example: Given the following declaration:
Type
  MyClass = Class
    Private
    Field1 : Longint;
    Field2 : Longint;
    Field3 : Longint;
    Procedure  Sety (value : Longint);
    Function Gety : Longint;
    Function Getz : Longint;
    Public
    Property X : Longint Read Field1 write Field2;
    Property Y : Longint Read GetY Write Sety;
    Property Z : Longint Read GetZ;
    end;
Var MyClass : TMyClass;
The following are valid statements:
WriteLn ('X : ',MyClass.X);
WriteLn ('Y : ',MyClass.Y);
WriteLn ('Z : ',MyClass.Z);
MyClass.X := 0;
MyClass.Y := 0;
But the following would generate an error:
MyClass.Z := 0;
because Z is a read-only property. What happens in the above statements is that when a value needs to be read, the compiler inserts a call to the various getNNN methods of the object, and the result of this call is used. When an assignment is made, the compiler passes the value that must be assigned as a paramater to the various setNNN methods. Because of this mechanism, properties cannot be passed as var arguments to a function or procedure, since there is no known address of the property (at least, not always). If the property definition contains an index, then the read and write specifiers must be a function and a procedure. Moreover, these functions require an additional parameter : An integer parameter. This allows to read or write several properties with the same function. For this, the properties must have the same type. The following is an example of a property with an index:
{$mode objfpc}
Type TPoint = Class(TObject)
       Private
       FX,FY : Longint;
       Function GetCoord (Index : Integer): Longint;
       Procedure SetCoord (Index : Integer; Value : longint);
       Public
       Property X : Longint index 1 read GetCoord Write SetCoord;
       Property Y : Longint index 2 read GetCoord Write SetCoord;
       Property Coords[Index : Integer] Read GetCoord;
       end;
Procedure TPoint.SetCoord (Index : Integer; Value : Longint);
begin
  Case Index of
   1 : FX := Value;
   2 : FY := Value;
  end;
end;
Function TPoint.GetCoord (INdex : Integer) : Longint;
begin
  Case Index of
   1 : Result := FX;
   2 : Result := FY;
  end;
end;
Var P : TPoint;
begin
  P := TPoint.create;
  P.X := 2;
  P.Y := 3;
  With P do
    WriteLn ('X=',X,' Y=',Y);
end.
When the compiler encounters an assignment to X, then SetCoord is called with as first parameter the index (1 in the above case) and with as a second parameter the value to be set. Conversely, when reading the value of X, the compiler calls GetCoord and passes it index 1. Indexes can only be integer values. You can also have array properties. These are properties that accept an index, just as an array does. Only now the index doesn't have to be an ordinal type, but can be any type.

A read specifier for an array property is the name method function that has the same return type as the property type. The function must accept as a sole arguent a variable of the same type as the index type. For an array property, you cannot specify fields as read specifiers.

A write specifier for an array property is the name of a method procedure that accepts two arguments: The first argument has the same type as the index, and the second argument is a parameter of the same type as the property type. As an example, see the following declaration:

Type TIntList = Class
      Private
      Function GetInt (I : Longint) : longint;
      Function GetAsString (A : String) : String;
      Procedure SetInt (I : Longint; Value : Longint;);
      Procedure SetAsString (A : String; Value : String);
      Public
      Property Items [i : Longint] : Longint Read GetInt
                                             Write SetInt;
      Property StrItems [S : String] : String Read GetAsString
                                              Write SetAsstring;
      end;
Var AIntList : TIntList;
Then the following statements would be valid:
AIntList.Items[26] := 1;
AIntList.StrItems['twenty-five'] := 'zero';
WriteLn ('Item 26 : ',AIntList.Items[26]);
WriteLn ('Item 25 : ',AIntList.StrItems['twenty-five']);
While the following statements would generate errors:
AIntList.Items['twenty-five'] := 1;
AIntList.StrItems[26] := 'zero';
Because the index types are wrong. Array properties can be declared as default properties. This means that it is not necessary to specify the property name when assigning or reading it. If, in the previous example, the definition of the items property would have been
 Property Items[i : Longint]: Longint Read GetInt
                                      Write SetInt; Default;
Then the assignment
AIntList.Items[26] := 1;
Would be equivalent to the following abbreviation.
AIntList[26] := 1;
You can have only one default property per class, and descendent classes cannot redeclare the default property.



root
2000-12-20