Subsections

- 1 Class definitions
- 2 Class instantiation
- 3 Methods
  - 1 invocation
  - 2 Virtual methods
  - 3 Message methods
- 4 Properties

#
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 a 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 when the `-S2` or `-Sd` options are specified.

# 1 Class definitions

The prototype declaration of a class is as follows :

> Class types

As many `private`, `protected`, `published` and `public` blocks as needed can be repeated. Methods are normal function or procedure declarations. As can be seen, 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.

# 2 Class instantiation

Classes must be created using their constructor. Remember that a class is a pointer to an object, so when a variable of some class is declared, 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, one would do the following :

      ClassVar := ClassType.ConstructorName;

The extended syntax of `new` and `dispose` can be used 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:

- The `{$PackRecords }` directive also affects classes. i.e. the alignment in memory of the different fields depends on the value of the `{$PackRecords }` directive.
- Just as for objects and records, a packed class can be declared. This has the same effect as on an object, or record, namely that the elements are aligned on 1-byte boundaries. i.e. as close as possible.
- `SizeOf(class)` will return 4, since a class is but a pointer to an object. To get the size of the class instance data, use the `TObject.InstanceSize` method.

# 3 Methods

## 1 invocation

Method invocation for classes is no different than for objects. The following is a valid method invocation:

    Var  AnObject : TAnObject;
    begin
      AnObject := TAnObject.Create;
      ANobject.AMethod;

## 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: virtual methods must be overridden 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.

## 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 this should not be done. Instead, the `TObject.Dispatch` method should be used.

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 that might be needed. `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 message 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 that might be needed. `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 the method is defined in.

# 4 Properties

Classes can contain properties as part of their fields list. A property acts like a normal field, i.e. its value can be retrieved or set, but it 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

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. Array propertie also exist. 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, one 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;

Only one default property per class is allowed, and descendent classes cannot redeclare the default property.