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.
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:
Class types
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:
Var AnObject : TAnObject; begin AnObject := TAnObject.Create; ANobject.AMethod;
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.MYPROCThe 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.
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.
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:
Properties
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.