Modern Pascal Introduction
Modern Pascal Introduction
Programmers
Michalis Kamburelis
Table of Contents
1. Why this book ....................................................................................................... 3
2. Basics .................................................................................................................... 4
2.1. "Hello world" program ................................................................................. 4
2.2. Compilers and FPC "syntax modes" .......................................................... 5
2.3. Functions, procedures, primitive types ....................................................... 6
2.4. Testing (if) ................................................................................................... 8
2.5. Logical, relational and bit-wise operators .................................................. 10
2.6. Testing single expression for multiple values (case) ................................. 11
2.7. Enumerated and ordinal types and sets and constant-length arrays ......... 12
2.8. Loops (for, while, repeat, for .. in) ............................................................ 13
2.9. Output, logging ......................................................................................... 16
2.10. Converting to a string ............................................................................. 17
3. Units .................................................................................................................... 18
3.1. Overview ................................................................................................... 18
3.2. Extensions used for units and programs .................................................. 20
3.3. Initialization and finalization ...................................................................... 20
3.4. Units using each other .............................................................................. 21
3.5. Qualifying identifiers with unit name ......................................................... 22
3.6. Exposing one unit identifiers from another ............................................... 25
4. Classes ............................................................................................................... 26
4.1. Basics ....................................................................................................... 26
4.2. Inheritance, virtual methods, override, reintroduce ................................... 27
4.3. Classes and class instances, constructors, destructors ............................ 29
4.4. Testing class (is), typecasting (as, TMyClass(X)) ..................................... 31
4.5. Properties .................................................................................................. 33
4.6. Exceptions - Quick Example ..................................................................... 36
4.7. Visibility specifiers ..................................................................................... 37
4.8. Default ancestor ........................................................................................ 37
4.9. Self ............................................................................................................ 37
4.10. Calling inherited method ......................................................................... 38
1
Modern Object Pascal Introduction for Programmers
2
Modern Object Pascal Introduction for Programmers
I also wanted this book to be practical and concise to fellow developers. As such, I
assume you already have some programming experience, and we can talk about things
like "how to declare a variable" and avoid a lengthy explanation "what even is a variable
and what is its purpose". When covering the basics, I will give a brief description, and
then move on, like this: a variable is a container for some value; the container has a
name; the value it holds may change over time.
I emphasize the word modern in modern Object Pascal. That’s because Pascal has
evolved a lot, and it’s quite different from e.g. Turbo Pascal that many people learned
in schools long time ago. Feature-wise, modern Pascal is quite similar to C++ or Java
or C#.
• It has all the modern features you expect — classes, units, interfaces, generics…
• It’s compiled to a fast, native code,
• It’s very type safe,
• High-level but can also be low-level if you need it to be.
1
For more reasoning about why use Pascal, see here .
We also have an active ecosystem of tools and libraries. To name just a few:
1
[Link]
3
Modern Object Pascal Introduction for Programmers
• Pascal has an excellent, portable and open-source compiler called the Free Pascal
Compiler, [Link] .
• There’s also a proprietary and commercial compiler and IDE Delphi https://
[Link]/products/Delphi .
• There’s a lot of libraries (for both FPC and Delphi) available, see [Link]
Fr0sT-Brutal/awesome-pascal .
2. Basics
program MyProgram;
begin
WriteLn('Hello world!');
end.
• If you use the command-line FPC, just create a new file [Link] and
execute fpc [Link] .
• If you use Lazarus, create a new project (menu "Project # New Project # Simple
Program"). Paste this as the program source code. Compile using the menu item
"Run # Compile" (or use shortcut Ctrl + F9).
4
Modern Object Pascal Introduction for Programmers
• If you use Delphi, also create a new project (menu "File # New # Console Application
- Delphi"). Paste this as the program source code. Compile using the menu item
"Project # Compile" (or use shortcut Ctrl + F9).
This is a command-line program, so just run the compiled executable from the
command-line.
You can also run it from Lazarus or Delphi IDE using the "Run" menu
item (shortcut F9 in both IDEs). In this case, note that the console
will appear and disappear quickly. The simplest way to avoid it is to
add Readln (wait for Enter) at the end of the application.
The rest of this article talks about the Object Pascal language, so don’t expect to see
anything more fancy than the command-line stuff. If you want to see something cool,
just create a new GUI project in Lazarus ("Project # New Project # Application") or
Delphi ("File # New # Multi-Device Application"). Voila — a working GUI application,
cross-platform, with native look everywhere, using a comfortable visual component
library.
The Pascal compilers come with lots of standard units for networking, GUI, database,
file formats (XML, json, images…), threading and everything else you may need. I
already mentioned my cool Castle Game Engine earlier:)
This book, all the text and Pascal examples, has been written to support two modern
Pascal compilers:
1. Free Pascal Compiler (FPC), open-source Pascal compiler, used also by the
Lazarus IDE.
To complicate matters a bit, FPC compiler has multiple "syntax modes". In this book,
we decided to show the ObjFpc syntax mode, which is recommended by the FPC
developers and is the default for new Pascal projects created using Lazarus or Castle
Game Engine. It’s a bit different from the Delphi syntax mode, which is most compatible
5
Modern Object Pascal Introduction for Programmers
But you don’t want to read about these differences now, if you’re just starting to learn
Pascal!
The differences are minor, both between compilers and between FPC ObjFpc mode
and Delphi mode. Just be aware you may see some {$ifdef FPC} … {$endif}
clauses in the examples, that make the code valid for both FPC ObjFpc mode and
Delphi. Using {$ifdef FPC_OBJFPC} … {$endif} in some of these cases would
be more precise, but look even more complicated. If your project targets only one of
these compilers, you can simplify your code, just pick the variant for your compiler and
remove the {$ifdef …} , {$endif} stuff.
program MyProgram;
var
X: Single;
begin
WriteLn(MyFunction('Note: '));
MyProcedure(5);
// Division using "/" always makes float result, use "div" for integer
division
X := 15 / 5;
2
[Link]
ObjFpc-mode-and-Delphi-(and-FPC-Delphi-mode)
6
Modern Object Pascal Introduction for Programmers
To return a value from a function, assign something to the magic Result variable.
You can read and set the Result freely, just like a local variable.
You can also treat the function name (like MyFunction in example above) as the
variable, to which you can assign. But I would discourage it in new code, as it looks
"fishy" when used on the right side of the assignment expression. Just use Result
always when you want to read or set the function result.
If you want to call the function itself recursively, you can of course do it. If you’re calling
a parameter-less function recursively, be sure to specify the parenthesis () (even
though in Pascal you can usually omit the parentheses for a parameter-less function),
this makes a recursive call to a parameter-less function different from accessing this
function’s current result. Like this:
You can call Exit to end the execution of the procedure or function before it reaches
the final end; . If you call parameter-less Exit in a function, it will return the last
thing you set as Result . You can also use Exit(X) construct, to set the function
result and exit now — this is just like return X construct in C-like languages.
7
Modern Object Pascal Introduction for Programmers
begin
if ExistingNames = '' then
Exit(NewName);
Result := ExistingNames + ', ' + NewName;
end;
Note that the function result can be discarded. Any function may be used just like a
procedure. This makes sense if the function has some side effect (e.g. it modifies a
global variable) besides calculating the result. For example:
var
Count: Integer;
MyCount: Integer;
begin
Count := 10;
CountMe; // the function result is discarded, but the function is
executed, Count is now 11
MyCount := CountMe; // use the result of the function, MyCount equals to
Count which is now 12
end.
Use if .. then or if .. then .. else to run some code when some condition is
satisfied. Unlike in the C-like languages, in Pascal you don’t have to wrap the condition
in parenthesis.
var
A: Integer;
B: boolean;
begin
if A > 0 then
DoSomething;
if A > 0 then
8
Modern Object Pascal Introduction for Programmers
begin
DoSomething;
AndDoSomethingMore;
end;
if A > 10 then
DoSomething
else
DoSomethingElse;
// equivalent to above
B := A > 10;
if B then
DoSomething
else
DoSomethingElse;
end;
The else is paired with the last if . So this works as you expect:
if A <> 0 then
if B <> 0 then
AIsNonzeroAndBToo
else
AIsNonzeroButBIsZero;
While the example with nested if above is correct, it is often better to place the
nested if inside a begin … end block in such cases. This makes the code more
obvious to the reader, and it will remain obvious even if you mess up the indentation.
The improved version of the example is below. When you add or remove some else
clause in the code below, it’s obvious to which condition it will apply (to the A test or
the B test), so it’s less error-prone.
if A <> 0 then
begin
if B <> 0 then
AIsNonzeroAndBToo
else
AIsNonzeroButBIsZero;
end;
9
Modern Object Pascal Introduction for Programmers
The logical operators are called and , or , not , xor . Their meaning is probably
obvious (search for "exclusive or" if you’re unsure what xor does:)). They take boolean
arguments, and return a boolean. They can also act as bit-wise operators when both
arguments are integer values, in which case they return an integer.
The relational (comparison) operators are = , <> , > , < , <= , >= . If you’re
accustomed to C-like languages, note that in Pascal you compare two values (check
are they equal) using a single equality character A = B (unlike in C where you use
A == B ). The special assignment operator in Pascal is := .
The logical (or bit-wise) operators have a higher precedence than relational operators.
You may need to use parenthesis around some expressions to have the desired order
of the calculations.
var
A, B: Integer;
begin
if A = 0 and B <> 0 then ... // INCORRECT example
The above fails to compile, because the compiler first wants to perform a bit-wise and
in the middle of the expression: (0 and B) . This is a bit-wise operation which returns
an integer value. Then the compiler applies = operator which yields a boolean value
A = (0 and B) . And finally the "type mismatch" error is risen after trying to compare
the boolean value A = (0 and B) and integer value 0 .
This is correct:
var
A, B: Integer;
begin
if (A = 0) and (B <> 0) then ...
10
Modern Object Pascal Introduction for Programmers
This will work OK, even when A is nil . The keyword nil is a pointer equal
to zero (when represented as a number). It is called a null pointer in many other
programming languages.
case SomeValue of
0: DoSomething;
1: DoSomethingElse;
2: begin
IfItsTwoThenDoThis;
AndAlsoDoThis;
end;
3..10: DoSomethingInCaseItsInThisRange;
11, 21, 31: AndDoSomethingForTheseSpecialValues;
else DoSomethingInCaseOfUnexpectedValue;
end;
In you come from C-like languages, and compare this with switch statement in these
languages, you will notice that there is no automatic fall-through. This is a deliberate
blessing in Pascal. You don’t have to remember to place break instructions. In every
execution, at most one branch of the case is executed, that’s it.
11
Modern Object Pascal Introduction for Programmers
Enumerated type in Pascal is a very nice, opaque type. You will probably use it much
more often than enums in other languages:)
type
TAnimalKind = (akDuck, akCat, akDog);
The convention is to prefix the enum names with a two-letter shortcut of type name,
hence ak = shortcut for "Animal Kind". This is a useful convention, since the enum
names are in the unit (global) namespace. So by prefixing them with ak prefix, you
minimize the chances of collisions with other identifiers.
The fact that enumerated type is opaque means that it cannot be just assigned to
and from an integer. However, for special use, you can use Ord(MyAnimalKind) to
forcefully convert enum to int, or typecast TAnimalKind(MyInteger) to forcefully
convert int to enum. In the latter case, make sure to check first whether MyInteger
is in good range (0 to Ord(High(TAnimalKind)) ).
type
TArrayOfTenStrings = array [0..9] of string;
TArrayOfTenStrings1Based = array [1..10] of string;
TMyNumber = 0..9;
TAlsoArrayOfTenStrings = array [TMyNumber] of string;
12
Modern Object Pascal Introduction for Programmers
type
TAnimalKind = (akDuck, akCat, akDog);
TAnimals = set of TAnimalKind;
var
A: TAnimals;
begin
A := [];
A := [akDuck, akCat];
A := A + [akDog];
A := A * [akCat, akDog];
Include(A, akDuck);
Exclude(A, akDuck);
end;
var
MyArray: array [0..9] of Integer;
I: Integer;
begin
// initialize
for I := 0 to 9 do
MyArray[I] := I * I;
// show
for I := 0 to 9 do
WriteLn('Square is ', MyArray[I]);
13
Modern Object Pascal Introduction for Programmers
while I < 10 do
begin
WriteLn('Square is ', MyArray[I]);
I := I + 1; // or "I += 1", or "Inc(I)"
end;
1. The loop condition has an opposite meaning. In while .. do you tell it when to
continue, but in repeat .. until you tell it when to stop.
2. In case of repeat , the condition is not checked at the beginning. So the repeat
loop always runs at least once.
Note that the value of the loop counter variable ( I in this example) should be
considered undefined after the loop has finished, due to possible optimizations.
Accessing the value of I after the loop may cause a compiler warning. Unless you
14
Modern Object Pascal Introduction for Programmers
exit the loop prematurely by Break or Exit : in such case, the counter variable is
guaranteed to retain the last value.
var
AK: TAnimalKind;
begin
for AK in TAnimalKind do...
var
Animals: TAnimals;
AK: TAnimalKind;
begin
Animals := [akDog, akCat];
for AK in Animals do ...
uses
SysUtils, [Link];
type
TMyClass = class
I, Square: Integer;
end;
TMyClassList = {$ifdef FPC}specialize{$endif} TObjectList<TMyClass>;
var
List: TMyClassList;
15
Modern Object Pascal Introduction for Programmers
C: TMyClass;
I: Integer;
begin
List := [Link](true); // true = owns children
try
for I := 0 to 9 do
begin
C := [Link];
C.I := I;
[Link] := I * I;
[Link](C);
end;
for C in List do
WriteLn('Square of ', C.I, ' is ', [Link]);
finally
FreeAndNil(List);
end;
end.
We didn’t yet explain the concept of classes, so the last example may not be obvious
to you yet — just carry on, it will make sense later:)
This is a "magic" routine in Pascal. It takes a variable number of arguments and they
can have any type. They are all converted to strings when displaying, with a special
syntax to specify padding and number precision.
WriteLn('Hello world!');
WriteLn('You can output an integer: ', 3 * 4);
WriteLn('You can pad an integer: ', 666:10);
WriteLn('You can output a float: ', Pi:1:4);
To explicitly use newline in the string, use the LineEnding constant (from FPC RTL).
(The Castle Game Engine defines also a shorter NL constant.) Pascal strings do not
interpret any special backslash sequences, so writing
16
Modern Object Pascal Introduction for Programmers
doesn’t work like some of you would think. This will work:
or just this:
WriteLn('One line.');
WriteLn('Second line.');
Note that this will only work in console applications. Make sure you have {$apptype
CONSOLE} (and not {$apptype GUI} ) defined in your main program file. On
some operating systems it actually doesn’t matter and will work always (Unix), but on
some operating systems trying to write something from a GUI application is an error
(Windows).
• You can convert particular types to strings using specialized functions like
IntToStr and FloatToStr . Furthermore, you can concatenate strings in
Pascal simply by adding them. So you can create a string like this: 'My int
number is ' + IntToStr(MyInt) + ', and the value of Pi is '
+ FloatToStr(Pi) .
17
Modern Object Pascal Introduction for Programmers
# Advantage: The separation of pattern string from arguments looks clean. If you
need to change the pattern string without touching the arguments (e.g. when
translating), you can do it easily.
# Another advantage: No compiler magic. You can use the same syntax to pass
any number of arguments of an arbitrary type in your own routines (declare
parameter as an array of const ). You can then pass these arguments
downward to Format , or deconstruct the list of parameters and do anything
you like with them.
# Disadvantage: Compiler does not check whether the pattern matches the
arguments. Using a wrong placeholder type will result in an exception at
runtime ( EConvertError exception, not anything nasty like Access Violation
(Segmentation Fault) error).
• WriteStr(TargetString, …) routine behaves much like Write(…) , except
that the result is saved to the TargetString .
# Advantage: It supports all the features of Write , including the special syntax
for formatting like Pi:1:4 .
# Disadvantage: The special syntax for formatting is a "compiler magic",
implemented specifically for routines like this. This is sometimes troublesome,
e.g. you cannot create your own routine MyStringFormatter(…) that would
also allow the special syntax like Pi:1:4 . For this reason (and also because it
wasn’t implemented for a long time in major Pascal compilers), this construction
is not very popular.
3. Units
3.1. Overview
Units allow you to group common stuff (anything that can be declared), for usage
by other units and programs. They are equivalent to modules and packages in other
18
Modern Object Pascal Introduction for Programmers
languages. They have an interface section, where you declare what is available for
other units and programs, and then the implementation.
unit MyUnit;
interface
implementation
end.
program MyProgram;
uses
MyUnit;
begin
WriteLn(MyFunction('Note: '));
MyProcedure(5);
end.
19
Modern Object Pascal Introduction for Programmers
E.g. FPC allows other file extensions for units. And some people use
.pp for unit files, like [Link] .
• .dpr extension (short for "Delphi Project"), if you want the project to be compatible
with both FPC/Lazarus and Delphi,
• .lpr extension (short for "Lazarus Project"), if you want to use only FPC/Lazarus.
20
Modern Object Pascal Introduction for Programmers
unit initialization_finalization;
interface
implementation
initialization
WriteLn('Hello world!');
finalization
WriteLn('Goodbye world!');
end.
unit AnotherUnit;
interface
uses
Classes;
implementation
uses SysUtils;
21
Modern Object Pascal Introduction for Programmers
end.
It is not allowed to have circular unit dependencies in the interface. That is, two
units cannot use each other in the interface section. The reason is that in order to
"understand" the interface section of a unit, the compiler must first "understand" all the
units it uses in the interface section. Pascal language follows this rule strictly, and it
allows a fast compilation and fully automatic detection on the compiler side what units
need to be recompiled. There is no need to use complicated Makefile files for a
simple task of compilation in Pascal, and there is no need to recompile everything just
to make sure that all dependencies are updated correctly.
It is OK to make a circular dependency between units when at least one "usage" is only
in the implementation. So it’s OK for unit A to use unit B in the interface, and then
unit B to use unit A in the implementation.
You can always explicitly define a unit of a given identifier, by using it like
[Link] . This is the usual solution when the identifier you want to
use from MyUnit is hidden by another unit. Of course you can also rearrange the
order of units on your uses clause, although this can affect other declarations than the
one you’re trying to fix.
program showcolor;
var
{ This doesn't work like we want, as TColor ends up
22
Modern Object Pascal Introduction for Programmers
In case of units, remember that they have two uses clauses: one in the interface, and
another one in the implementation. The rule later units hide the stuff from earlier units
is applied here consistently, which means that units used in the implementation section
can hide identifiers from units used in the interface section. However, remember that
when reading the interface section, only the units used in the interface matter.
This may create a confusing situation, where two seemingly-equal declarations are
considered different by the compiler:
unit UnitUsingColors;
// INCORRECT example
interface
uses Graphics;
implementation
uses GoogleMapsEngine;
end.
The unit Graphics (from Lazarus LCL) defines the TColor type. But the
compiler will fail to compile the above unit, claiming that you don’t implement a
procedure ShowColor that matches the interface declaration. The problem is that
23
Modern Object Pascal Introduction for Programmers
unit GoogleMapsEngine also defines a TColor type. And it is used only in the
implementation section, therefore it shadows the TColor definition only in the
implementation. The equivalent version of the above unit, where the error is obvious,
looks like this:
unit UnitUsingColors;
// INCORRECT example.
// This is what the compiler "sees" when trying to compile previous
example
interface
uses Graphics;
implementation
uses GoogleMapsEngine;
end.
The solution is trivial in this case, just change the implementation to explicitly
use TColor from Graphics unit. You could fix it also by moving the
GoogleMapsEngine usage, to the interface section and earlier than Graphics ,
although this could result in other consequences in real-world cases, when
UnitUsingColors would define more things.
unit UnitUsingColors;
interface
uses Graphics;
24
Modern Object Pascal Introduction for Programmers
implementation
uses GoogleMapsEngine;
end.
unit MyUnit;
interface
uses Graphics;
type
{ Expose TColor from Graphics unit as TMyColor. }
TMyColor = TColor;
const
{ This works with constants too. }
25
Modern Object Pascal Introduction for Programmers
clYellow = [Link];
clBlue = [Link];
implementation
end.
Note that this trick cannot be done as easily with global procedures, functions and
variables. With procedures and functions, you could expose a constant pointer to a
procedure in another unit (see Section 8.2, “Callbacks (aka events, aka pointers to
functions, aka procedural variables)”), but that looks quite dirty.
The usual solution is to create trivial "wrapper" functions that simply call the functions
from the internal unit, passing the parameters and return values as needed.
To make this work with global variables, one can use global (unit-level) properties, see
Section 4.5, “Properties”.
4. Classes
4.1. Basics
We have classes. At the basic level, a class is just a container for
• methods (which is fancy name for "a procedure or function inside a class"),
• and properties (which is a fancy syntax for something that looks like a field, but is in
fact a pair of methods to get and set something; more in Section 4.5, “Properties”).
• Actually, there are more possibilities, described in Section 9.2, “More stuff inside
classes and nested classes”.
type
TMyClass = class
MyInt: Integer; // this is a field
property MyIntProperty: Integer read MyInt write MyInt; // this is a
property
procedure MyMethod; // this is a method
end;
procedure [Link];
26
Modern Object Pascal Introduction for Programmers
begin
WriteLn(MyInt + 10);
end;
program MyProgram;
uses
SysUtils;
type
TMyClass = class
MyInt: Integer;
procedure MyVirtualMethod; virtual;
end;
TMyClassDescendant = class(TMyClass)
procedure MyVirtualMethod; override;
end;
procedure [Link];
begin
WriteLn('TMyClass shows MyInt + 10: ', MyInt + 10);
end;
procedure [Link];
begin
WriteLn('TMyClassDescendant shows MyInt + 20: ', MyInt + 20);
end;
var
C: TMyClass;
begin
C := [Link];
27
Modern Object Pascal Introduction for Programmers
try
[Link];
finally
FreeAndNil(C);
end;
C := [Link];
try
[Link];
finally
FreeAndNil(C);
end;
end.
When a method is virtual it means that the compiler searches for the method
implementation at runtime, based on the actual class of the instance. What does this
mean in practice?
• Run the above example unmodified. Note that the method MyVirtualMethod
is virtual. The call [Link] selects the appropriate implementation
based on the actual class of the instance C . When C is of class
TMyClassDescendant , the [Link]
implementation is called. Thus the output should be:
• Now modify the above example removing the virtual; and override;
pieces. Both calls [Link] will now call the implementation from
TMyClass , because C is declared as TMyClass , so at compile-time all the
compiler knows is that C is a TMyClass . The output will be:
In short, this is usually not what you want. You want virtual methods.
By default methods are not virtual, declare them with virtual to make them so.
Overrides must be marked with override , otherwise you will get a warning. To hide
a method (declared in ancestor as virtual ) without overriding it (usually you don’t
want to do this, unless you know what you’re doing) use reintroduce .
28
Modern Object Pascal Introduction for Programmers
• Before invoking a constructor, a memory for the class instance is allocated, and
then the constructor code is called.
• You don’t need to define a constructor in all your classes. All classes implicitly
descend from the TObject which has a parameter-less constructor called
Create . So you always have a constructor, even if you didn’t define one.
• But you can define a constructor in your class. It’s the best way to initialize a class
instance. If you want to later depend that e.g. "initial value of field X is Y", then make
it so ( X := Y; ) in the constructor.
• Your own constructors are usually also called just Create . More details about
naming constructors and destructors are in Section 5.4, “The virtual destructor called
Destroy”.
X := [Link];
type
TMyClass = class
public
X: Integer;
constructor Create;
29
Modern Object Pascal Introduction for Programmers
end;
constructor [Link];
begin
inherited Create; // Call the ancestor constructor
// Initialization code here
X := 123;
end;
• After invoking the destructor, a memory for the class instance is released. Accessing
the fields of the destroyed instance is no longer allowed.
• Again, you don’t need to define a destructor in all your classes. All classes
implicitly descend from the TObject which has a parameter-less destructor called
Destroy .
• But you can define a destructor in your class. This is your last chance to do any
"cleanup". E.g. maybe your class instance created some other class instances,
internal, and now they need to be freed.
• If you define one, there should be only one destructor, called Destroy , always
with override; . More details why it should be so are in Section 5.4, “The virtual
destructor called Destroy”.
Here’s an example:
uses
SysUtils;
type
TMyClass = class
private
InternalStuff: TObject;
public
constructor Create;
destructor Destroy; override;
end;
30
Modern Object Pascal Introduction for Programmers
constructor [Link];
begin
inherited Create; // Call the ancestor constructor at the beginning
InternalStuff := [Link];
Writeln('[Link]');
end;
destructor [Link];
begin
Writeln('[Link]');
FreeAndNil(InternalStuff); // will call [Link]
inherited Destroy; // Call the ancestor destructor at the end
end;
var
C: TMyClass;
begin
C := [Link];
try
// use C
finally
FreeAndNil(C); // will call [Link]
end;
end.
To test the class of an instance at runtime, use the is operator. To typecast the
instance to a specific class, use the as operator.
program is_as;
uses
SysUtils;
type
TMyClass = class
procedure MyMethod;
end;
TMyClassDescendant = class(TMyClass)
31
Modern Object Pascal Introduction for Programmers
procedure MyMethodInDescendant;
end;
procedure [Link];
begin
WriteLn('MyMethod');
end;
procedure [Link];
begin
WriteLn('MyMethodInDescendant');
end;
var
Descendant: TMyClassDescendant;
C: TMyClass;
begin
Descendant := [Link];
try
[Link];
[Link];
finally
FreeAndNil(Descendant);
end;
end.
Instead of casting using X as TMyClass , you can also use the unchecked typecast
TMyClass(X) . This is faster, but results in an undefined behavior if the X is not,
in fact, a TMyClass descendant. So don’t use the TMyClass(X) typecast, or use
it only in a code where it’s blindingly obvious that it’s correct, for example right after
testing with is :
if A is TMyClass then
32
Modern Object Pascal Introduction for Programmers
(A as TMyClass).CallSomeMethodOfMyClass;
// below is marginally faster
if A is TMyClass then
TMyClass(A).CallSomeMethodOfMyClass;
4.5. Properties
Properties are a very nice "syntactic sugar" to
1. Make something that looks like a field (can be read and set) but underneath is
realized by calling a getter and setter methods. The typical usage is to perform
some side-effect (e.g. redraw the screen) each time some value changes.
2. Make something that looks like a field, but is read-only. In effect, it’s like a constant
or a parameter-less function.
type
TWebPage = class
private
FURL: string;
FColor: TColor;
function SetColor(const Value: TColor);
public
{ No way to set it directly.
Call the Load method, like Load('[Link]
to load a page and set this property. }
property URL: string read FURL;
procedure Load(const AnURL: string);
property Color: TColor read FColor write SetColor;
end;
33
Modern Object Pascal Introduction for Programmers
Note that instead of specifying a method, you can also specify a field (typically a private
field) to directly get or set. In the example above, the Color property uses a setter
method SetColor . But for getting the value, the Color property refers directly to
the private field FColor . Directly referring to a field is faster than implementing trivial
getter or setter methods (faster for you, and faster at execution).
1. Whether it can be read, and how (by directly reading a field, or by using a "getter"
method).
2. And, in a similar manner, whether it can be set, and how (by directly writing to a
designated field, or by calling a "setter" method).
The compiler checks that the types and parameters of indicated fields and methods
match with the property type. For example, to read an Integer property you have
to either provide an Integer field, or a parameter-less method that returns an
Integer .
Technically, for the compiler, the "getter" and "setter" methods are just normal methods
and they can do absolutely anything (including side-effects or randomization). But it’s
a good convention to design properties to behave more-or-less like fields:
• The getter function should have no visible side-effects (e.g. it should not read some
input from file / keyboard). It should be deterministic (no randomization, not even
pseudo-randomization :). Reading a property many times should be valid, and return
the same value, if nothing changed in-between.
Note that it’s OK for getter to have some invisible side-effect, for example to cache
a value of some calculation (known to produce the same results for given instance),
to return it faster next time. This is in fact one of the cool possibilities of a "getter"
function.
• The setter function should always set the requested value, such that calling the
getter yields it back. Do not reject invalid values silently in the "setter" (raise an
34
Modern Object Pascal Introduction for Programmers
exception if you must). Do not convert or scale the requested value. The idea is
that after [Link] := 123; the programmer can expect that
[Link] = 123 .
• The read-only properties are often used to make some field read-only from the
outside. Again, the good convention is to make it behave like a constant, at least
constant for this object instance with this state. The value of the property should not
change unexpectedly. Make it a function, not a property, if using it has a side effect
or returns something random.
• The "backing" field of a property is almost always private, since the idea of a property
is to encapsulate all outside access to it.
• It’s technically possible to make set-only properties, but I have not yet seen a good
example of such thing:)
Serialization of properties
Serialization is what happens when Lazarus reads (or writes) the component state from
an [Link] file. (In Delphi, the equivalent file has .dfm extension.) You can also
use this mechanism explicitly, using routines like ReadComponentFromTextStream
from the LResources unit. You can also use other serialization algorithms, e.g.
FpJsonRtti unit (serializing to JSON).
At each property, you can declare some additional things that will be helpful for any
serialization algorithm:
• You can specify the property default value (using the default keyword). Note
that you are still required to initialize the property in the constructor to this exact
default value (it is not done automatically). The default declaration is merely an
35
Modern Object Pascal Introduction for Programmers
information to the serialization algorithm: "when the constructor finishes, the given
property has the given value".
• Whether the property should be stored at all (using the stored keyword).
program MyProgram;
uses
SysUtils;
type
TMyClass = class
procedure MyMethod;
end;
procedure [Link];
begin
if Random > 0.5 then
raise [Link]('Raising an exception!');
end;
var
C: TMyClass;
begin
Randomize;
C := [Link];
try
[Link];
finally
FreeAndNil(C);
end;
end.
Note that the finally clause is executed even if you exit the block using the Exit
(from function / procedure / method) or Break or Continue (from loop body).
See the Section 6, “Exceptions” chapter for more in-depth description of exceptions.
36
Modern Object Pascal Introduction for Programmers
public
everyone can access it, including the code in other units.
private
only accessible in this class.
protected
only accessible in this class and descendants.
The explanation of private and protected visibility above is not precisely true.
The code in the same unit can overcome their limits, and access the private and
protected stuff freely. Sometimes this is a nice feature, allows you to implement
tightly-connected classes. Use strict private or strict protected to secure
your classes more tightly. See the Section 9.1, “Private and strict private”.
By default, if you don’t specify the visibility, then the visibility of declared stuff is
public . The exception is for classes compiled with {$M+} , or descendants of
classes compiled with {$M+} , which includes all descendants of TPersistent ,
which also includes all descendants of TComponent (since TComponent descends
from TPersistent ). For them, the default visibility specifier is published , which
is like public , but in addition the streaming system knows to handle this.
Not every field and property type is allowed in the published section (not every
type can be streamed, and only classes can be streamed from simple fields). Just use
public if you don’t care about streaming but want something available to all users.
4.9. Self
The special keyword Self can be used within the class implementation to explicitly
refer to your own instance. It is equivalent to this from C++, Java and similar
languages.
37
Modern Object Pascal Introduction for Programmers
uses SysUtils;
type
TMyClass1 = class
procedure MyMethod;
end;
TMyClass2 = class(TMyClass1)
procedure MyMethod;
procedure MyOtherMethod;
end;
procedure [Link];
begin
Writeln('[Link]');
end;
procedure [Link];
begin
Writeln('[Link]');
end;
procedure [Link];
begin
MyMethod; // this calls [Link]
end;
var
C: TMyClass2;
begin
C := [Link];
try
[Link];
finally FreeAndNil(C) end;
end.
38
Modern Object Pascal Introduction for Programmers
If the method is not defined in a given class, then it calls the method of an ancestor
class. In effect, when you call MyMethod on an instance of TMyClass2 , then
Sometimes, you don’t want to call the method of your own class. You want to call the
method of an ancestor (or ancestor’s ancestor, and so on). To do this, add the keyword
inherited before the call to MyMethod , like this:
inherited MyMethod;
This way you force the compiler to start searching from an ancestor class.
In our example, it means that compiler is searching for MyMethod inside
[Link] , then [Link] , and then gives up. It does not
even consider using the implementation of [Link] .
The inherited call is often used to call the ancestor method of the same name. This
way the descendants can enhance the ancestors (keeping the ancestor functionality,
instead of replacing the ancestor functionality). Like in the example below.
uses SysUtils;
type
TMyClass1 = class
constructor Create;
procedure MyMethod(const A: Integer);
39
Modern Object Pascal Introduction for Programmers
end;
TMyClass2 = class(TMyClass1)
constructor Create;
procedure MyMethod(const A: Integer);
end;
constructor [Link];
begin
inherited Create; // this calls [Link]
Writeln('[Link]');
end;
constructor [Link];
begin
inherited Create; // this calls [Link]
Writeln('[Link]');
end;
var
C: TMyClass2;
begin
C := [Link];
try
[Link](123);
finally FreeAndNil(C) end;
end.
Since using inherited to call a method with the same name, with the same
arguments, is a very common case, there is a special shortcut for it: you can just write
inherited; ( inherited keyword followed immediately by a semicolon, instead
of a method name). This means "call an inherited method with the same name, passing
it the same arguments as the current method".
40
Modern Object Pascal Introduction for Programmers
Note 1: The inherited; is really just a shortcut for calling the ancestor’s method
with the same variables passed in. If you have modified your own parameter (which
is possible, if the parameter is not const ), then the ancestor’s method can receive
different input values from your descendant. Consider this:
Note 2: You usually want to make the MyMethod virtual when many classes (along
the "inheritance chain") define it. More about the virtual methods in the section below.
But the inherited keyword works regardless of whether the method is virtual or not.
The inherited always means that the compiler starts searching for the method in
an ancestor, and it makes sense for both virtual and non-virtual methods.
By default, the methods are not virtual. This is similar to C++, and unlike Java.
When a method is not virtual, the compiler determines which method to call based on
the currently declared class type, not based on the actually created class type. The
difference seems subtle, but it’s important when your variable is declared to have a
class like TFruit , but it may be in fact a descendant class like TApple .
The idea of the object-oriented programming is that the descendant class is always as
good as the ancestor, so the compiler allows to use a descendant class always when
the ancestor is expected. When your method is not virtual, this can have undesired
consequences. Consider the example below:
41
Modern Object Pascal Introduction for Programmers
uses SysUtils;
type
TFruit = class
procedure Eat;
end;
TApple = class(TFruit)
procedure Eat;
end;
procedure [Link];
begin
Writeln('Eating a fruit');
end;
procedure [Link];
begin
Writeln('Eating an apple');
end;
var
Apple: TApple; // Note: you could also declare "Apple: TFruit" here
begin
Apple := [Link];
try
DoSomethingWithAFruit(Apple);
finally FreeAndNil(Apple) end;
end.
42
Modern Object Pascal Introduction for Programmers
In effect, the call [Link] called the [Link] implementation, and nothing
calls the [Link] implementation.
If you think about how the compiler works, this is natural: when you wrote the
[Link] , the Fruit variable was declared to hold a class TFruit . So the
compiler was searching for the method called Eat within the TFruit class. If the
TFruit class would not contain such method, the compiler would search within an
ancestor ( TObject in this case). But the compiler cannot search within descendants
(like TApple ,) as it doesn’t know whether the actual class of Fruit is TApple ,
TFruit , or some other TFruit descendant (like a TOrange , not shown in the
example above).
Using the virtual methods changes this behavior. If the Eat method would be virtual
(an example of it is shown below), then the actual implementation to be called is
determined at runtime. If the Fruit variable will hold an instance of the class TApple
(even if it’s declared as TFruit ), then the Eat method will be searched within the
TApple class first.
• Mark its first definition (in the top-most ancestor) with the virtual keyword.
• Mark all the other definitions (in the descendants) with the override keyword.
All the overridden versions must have exactly the same parameters (and return the
same types, in case of functions).
uses SysUtils;
type
TFruit = class
procedure Eat; virtual;
end;
TApple = class(TFruit)
procedure Eat; override;
end;
43
Modern Object Pascal Introduction for Programmers
procedure [Link];
begin
Writeln('Eating a fruit');
end;
procedure [Link];
begin
Writeln('Eating an apple');
end;
var
Apple: TApple; // Note: you could also declare "Apple: TFruit" here
begin
Apple := [Link];
try
DoSomethingWithAFruit(Apple);
finally FreeAndNil(Apple) end;
end.
Internally, virtual methods work by having so-called virtual method table associated
with each class. This table is a list of pointers to the implementations of virtual methods
for this class. When calling the Eat method, the compiler looks into a virtual method
table associated with the actual class of Fruit , and uses a pointer to the Eat
implementation stored there.
If you don’t use the override keyword, the compiler will warn you that you’re hiding
(obscuring) the virtual method of an ancestor with a non-virtual definition. If you’re sure
that this is what you want, you can add a reintroduce keyword. But in most cases,
you will rather want to keep the method virtual, and add the override keyword, thus
making sure that it’s always invoked correctly.
44
Modern Object Pascal Introduction for Programmers
5. Freeing classes
5.1. Remember to free the class instances
The class instances have to be manually freed, otherwise you get memory leaks.
Actually, that’s an oversimplification, as FreeAndNil does a useful trick and sets the
variable A to nil before calling the destructor on a suitable reference. This helps
to prevent a certain class of bugs — the idea is that the "outside" code should never
access a half-destructed instance of the class.
Often you will also see people using the [Link] method, which is like doing
45
Modern Object Pascal Introduction for Programmers
[Link];
Note that in normal circumstances, you should never call a method on an instance
which may be nil . So the call [Link] may look suspicious at the first sight, if A can
be nil . However, the Free method is an exception to this rule. It does something
dirty in the implementation — namely, checks whether Self <> nil .
This trick (officially allowing the method to be used with Self equal
nil ) is possible only in non-virtual methods.
We discourage from using this trick in your own code (for virtual
or non-virtual methods) as it is counter-intuitive to normal usage. In
general all instance methods should be able to assume that they
work on valid (non-nil) instance and can access fields and call any
other methods (virtual or not).
The Castle Game Engine does it like that. It helps to keep a nice assertion that
all references are either nil, or point to valid instances. Though note that using
FreeAndNil(A) doesn’t guarantee this assertion, it only helps with this. For
example, if you copy the instance reference, and call FreeAndNil(A) on one copy,
the other copy will be a non-nil dangling pointer.
A := [Link];
B := A;
FreeAndNil(A);
// B now contains a dangling pointer
More about dealing with this in the later section about "Free notification".
3
[Link]
method_with_self_nil.dpr
46
Modern Object Pascal Introduction for Programmers
Still, FreeAndNil(A) takes care of the most trivial cases, so it’s a good habit to use
it IMHO. You will appreciate it when debugging some errors, it is nice to easily observe
" X is already freed, because X is nil now".
uses SysUtils;
type
TGun = class
end;
TPlayer = class
Gun1, Gun2: TGun;
constructor Create;
destructor Destroy; override;
end;
constructor [Link];
begin
inherited;
Gun1 := [Link];
Gun2 := [Link];
end;
destructor [Link];
begin
FreeAndNil(Gun1);
FreeAndNil(Gun2);
inherited;
end;
To avoid the need to explicitly free the instance, one can also use the TComponent
feature of "ownership". An object that is owned will be automatically freed by the owner.
The mechanism is smart and it will never free an already freed instance (so things will
47
Modern Object Pascal Introduction for Programmers
also work correctly if you manually free the owned object earlier). We can change the
previous example to this:
type
TGun = class(TComponent)
end;
TPlayer = class(TComponent)
Gun1, Gun2: TGun;
constructor Create(AOwner: TComponent); override;
end;
Note that you can always use nil value for the owner. This way the "ownership"
mechanism will not be used for this component. It makes sense if you need to
use the TComponent descendant, but you want to always manually free it. To
do this, you would create a component descendant like this: ManualGun :=
[Link](nil); .
type
TGun = class
end;
48
Modern Object Pascal Introduction for Programmers
TPlayer = class
Guns: TGunList;
Gun1, Gun2: TGun;
constructor Create;
destructor Destroy; override;
end;
constructor [Link];
begin
inherited;
// Actually, the parameter true (OwnsObjects) is already the default
Guns := [Link](true);
Gun1 := [Link];
[Link](Gun1);
Gun2 := [Link];
[Link](Gun2);
end;
destructor [Link];
begin
{ We have to take care to free the list.
It will automatically free its contents. }
FreeAndNil(Guns);
{ No need to free the Gun1, Gun2 anymore. It's a nice habit to set to
"nil"
their references now, as we know they are freed. In this simple class,
with so simple destructor, it's obvious that they cannot be accessed
anymore -- but doing this pays off in case of larger and more
complicated
destructors.
Beware that the list classes "ownership" mechanism is simple, and you will get an error
if you free the instance using some other means, while it’s also contained within a list.
49
Modern Object Pascal Introduction for Programmers
Use the Extract method to remove something from a list without freeing it, thus
taking the responsibility to free it yourself.
In the Castle Game Engine: The descendants of TX3DNode have automatic memory
management when inserted as children of another TX3DNode . The root X3D node,
TX3DRootNode , is in turn usually owned by TCastleSceneCore . Some other
things also have a simple ownership mechanism — look for parameters and properties
called OwnsXxx .
In theory, you could have multiple destructors, but in practice it’s almost never a good
idea. It’s much easier to have only one destructor called Destroy , which is in turn
called by the Free method, which is in turn called by the FreeAndNil procedure.
It’s normal that a class has multiple constructors. Usually they are
all called Create , and only have different parameters, but it’s also
OK to invent other names for constructors.
This all gives you a bit of extra flexibility when defining constructors.
It is often not necessary to make them virtual, so by default you’re
not forced to do it.
50
Modern Object Pascal Introduction for Programmers
If you copy a reference to the instance, such that you have two references to the same
memory, and then one of them is freed — the other one becomes a "dangling pointer". It
should not be accessed, as it points to a memory that is no longer allocated. Accessing
it may result in a runtime error, or garbage being returned (as the memory may be
reused for other stuff in your program).
Using the FreeAndNil to free the instance doesn’t help here. FreeAndNil sets
to nil only the reference it got — there’s no way for it to set all other references
automatically. Consider this code:
var
Obj1, Obj2: TObject;
begin
Obj1 := [Link];
Obj2 := Obj1;
FreeAndNil(Obj1);
1. At the end of this block, the Obj1 is nil . If some code has to access it, it can
reliably use if Obj1 <> nil then … to avoid calling methods on a freed
instance, like
51
Modern Object Pascal Introduction for Programmers
Same goes for calling a virtual method, or calling a non-virtual method that
accessed a field of a nil instance.
2. With Obj2 , things are less predictable. It’s not nil , but it’s invalid. Trying
to access a field of a non-nil invalid instance results in an unpredictable
behavior — maybe an access violation exception, maybe a garbage data returned.
• One solution is to, well, be careful and read the documentation. Don’t assume
anything about the lifetime of the reference, if it’s created by other code. If a class
TCar has a field pointing to some instance of TWheel , it’s a convention that the
reference to wheel is valid as long as the reference to car exists, and the car will
free its wheels inside its destructor. But that’s just a convention, the documentation
should mention if there’s something more complicated going on.
• In the above example, right after freeing the Obj1 instance, you can simply set the
Obj2 variable explicitly to nil . That’s trivial in this simple case.
• The most future-proof solution is to use TComponent class "free notification"
mechanism. One component can be notified when another component is freed, and
thus set its reference to nil .
Thus you get something like a weak reference. It can cope with various usage
scenarios, for example you can allow the code from outside of the class to set your
reference, and the outside code can also free the instance at any time.
Here’s a complete example, showing how to use this mechanism, together with
constructor / destructor and a setter property. Sometimes it can be done simpler,
but this is the full-blown version that is always correct:)
type
TControl = class(TComponent)
end;
TContainer = class(TComponent)
private
FSomeSpecialControl: TControl;
procedure SetSomeSpecialControl(const Value: TControl);
52
Modern Object Pascal Introduction for Programmers
protected
procedure Notification(AComponent: TComponent; Operation:
TOperation); override;
public
destructor Destroy; override;
property SomeSpecialControl: TControl
read FSomeSpecialControl write SetSomeSpecialControl;
end;
implementation
destructor [Link];
begin
{ set to nil by SetSomeSpecialControl, to detach free notification }
SomeSpecialControl := nil;
inherited;
end;
53
Modern Object Pascal Introduction for Programmers
type
TControl = class(TComponent)
end;
TContainer = class(TComponent)
private
FSomeSpecialControlObserver: TFreeNotificationObserver;
FSomeSpecialControl: TControl;
procedure SetSomeSpecialControl(const Value: TControl);
procedure SomeSpecialControlFreeNotification(const Sender:
TFreeNotificationObserver);
public
constructor Create(AOwner: TComponent); override;
property SomeSpecialControl: TControl
read FSomeSpecialControl write SetSomeSpecialControl;
end;
implementation
uses CastleComponentSerialize;
54
Modern Object Pascal Introduction for Programmers
[Link] := Value;
end;
end;
See [Link] .
6. Exceptions
6.1. Overview
• At any point within the program, you can raise an exception using the raise
keyword. In effect the lines of code following the raise … call will not execute.
Note: If an exception is raised but never caught, it will cause the entire application
to stop with an error.
# But in LCL applications, the exceptions are always caught around events (and
cause LCL dialog box) if you don’t catch them earlier.
# So it is not so easy to make an exception that is not caught anywhere (not caught
in your code, LCL code, CGE code…).
• Although an exception breaks the execution, you can use the try … finally …
end construction to execute some code always, even if the code was interrupted
by an exception.
55
Modern Object Pascal Introduction for Programmers
The try … finally … end construction also works when code is interrupted
by Break or Continue or Exit keywords. The point is to always execute code
in the finally section.
• The compiler does not enforce any particular class. You just must call raise XXX
where XXX is an instance of any class. Any class (so, anything descending from
TObject ) is fine.
• It is a standard convention for exception classes to descend from a special
Exception class. The Exception class extends TObject , adding a string
Message property and a constructor to easily set this property. All exceptions
raised by the standard library descend from Exception . We advise to follow this
convention.
• Exception classes (by convention) have names that start with E , not T . Like
ESomethingBadHappened .
• The compiler will automatically free exception object when it is handled. Don’t free
it yourself.
In most cases, you just construct the object at the same time when you call raise ,
like raise [Link]('Description of what
bad thing happened.') .
6.2. Raising
If you want to raise your own exception, declare it and call raise … when appropriate:
type
EInvalidParameter = class(Exception);
Note that the expression following the raise should be a valid class instance to raise.
You will almost always create the exception instance here.
56
Modern Object Pascal Introduction for Programmers
You can also use the CreateFmt constructor, which is a comfortable shortcut to
Create(Format(MessageFormat, MessageArguments)) . This is a common
way to provide more information to the exception message. We can improve the
previous example like this:
type
EInvalidParameter = class(Exception);
6.3. Catching
You can catch an exception like this:
var
Parameter1, Parameter2, Parameter3: String;
begin
try
WriteLn('Input 1st parameter:');
Parameter1 := ReadParameter;
WriteLn('Input 2nd parameter:');
Parameter2 := ReadParameter;
WriteLn('Input 3rd parameter:');
Parameter3 := ReadParameter;
except
// capture EInvalidParameter raised by one of the above ReadParameter
calls
on EInvalidParameter do
WriteLn('EInvalidParameter exception occurred');
end;
end;
To improve the above example, we can declare the name for the exception instance
(we will use E in the example). This way we can print the exception message:
try
57
Modern Object Pascal Introduction for Programmers
...
except
on E: EInvalidParameter do
WriteLn('EInvalidParameter exception occurred with message: ' +
[Link]);
end;
try
...
except
on E: EInvalidParameter do
WriteLn('EInvalidParameter exception occurred with message: ' +
[Link]);
on E: ESomeOtherException do
WriteLn('ESomeOtherException exception occurred with message: ' +
[Link]);
end;
You can also react to any exception raised, if you don’t use any on expression:
try
...
except
WriteLn('Warning: Some exception occurred');
end;
// WARNING: DO NOT FOLLOW THIS EXAMPLE WITHOUT READING A WARNING BELOW
// ABOUT "CAPTURING ALL EXCEPTIONS"
In general you should only catch exceptions of a specific class, that signal a particular
problem that you know what to do with. Be careful with catching exceptions of a general
type (like catching any Exception or any TObject ), as you may easily catch too
much, and later cause troubles when debugging other problems. As in all programming
languages with exceptions, the good rule to follow is to never capture an exception that
you do not know how to handle. In particular, do not capture an exception just as a
simple workaround of the problem, without investigating first why the exception occurs.
• Does the exception indicate a problem in user input? Then you should report it to
user.
• Does the exception indicate a bug in your code? Then you should fix the code, to
avoid the exception from happening at all.
58
Modern Object Pascal Introduction for Programmers
try
...
except
on E: TObject do
WriteLn('Warning: Some exception occurred');
end;
// WARNING: DO NOT FOLLOW THIS EXAMPLE WITHOUT READING A WARNING ABOVE
// ABOUT "CAPTURING ALL EXCEPTIONS"
try
...
except
on E: Exception do
WriteLn('Warning: Some exception occurred: ' + [Link] + ',
message: ' + [Link]);
end;
// WARNING: DO NOT FOLLOW THIS EXAMPLE WITHOUT READING A WARNING ABOVE
// ABOUT "CAPTURING ALL EXCEPTIONS"
You can "re-raise" the exception in the except … end block, if you decide so. You
can just do raise E if the exception instance is E , you can also just use parameter-
less raise . For example:
try
...
except
on E: EInvalidSoundFile do
begin
if [Link] = '[Link] then
WriteLn('Warning: loading [Link] failed,
ignore it')
else
raise;
end;
end;
59
Modern Object Pascal Introduction for Programmers
Note that, although the exception is an instance of an object, you should never manually
free it after raising. The compiler will generate proper code that makes sure to free the
exception object once it’s handled.
Often you use try .. finally .. end construction to free an instance of some
object, regardless of whether an exception occurred when using this object. The way
to write it looks like this:
procedure MyProcedure;
var
MyInstance: TMyClass;
begin
MyInstance := [Link];
try
[Link];
[Link];
finally
FreeAndNil(MyInstance);
end;
end;
This always works, and does not cause memory leaks, even if
[Link] or [Link] raise an
exception.
Note that this takes into account that local variables, like MyInstance above, have
undefined values (may contain random "memory garbage") before the first assignment.
That is, writing something like this would not be valid:
// INCORRECT EXAMPLE:
procedure MyProcedure;
var
MyInstance: TMyClass;
begin
try
CallSomeOtherProcedure;
MyInstance := [Link];
[Link];
60
Modern Object Pascal Introduction for Programmers
[Link];
finally
FreeAndNil(MyInstance);
end;
end;
Sometimes it is justified to fix the above code by first initializing all local variables to
nil (on which calling FreeAndNil is safe, and will not do anything). This makes
sense if you free a lot of class instances. So the two code examples below work equally
well:
procedure MyProcedure;
var
MyInstance1: TMyClass1;
MyInstance2: TMyClass2;
MyInstance3: TMyClass3;
begin
MyInstance1 := [Link];
try
[Link];
MyInstance2 := [Link];
try
[Link];
MyInstance3 := [Link];
try
[Link];
finally
FreeAndNil(MyInstance3);
end;
finally
FreeAndNil(MyInstance2);
end;
finally
61
Modern Object Pascal Introduction for Programmers
FreeAndNil(MyInstance1);
end;
end;
procedure MyProcedure;
var
MyInstance1: TMyClass1;
MyInstance2: TMyClass2;
MyInstance3: TMyClass3;
begin
MyInstance1 := nil;
MyInstance2 := nil;
MyInstance3 := nil;
try
MyInstance1 := [Link];
[Link];
MyInstance2 := [Link];
[Link];
MyInstance3 := [Link];
[Link];
finally
FreeAndNil(MyInstance3);
FreeAndNil(MyInstance2);
FreeAndNil(MyInstance1);
end;
end;
In this simple example, you could also make a valid argument that
the code should be split into 3 separate procedures, one calling each
other.
The final section in the try .. finally .. end block executes in most possible
scenarios when you leave the main code. Consider this:
try
A;
finally
B;
end;
62
Modern Object Pascal Introduction for Programmers
So B will execute if
The only way to really avoid the B being executed is to unconditionally interrupt the
4
application process using Halt or some platform-specific APIs (like libc exit on Unix )
inside A . Which generally should not be done — it’s more flexible to use exceptions
to interrupt the application, because it allows some other code to have a chance to
clean up.
procedure MyProcedure;
begin
try
WriteLn('Do something');
Exit;
WriteLn('This will not happen');
finally
WriteLn('This will happen regardless of whether we have left the block
through Exception, Exit, Continue, Break, etc.');
end;
WriteLn('This will not happen');
end;
See the Section 6, “Exceptions” chapter for more in-depth description of exceptions
including how to raise them and use try … except … end to catch them.
4
[Link]
63
Modern Object Pascal Introduction for Programmers
captured and will result in a nice dialog message, that allows the user
to continue and stop the application. This means that your own exceptions
do not "get out" from [Link] , so they do not
automatically break the application. You can configure what happens using
[Link] .
• In case of other applications, you can configure how the exception is displayed by
assigning a global callback to OnHaltProgram .
7. Run-time library
Modern programs should use TStream class and its many descendants to do input /
output. It has many useful descendants, like TFileStream , TMemoryStream ,
TStringStream .
uses
SysUtils, Classes;
var
S: TStream;
InputInt, OutputInt: Integer;
begin
InputInt := 666;
S := [Link]('my_binary_file.data', fmCreate);
try
[Link](InputInt, SizeOf(InputInt));
finally
FreeAndNil(S);
end;
64
Modern Object Pascal Introduction for Programmers
S := [Link]('my_binary_file.data', fmOpenRead);
try
[Link](OutputInt, SizeOf(OutputInt));
finally
FreeAndNil(S);
end;
In the Castle Game Engine: You should use the Download function to create a
stream that obtains data from any URL. Regular files, HTTP and HTTPS resources,
Android assets and more are supported this way. Moreover, to open the resource inside
your game data (in the data subdirectory) use the special castle-data:/xxx
URL. Examples:
EnableNetwork := true;
S := Download('[Link]
S := Download('[Link]
S := Download('castle-data:/gui/my_image.png');
To read text files, we advise using the TCastleTextReader class. It provides a line-
oriented API, and wraps a TStream inside. The TCastleTextReader constructor
can take a ready URL, or you can pass there your custom TStream source.
Text := [Link]('castle-data:/my_data.txt');
try
while not [Link] do
WriteLnLog('NextLine', [Link]);
finally
FreeAndNil(Text);
end;
Documentation of all the Castle Game Engine features to load and save streams,
including the Download function and the TCastleTextReader class, is on https://
[Link]/url .
65
Modern Object Pascal Introduction for Programmers
The language and run-time library offer various flexible containers. There are a number
of non-generic classes (like TList and TObjectList from the Contnrs unit),
there are also dynamic arrays ( array of TMyType ). But to get the most flexibility
and type-safety, I advise using generic containers for most of your needs.
The generic containers give you a lot of helpful methods to add, remove, iterate, search,
sort… The compiler also knows (and checks) that the container holds only items of the
appropriate type.
• FGL unit
• the naming is consistent with other parts of the standard library (like the non-generic
containers from the Contnrs unit).
TList
A generic list of types.
TObjectList
A generic list of object instances. It can "own" children, which means that it will free
them automatically.
66
Modern Object Pascal Introduction for Programmers
TDictionary
A generic dictionary.
TObjectDictionary
A generic dictionary, that can "own" the keys and/or values.
type
TApple = class
Name: string;
end;
var
A: TApple;
Apples: TAppleList;
begin
Apples := [Link](true);
try
A := [Link];
[Link] := 'my apple';
[Link](A);
A := [Link];
[Link] := 'another apple';
[Link](A);
Note that some operations require comparing two items, like sorting and searching (e.g.
by Sort and IndexOf methods). The [Link] containers use
a comparer for this. The default comparer is reasonable for all types, even for records
67
Modern Object Pascal Introduction for Programmers
(in which case it compares memory contents, which is a reasonable default at least for
searching using IndexOf ).
When sorting the list you can provide a custom comparer as a parameter. The comparer
is a class implementing the IComparer interface. In practice, you usually define
the appropriate callback, and use TComparer<T>.Construct method to wrap this
callback into an IComparer instance. An example of doing this is below:
type
TApple = class
Name: string;
end;
function CompareApples(
{$ifdef GENERICS_CONSTREF}constref{$else}const{$endif}
Left, Right: TApple): Integer;
begin
Result := AnsiCompareStr([Link], [Link]);
end;
type
TAppleComparer = {$ifdef FPC}specialize{$endif} TComparer<TApple>;
var
A: TApple;
68
Modern Object Pascal Introduction for Programmers
L: TAppleList;
begin
L := [Link](true);
try
A := [Link];
[Link] := '11';
[Link](A);
A := [Link];
[Link] := '33';
[Link](A);
A := [Link];
[Link] := '22';
[Link](A);
type
TApple = class
Name: string;
end;
var
69
Modern Object Pascal Introduction for Programmers
Apples: TAppleDictionary;
A, FoundA: TApple;
ApplePair: {$ifdef FPC} [Link] {$else}
TPair<String, TApple> {$endif};
AppleKey: string;
begin
Apples := [Link];
try
A := [Link];
[Link] := 'my apple';
[Link]('apple key 1', A);
The TObjectDictionary can additionally own the dictionary keys and/or values,
which means that they will be automatically freed. Be careful to only own keys and/
or values if they are object instances. If you set to "owned" some other type, like an
70
Modern Object Pascal Introduction for Programmers
Integer (for example, if your keys are Integer , and you include doOwnsKeys ),
you will get a nasty crash when the code executes.
type
TApple = class
Name: string;
end;
var
Apples: TAppleDictionary;
A: TApple;
ApplePair: {$ifdef FPC} [Link] {$else}
TPair<String, TApple> {$endif};
begin
Apples := [Link]([doOwnsValues]);
try
A := [Link];
[Link] := 'my apple';
[Link]('apple key 1', A);
If you prefer using the FGL unit instead of [Link] , the most
important classes from the FGL unit are:
71
Modern Object Pascal Introduction for Programmers
TFPGList
A generic list of types.
TFPGObjectList
A generic list of object instances. It can "own" children.
TFPGMap
A generic dictionary.
In FGL unit, the TFPGList can be only used for types for which the equality operator
(=) is defined. For TFPGMap the "greater than" (>) and "less than" (<) operators must
be defined for the key type. If you want to use these lists with types that don’t have
built-in comparison operators (e.g. with records), you have to overload their operators
as shown in the Section 8.11, “Operator overloading”.
If you want to know more about the generics, see Section 8.4, “Generics”.
var
X, Y: TMyObject;
begin
X := [Link];
Y := X;
// X and Y are now two pointers to the same data
[Link] := 123; // this also changes [Link]
FreeAndNil(X);
end;
To copy the class instance contents, the standard approach is to derive your
class from TPersistent , and override its Assign method. Once it’s implemented
properly in TMyObject , you use it like this:
72
Modern Object Pascal Introduction for Programmers
var
X, Y: TMyObject;
begin
X := [Link];
Y := [Link];
[Link](X);
[Link] := 123; // this does not change [Link]
FreeAndNil(X);
FreeAndNil(Y);
end;
To make it work, you need to implement the Assign method to actually copy the fields
you want. You should carefully implement the Assign method, to copy from a class
that may be a descendant of the current class.
uses
SysUtils, Classes;
type
TMyClass = class(TPersistent)
public
MyInt: Integer;
procedure Assign(Source: TPersistent); override;
end;
TMyClassDescendant = class(TMyClass)
public
MyString: string;
procedure Assign(Source: TPersistent); override;
end;
73
Modern Object Pascal Introduction for Programmers
var
C1, C2: TMyClass;
CD1, CD2: TMyClassDescendant;
begin
// test [Link]
C1 := [Link];
C2 := [Link];
try
[Link] := 666;
[Link](C1);
WriteLn('C2 state: ', [Link]);
finally
FreeAndNil(C1);
FreeAndNil(C2);
end;
// test [Link]
CD1 := [Link];
CD2 := [Link];
try
[Link] := 44;
74
Modern Object Pascal Introduction for Programmers
[Link] := 'blah';
[Link](CD1);
WriteLn('CD2 state: ', [Link], ' ', [Link]);
finally
FreeAndNil(CD1);
FreeAndNil(CD2);
end;
end.
Your class is a direct descendant of the TPersistent class. (Or, it’s not a direct
descendant of TPersistent , but no ancestor has overridden the Assign
method.)
In this case, your class should use the inherited keyword (to call the
[Link] ) only if you cannot handle the assignment in your code.
Your class descends from some class that has already overridden the Assign
method.
In this case, your class should always use the inherited keyword (to call
the ancestor Assign ). In general, calling inherited in overridden methods is
usually a good idea.
To understand the reason behind the above rule (when you should call, and when
you should not call inherited from the Assign implementation), and how it
relates to the AssignTo method, let’s look at the [Link] and
[Link] implementations:
75
Modern Object Pascal Introduction for Programmers
end;
When you have a class like TApple , your [Link] implementation usually
deals with copying fields that are specific to the TApple class (not to the TApple
ancestor, like TFruit ). So, the [Link] implementation usually checks
whether Source is TApple at the beginning, before copying apple-related fields.
Then, it calls inherited to allow TFruit to handle the rest of the fields.
• If you pass TApple instance to [Link] , it will work and copy all the
fields.
• If you pass TOrange instance to [Link] , it will work and only copy the
common fields shared by both TOrange and TApple . In other words, the fields
defined at TFruit .
• If you pass TWerewolf instance to [Link] , it will raise an
exception (because [Link] will call [Link] which will call
[Link] which raises an exception).
76
Modern Object Pascal Introduction for Programmers
The local routine can freely access (read and write) all the parameters of a parent, and
all the local variables of the parent that were declared above it. This is very powerful.
It often allows to split long routines into a couple of small ones without much effort (as
you don’t have to pass around all the necessary information in the parameters). Be
careful to not overuse this feature — if many nested functions use (and even change)
the same variable of the parent, the code may get hard to follow.
var
I: Integer;
begin
Result := 0;
for I := 0 to N do
Result := Result + Square(I);
end;
Another version, where we let the local routine Square to access I directly:
begin
77
Modern Object Pascal Introduction for Programmers
Result := 0;
for I := 0 to N do
Result := Result + Square;
end;
Local routines can go to any depth — which means that you can define a local routine
within another local routine. So you can go wild (but please don’t go too wild, or the
code will get unreadable:).
• Normal, which means it can point to any normal routine (not a method, not local).
type
TMyFunction = function (const A, B: Integer): Integer;
78
Modern Object Pascal Introduction for Programmers
end;
var
SomeFunction: TMyFunction;
begin
SomeFunction := @Add;
WriteLn('1 + 2 + 3 ... + 10 = ', ProcessTheList(SomeFunction));
SomeFunction := @Multiply;
WriteLn('1 * 2 * 3 ... * 10 = ', ProcessTheList(SomeFunction));
end.
uses
SysUtils;
type
TMyMethod = procedure (const A: Integer) of object;
TMyClass = class
CurrentValue: Integer;
procedure Add(const A: Integer);
procedure Multiply(const A: Integer);
procedure ProcessTheList(const M: TMyMethod);
end;
79
Modern Object Pascal Introduction for Programmers
for I := 2 to 10 do
M(I);
end;
var
C: TMyClass;
begin
C := [Link];
try
[Link]({$ifdef FPC}@{$endif} [Link]);
WriteLn('1 + 2 + 3 ... + 10 = ', [Link]);
Note that you cannot pass global procedures / functions as methods. They are
incompatible. If you have to provide an of object callback, but don’t want to
create a dummy class instance, you can pass Section 9.3, “Class methods” as
methods.
type
TMyMethod = function (const A, B: Integer): Integer of object;
TMyClass = class
class function Add(const A, B: Integer): Integer;
class function Multiply(const A, B: Integer): Integer;
end;
80
Modern Object Pascal Introduction for Programmers
var
M: TMyMethod;
begin
{$ifdef FPC}
// Unfortunately, this requires a bit of hack to work in FPC ObjFpc
mode.
M := @TMyClass(nil).Add;
M := @TMyClass(nil).Multiply;
{$else}
M := [Link];
M := [Link];
{$endif}
end.
• A (possibly) local routine: declare with is nested at the end, and make sure to
use {$modeswitch nestedprocvars} directive for the code. These go hand-
in-hand with Section 8.1, “Local (nested) routines”.
Example:
{$ifdef FPC}
{$ifdef VER3_2} {$message warn 'This code needs FPC >=
3.3.x'} begin end. {$endif}
{$mode objfpc}{$H+}{$J-}
{$modeswitch functionreferences}
{$modeswitch anonymousfunctions}
{$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
81
Modern Object Pascal Introduction for Programmers
type
{ Note about below TIntMapFunc and TIntMapProc definition, what to use?
With Delphi 12.1, only the "reference to" version will compile.
TIntMapFunc =
//function(const Index, Item: Integer): Integer;
//function(const Index, Item: Integer): Integer of object;
reference to function(const Index, Item: Integer): Integer;
TIntMapProc =
//procedure(const Index, Item: Integer);
//procedure(const Index, Item: Integer) of object;
reference to procedure(const Index, Item: Integer);
82
Modern Object Pascal Introduction for Programmers
Index: Integer;
begin
for Index := 0 to Count - 1 do
Items[Index] := AFunc(Index, Items[Index]);
end;
var
MyList: TMyInts;
I: Integer;
F: TIntMapFunc;
begin
MyList := [Link];
try
for I := 0 to 10 do
[Link](I);
83
Modern Object Pascal Introduction for Programmers
More information:
8.4. Generics
A powerful feature of any modern language. The definition of something (typically, of
a class) can be parameterized with another type. The most typical example is when
you need to create a container (a list, dictionary, tree, graph…): you can define a list
of type T, and then specialize it to instantly get a list of integers, a list of strings, a list
of TMyRecord, and so on.
The generics in Pascal work much like generics in C++. Which means that they are
"expanded" at specialization time, a little like macros (but much safer than macros;
for example, the identifiers are resolved at the time of generic definition, not at
specialization, so you cannot "inject" any unexpected behavior when specializing the
generic). In effect this means that they are very fast (can be optimized for each particular
type) and work with types of any size. You can use a primitive type (integer, float) as
well as a record, as well as a class when specializing a generic.
{$ifndef FPC}
{$message warn 'Delphi does not allow addition on types that are generic
parameters'}
begin end.
{$endif}
uses SysUtils;
type
84
Modern Object Pascal Introduction for Programmers
type
TMyFloatCalculator = {$ifdef FPC}specialize{$endif}
TMyCalculator<Single>;
TMyStringCalculator = {$ifdef FPC}specialize{$endif}
TMyCalculator<string>;
var
FloatCalc: TMyFloatCalculator;
StringCalc: TMyStringCalculator;
begin
FloatCalc := [Link];
try
[Link](3.14);
[Link](1);
WriteLn('FloatCalc: ', [Link]:2);
finally
FreeAndNil(FloatCalc);
end;
StringCalc := [Link];
try
[Link]('something');
[Link](' more');
WriteLn('StringCalc: ', [Link]);
finally
FreeAndNil(StringCalc);
end;
end.
Generics are not limited to classes, you can have generic functions and procedures
as well:
85
Modern Object Pascal Introduction for Programmers
{$ifndef FPC}
{$message warn 'Delphi does not support global generic functions'}
begin end.
{$endif}
uses SysUtils;
{ Note: this example requires FPC 3.1.1 (will not compile with FPC 3.0.0
or older). }
begin
WriteLn('Min (1, 0): ', specialize Min<Integer>(1, 0));
WriteLn('Min (3.14, 5): ', specialize Min<Single>(3.14, 5):1:2);
WriteLn('Min (''a'', ''b''): ', specialize Min<string>('a', 'b'));
end.
See also the Section 7.2, “Containers (lists, dictionaries) using generics” about
important standard classes using generics.
8.5. Overloading
Methods (and global functions and procedures) with the same name are allowed, as
long as they have different parameters. At compile time, the compiler detects which
one you want to use, knowing the parameters you pass.
By default, the overloading uses the FPC approach, which means that all the methods
in given namespace (a class or a unit) are equal, and hide the other methods in
namespaces with less priority. For example, if you define a class with methods
Foo(Integer) and Foo(string) , and it descends from a class with method
Foo(Float) , then the users of your new class will not be able to access the method
Foo(Float) easily (they still can --- if they typecast the class to its ancestor type).
To overcome this, use the overload keyword.
8.6. Preprocessor
You can use simple preprocessor directives for
86
Modern Object Pascal Introduction for Programmers
Note that macros with parameters are not allowed. In general, you should avoid using
the preprocessor stuff… unless it’s really justified. The preprocessing happens before
parsing, which means that you can "break" the normal syntax of the Pascal language.
This is a powerful, but also somewhat dirty, feature.
unit PreprocessorStuff;
interface
{$ifdef FPC}
{ This is only defined when compiled by FPC, not other compilers (like
Delphi). }
procedure Foo;
{$endif}
{ Define a NewLine constant. Here you can see how the normal syntax of
Pascal
is "broken" by preprocessor directives. When you compile on Unix
(includes Linux, Android, macOS), the compiler sees this:
const NewLine = ;
It's a *good* thing that the compilation fails in this case -- if you
will have to port the program to an OS that is not Unix, not Windows,
you will be reminded by a compiler to choose the newline convention
on that system. }
const
NewLine =
87
Modern Object Pascal Introduction for Programmers
{$define MY_SYMBOL}
{$ifdef MY_SYMBOL}
procedure Bar;
{$endif}
implementation
{$include some_file.inc}
// $I is just a shortcut for $include
{$I some_other_file.inc}
end.
Include files have commonly the .inc extension, and are used for two purposes:
• The include file may only contain other compiler directives, that "configure" your
source code. For example you could create a file [Link] with these
contents:
{$ifdef FPC}
{$mode objfpc}
{$H+}
{$J-}
{$modeswitch advancedrecords}
{$ifdef VER2}
{$message fatal 'This code can only be compiled using FPC version
>= 3.0.'}
{$endif}
{$endif}
88
Modern Object Pascal Introduction for Programmers
Now you can include this file using {$I [Link]} in all your sources.
• The other common use is to split a large unit into many files, while still keeping
it a single unit as far as the language rules are concerned. Do not overuse this
technique — your first instinct should be to split a single unit into multiple units, not to
split a single unit into multiple include files. Nevertheless, this is a useful technique.
1. It allows to avoid "exploding" the number of units, while still keeping your
source code files short. For example, it may be better to have a single unit with
"commonly used UI controls" than to create one unit for each UI control class,
as the latter approach would make the typical "uses" clause long (since a typical
UI code will depend on a couple of UI classes). But placing all these UI classes
in a single [Link] file would make it a long file, unhandy to navigate, so
splitting it into multiple include files may make sense.
Sometimes this is better than writing a long code with many {$ifdef
UNIX} , {$ifdef MSWINDOWS} intermixed with normal code (variable
declarations, routine implementation). The code is more readable this way.
You can even use this technique more aggressively, by using the -
Fi command-line option of FPC to include some subdirectories only for
specific platforms. Then you can have many version of include file {$I
my_platform_specific_implementation.inc} and you simply include
them, letting the compiler find the correct version.
8.7. Records
A record is just a container for other variables. It’s like a much, much simplified class:
there is no inheritance or virtual methods. It is like a structure in C-like languages.
89
Modern Object Pascal Introduction for Programmers
{$ifdef FPC}
{$mode objfpc}{$H+}{$J-}
{$modeswitch advancedrecords}
{$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
type
TMyRecord = record
public
I, Square: Integer;
procedure WriteLnDescription;
end;
procedure [Link];
begin
WriteLn('Square of ', I, ' is ', Square);
end;
var
A: array [0..9] of TMyRecord;
R: TMyRecord;
I: Integer;
begin
for I := 0 to 9 do
begin
A[I].I := I;
A[I].Square := I * I;
end;
for R in A do
[Link];
end.
In modern Object Pascal, your first instinct should be to design a class , not a
record — because classes are packed with useful features, like constructors and
inheritance.
But records are still very useful when you need speed or a predictable memory layout:
• Records do not have any constructor or destructor. You just define a variable of a
record type. It has undefined contents (memory garbage) at the beginning (except
auto-managed types, like strings; they are guaranteed to be initialized to be empty,
90
Modern Object Pascal Introduction for Programmers
and finalized to free the reference count). So you have to be more careful when
dealing with records, but it gives you some performance gain.
• The memory layout of records (size, padding between fields) is clearly defined
in some situations: when you request the C layout, or when you use packed
record . This is useful:
# to implement dirty low-level tricks (like unsafe typecasting one type to another,
being aware of their memory representation).
• Records can also have case parts, which work like unions in C-like languages.
They allows to treat the same memory piece as a different type, depending on your
needs. As such, this allows for greater memory efficiency in some cases. And it
allows for more dirty, low-level unsafe tricks:)
Variant records
Variant records allow to define a section at the end of your record where the same
memory can be accessed by a few different names/types.
Example:
type
TVector2 = packed record
case Integer of
0: (X, Y: Single);
1: (Data: array [0..1] of Single);
91
Modern Object Pascal Introduction for Programmers
end;
var
V2: TVector2;
V: TVector3;
I: Integer;
begin
Writeln('Size of TVector2 is ', SizeOf(TVector2));
Writeln(' Should be equal to ', SizeOf(Single) * 2);
V.X := 1;
V.Y := 2;
V.Z := 3;
for I := 0 to 2 do
Writeln('[Link][', I, '] is ', [Link][I]:1:2);
V2 := [Link];
for I := 0 to 1 do
Writeln('[Link][', I, '] is ', [Link][I]:1:2);
end.
Variant type
Variant is a special type in Pascal that underneath can hold values of various types.
Moreover, operators are defined to allow operating on them and converting their values
at run-time.
The effect is a bit similar to scripting programming languages with dynamic typing.
Do not use them without consideration: things are a bit less safe (you don’t control
types, conversions happen implicitly). Also there’s a small performance hit, since all
operations need to check and synchronize the types at run-time.
92
Modern Object Pascal Introduction for Programmers
But sometimes it does make sense. Namely, when you have to process data that
intrinsically indeed may have different types, and you only know those types at runtime.
E.g. when you want to process result of SQL select * from some_table in a
generic database viewer (not knowing table structure at compile-time).
uses Variants;
var
V1, V2, V3: Variant;
begin
V1 := 'My String';
V1 := 123; // V1 no longer holds String, it has Integer now
V2 := 456.789;
V3 := V1 + V2; // result is float
Writeln('V3 = ', V3);
end.
uses SysUtils;
93
Modern Object Pascal Introduction for Programmers
into a String. }
function GlueEverything(const MyArray: array of const): String;
var
I: Integer;
begin
Result := '';
for I := 0 to High(MyArray) do
begin
// treat MyArray[I] as TVarRec, check for type and do something
case MyArray[I].VType of
vtInteger:
begin
Writeln('Integer: ', MyArray[I].VInteger);
Result := Result + IntToStr(MyArray[I].VInteger) + ' ';
end;
vtAnsiString:
begin
Writeln('Ansi String (8-bit chars): ',
AnsiString(MyArray[I].VAnsiString));
Result := Result + AnsiString(MyArray[I].VAnsiString) + ' ';
end;
vtUnicodeString:
begin
Writeln('Unicode String (16-bit chars): ',
UnicodeString(MyArray[I].VUnicodeString));
Result := Result + UnicodeString(MyArray[I].VUnicodeString) + '
';
end;
else
Writeln('Something else, ignoring');
end;
end;
end;
var
S: String;
begin
S := GlueEverything([123, 'Hello', 'World', 456]);
Writeln(S);
end.
94
Modern Object Pascal Introduction for Programmers
• The old-style objects can be allocated / freed, and during that operation you can
call their constructor / destructor.
• But they can also be simply declared and used, like records. A simple record or
object type is not a reference (pointer) to something, it’s simply the data. This
makes them comfortable for small data, where calling allocation / free would be
bothersome.
• Old-style objects offer inheritance and virtual methods, although with small
differences from the modern classes. Be careful — bad things will happen if you try
to use an object without calling its constructor, and the object has virtual methods.
It’s discouraged to use the old-style objects in most cases. Modern classes provide
much more functionality. And when needed, records (including advanced records) can
be used for performance. These concepts are usually a better idea than old-style
objects.
8.10. Pointers
You can create a pointer to any other type. The pointer to type TMyRecord is declared
as ^TMyRecord , and by convention is called PMyRecord . This is a traditional
example of a linked list of integers using records:
type
PMyRecord = ^TMyRecord;
TMyRecord = record
Value: Integer;
Next: PMyRecord;
end;
Note that the definition is recursive (type PMyRecord is defined using type
TMyRecord , while TMyRecord is defined using PMyRecord ). It is allowed to define
a pointer type to a not-yet-defined type, as long as it will be resolved within the same
type block.
You can allocate and free pointers using the New / Dispose methods, or (more
low-level, not type-safe) GetMem / FreeMem methods. You dereference the pointer
95
Modern Object Pascal Introduction for Programmers
(to access the stuff pointed by) you append the ^ operator (e.g. MyInteger :=
MyPointerToInteger^ ). To make the inverse operation, which is to get a pointer of
an existing variable, you prefix it with @ operator (e.g. MyPointerToInteger :=
@MyInteger ).
Remember that a class instance is also in fact a pointer, although it doesn’t require any
^ or @ operators to use it. A linked list using classes is certainly possible, it would
simply be this:
type
TMyClass = class
Value: Integer;
Next: TMyClass;
end;
Both FPC and Delphi support overloading operators by defining class operator
methods inside advanced records. Like this:
{$ifdef FPC}
{$mode objfpc}{$H+}{$J-}
{$modeswitch advancedrecords}
{$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses SysUtils;
type
TVector3 = record
public
X, Y, Z: Single;
class operator {$ifdef FPC}+{$else}Add{$endif}
(const A, B: TVector3): TVector3;
class operator {$ifdef FPC}*{$else}Multiply{$endif}
(const V: TVector3; const Scalar: Single): TVector3;
function ToString: String;
96
Modern Object Pascal Introduction for Programmers
end;
var
V1, V2: TVector3;
begin
V1.X := 1.0; V1.Y := 2.0; V1.Z := 3.0;
V2.X := 4.0; V2.Y := 5.0; V2.Z := 6.0;
WriteLn('V1: ', [Link]);
WriteLn('V2: ', [Link]);
WriteLn('V1 + V2: ', (V1 + V2).ToString);
WriteLn('V1 * 10: ', (V1 * 10).ToString);
end.
With FPC, make sure to tell the compiler you use the "advanced
records" feature by {$modeswitch advancedrecords} .
Take a look at the documentation to learn all possible operators that can be overloaded:
5
• FPC operator overloading
6
• Delphi operator overloading
5
[Link]
6
[Link]
97
Modern Object Pascal Introduction for Programmers
{$ifndef FPC}
{$message warn 'Delphi does not support global operator overloading'}
begin end.
{$endif}
uses
StrUtils;
begin
WriteLn('bla' * 10);
end.
{$ifndef FPC}
{$message warn 'Delphi does not support global operator overloading'}
begin end.
{$endif}
uses
SysUtils;
type
TMyClass = class
MyInt: Integer;
end;
98
Modern Object Pascal Introduction for Programmers
begin
Result := [Link];
[Link] := [Link] * [Link];
end;
var
C1, C2: TMyClass;
begin
C1 := [Link];
try
[Link] := 12;
C2 := C1 * C1;
try
WriteLn('12 * 12 = ', [Link]);
finally
FreeAndNil(C2);
end;
finally
FreeAndNil(C1);
end;
end.
You can override operators on records too using the global operator functions. This
is usually easier than overloading them for classes, as the caller doesn’t have to deal
then with memory management.
{$ifndef FPC}
{$message warn 'Delphi does not support global operator overloading'}
begin end.
{$endif}
uses SysUtils;
type
TMyRecord = record
MyInt: Integer;
end;
99
Modern Object Pascal Introduction for Programmers
end;
var
R1, R2: TMyRecord;
begin
[Link] := 12;
R2 := R1 * R1;
WriteLn('12 * 12 = ', [Link]);
end.
However, for records, we don’t advise to use the global operator functions.
Instead, use {$modeswitch advancedrecords} and override operators as
class operator inside the record. Reasons:
{$ifdef FPC}
{$mode objfpc}{$H+}{$J-}
{$modeswitch advancedrecords}
{$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
{$ifndef FPC}
{$message warn 'Delphi does not have FGL unit'}
begin end.
{$endif}
uses
SysUtils, FGL;
type
TMyRecord = record
MyInt: Integer;
class operator+ (const C1, C2: TMyRecord): TMyRecord;
class operator= (const C1, C2: TMyRecord): boolean;
end;
100
Modern Object Pascal Introduction for Programmers
type
TMyRecordList = {$ifdef FPC}specialize{$endif} TFPGList<TMyRecord>;
var
R, ListItem: TMyRecord;
L: TMyRecordList;
begin
L := [Link];
try
[Link] := 1; [Link](R);
[Link] := 10; [Link](R);
[Link] := 100; [Link](R);
[Link] := 0;
for ListItem in L do
R := ListItem + R;
The private visibility specifier means that the field (or method) is not accessible
outside of this class. But it allows an exception: all the code defined in the same unit can
break this, and access private fields and methods. A C++ programmer would say that
in Pascal all classes within a single unit are friends. This is often useful, and doesn’t
break your encapsulation, since it’s limited to a unit.
101
Modern Object Pascal Introduction for Programmers
However, if you create larger units, with many classes (that are not tightly integrated
with each other), it’s safer to use strict private . It means that the field (or method)
is not accessible outside of this class — period. No exceptions.
Note that to declare a field after a constant or type you will need to open a var block.
type
TMyClass = class
private
type
TInternalClass = class
Velocity: Single;
procedure DoSomething;
end;
var
FInternalClass: TInternalClass;
public
const
DefaultVelocity = 100.0;
constructor Create;
destructor Destroy; override;
end;
constructor [Link];
begin
inherited;
FInternalClass := [Link];
[Link] := DefaultVelocity;
[Link];
end;
destructor [Link];
begin
FreeAndNil(FInternalClass);
102
Modern Object Pascal Introduction for Programmers
inherited;
end;
These are methods you can call having a class reference ( TMyClass ), not necessarily
a class instance.
type
TEnemy = class
procedure Kill;
class procedure KillAll;
end;
var
E: TEnemy;
begin
E := [Link];
try
[Link];
finally FreeAndNil(E) end;
[Link];
end;
Note that they can be virtual — it makes sense, and is sometimes very useful, when
combined with Section 9.4, “Class references”.
The class methods can also be limited by the Section 4.7, “Visibility specifiers”, like
private or protected . Just like regular methods.
Note that a constructor always acts like a class method when called in a normal fashion
( MyInstance := [Link](…); ). Although it’s possible to also call a
constructor from within the class itself, like a normal method, and then it acts like a
normal method. This is a useful feature to "chain" constructors, when one constructor
(e.g. overloaded to take an integer parameter) does some job, and then calls another
constructor (e.g. parameter-less).
103
Modern Object Pascal Introduction for Programmers
Class reference allows you to choose the class at runtime, for example to call a class
method or constructor without knowing the exact class at compile-time. It is a type
declared as class of TMyClass .
type
TMyClass = class(TComponent)
end;
TMyClass1 = class(TMyClass)
end;
TMyClass2 = class(TMyClass)
end;
var
C: TMyClass;
ClassRef: TMyClassRef;
begin
// Obviously you can do this:
C := [Link](nil); FreeAndNil(C);
C := [Link](nil); FreeAndNil(C);
C := [Link](nil); FreeAndNil(C);
ClassRef := TMyClass;
C := [Link](nil); FreeAndNil(C);
ClassRef := TMyClass1;
C := [Link](nil); FreeAndNil(C);
ClassRef := TMyClass2;
C := [Link](nil); FreeAndNil(C);
end;
Class references can be combined with virtual class methods. This gives a similar
effect as using classes with virtual methods — the actual method to be executed is
determined at runtime.
104
Modern Object Pascal Introduction for Programmers
type
TMyClass = class(TComponent)
class procedure DoSomething; virtual; abstract;
end;
TMyClass1 = class(TMyClass)
class procedure DoSomething; override;
end;
TMyClass2 = class(TMyClass)
class procedure DoSomething; override;
end;
var
C: TMyClass;
ClassRef: TMyClassRef;
begin
ClassRef := TMyClass1;
[Link];
ClassRef := TMyClass2;
[Link];
If you have an instance, and you would like to get a reference to its class (not the
declared class, but the final descendant class used at its construction), you can use the
ClassType property. The declared type of ClassType is TClass , which stands
for class of TObject . Often you can safely typecast it to something more specific,
when you know that the instance is something more specific than TObject .
In particular, you can use the ClassType reference to call virtual methods, including
virtual constructors. This allows you to create a method like Clone that constructs
an instance of the exact run-time class of the current object. You can combine it with
Section 7.3, “Cloning: [Link]” to have a method that returns a newly-
constructed clone of the current instance.
105
Modern Object Pascal Introduction for Programmers
Remember that it only works when the constructor of your class is virtual. For example,
it can be used with the standard TComponent descendants, since they all must
override [Link](AOwner: TComponent) virtual constructor.
type
TMyClass = class(TComponent)
procedure Assign(Source: TPersistent); override;
function Clone(AOwner: TComponent): TMyClass;
end;
While this is nice, it makes the normal class methods incompatible when assigning to
a global procedure pointer. That is, this will not compile:
type
TMyCallback = procedure (A: Integer);
TMyClass = class
class procedure Foo(A: Integer);
end;
106
Modern Object Pascal Introduction for Programmers
var
Callback: TMyCallback;
begin
// Error: [Link] not compatible with TMyCallback
Callback := {$ifdef FPC} @TMyClass(nil).Foo {$else}
[Link] {$endif};
end.
The above example fails to compile, because the Callback is incompatible with the
class method Foo . And it’s incompatible because internally the class method has that
special hidden implicit parameter to pass a class reference.
One way to fix the above example is to change the definition of TMyCallback . It
will work if it is a method callback, declared as TMyCallback = procedure (A:
Integer) of object; . But sometimes, it’s not desirable.
Here comes the static class method. It is, in essence, just a global procedure /
function, but its namespace is limited inside the class. It does not have any implicit
class reference (and so, it cannot be virtual and it cannot call virtual class methods).
On the upside, it is compatible with normal (non-object) callbacks. So this will work:
type
TMyCallback = procedure (A: Integer);
107
Modern Object Pascal Introduction for Programmers
TMyClass = class
class procedure Foo(A: Integer); static;
end;
var
Callback: TMyCallback;
begin
Callback := @[Link];
end.
It is similar to a regular property (see Section 4.5, “Properties”), but all classes access
(read and write) the same value. For a class property, you can define a getter and / or
a setter. They may refer to a class variable or a static class method.
A class variable is, you guessed it, like a regular field but you don’t need a class instance
to access it. In effect, it’s just like a global variable, but with the namespace limited to
the containing class. It can be declared within the class var section of the class.
Alternatively it can be declared by following the normal field definition with the keyword
static .
And a static class method is just like a global procedure / function, but with the
namespace limited to the containing class. More about static class methods in the
section above, see Section 9.5, “Static class methods”.
type
TMyClass = class
strict private
// Alternative:
// FMyProperty: Integer; static;
class var
108
Modern Object Pascal Introduction for Programmers
FMyProperty: Integer;
class procedure SetMyProperty(const Value: Integer); static;
public
class property MyProperty: Integer
read FMyProperty write SetMyProperty;
end;
begin
[Link] := 123;
Writeln('[Link] is now ', [Link]);
end.
The method is just a procedure or function inside a class. From the outside of the
class, you call it with a special syntax [Link](…) . After a while you
grow accustomed to thinking that if I want to make action Action on instance X, I write
`[Link](…)`.
109
Modern Object Pascal Introduction for Programmers
RenderMesh([Link][I].Mesh, Color);
end;
This works perfectly, but the downside is that calling it looks a little ugly. While usually
you call actions like [Link](…) , in this case you have to call them like Render(X,
…) . It would be cool to be able to just write [Link](…) , even when Render is
not implemented in the same unit as TMy3DObject .
And this is where you use class helpers. They are just a way to implement procedures /
functions that operate on given class, and that are called like methods, but are not in
fact normal methods — they were added outside of the TMy3DObject definition.
type
TMy3DObjectHelper = class helper for TMy3DObject
procedure Render(const Color: TColor);
end;
The more general concept is "type helper". Using them you can add
methods even to primitive types, like integers or enums. You can
also add "record helpers" to (you guessed it…) records. See http://
[Link]/fpc-announce/2013-February/[Link] .
The answer is actually different for constructors and destructors, and it also depends
on whether you are using TObject or TComponent as the ancestor class.
Destructors
There is only one destructor in a class. It’s name is always Destroy , it is virtual (since
you can call it without knowing the exact class at compile-time) and parameter-less.
Define it like this:
110
Modern Object Pascal Introduction for Programmers
type
TMyClass = class(TObject)
public
destructor Destroy; override;
end;
destructor [Link];
begin
// Cleanup code here
inherited; // Call the ancestor destructor
end;
While in theory it is possible to deviate from this approach, and define additional
destructors with different names, we don’t recommend it. A single destructor should be
capable of cleaning up the instance (regardless of how it was created), this is obvious
to developers and this way Free and FreeAndNil can be used reliably (they will
both indirectly call the Destroy method).
Constructors
Constructor name is by convention Create .
In the base TObject class there is one simple constructor, without any parameters,
called Create . It is not virtual. When creating descendants you’re free to define your
own constructor(s) with any parameters you want. The new constructor will hide the
constructor in the ancestor (though see below for some warnings about this "hiding").
If you don’t have a good reason to make the constructor virtual in descendants, don’t
do it, it is not necessary.
type
TMyClass = class(TObject)
public
constructor Create(AValue: Integer; const AName: String);
end;
111
Modern Object Pascal Introduction for Programmers
When creating a class, the correct constructor is called, since you explicitly indicate the
constructor name and parameters when creating an instance, like this:
X1 := [Link]('[Link]');
X2 := [Link](10, 'hello');
The TComponent standard class has a virtual constructor declared like this:
constructor Create(AOwner: TComponent); virtual; . Using virtual
constructor is necessary for the streaming functionality of TComponent , to create a
class without knowing its type at compile time (see Section 9.4, “Class references” for
explanation). In the TComponent descendants, you should override it.
For example, this is how you can define a constructor in a TComponent descendant.
Note that we cannot take additional parameters in this case (e.g. to take initial value
for property MyInt ), because we need to keep the same signature as in the ancestor
class. Any customization will have to be done by developer by setting properties or
calling other methods after the instance is created.
type
TMyComponent = class(TComponent)
private
FMyInt: Integer;
public
112
Modern Object Pascal Introduction for Programmers
Some other classes, also in your own code, may have virtual constructors (to enable
streaming them or just creating them using class references). In such cases, the
constructor in descendants should generally use the same parameters and be declared
with override (unless you really know what’s going on, i.e. you understand why
ancestor constructor was virtual and this reason doesn’t apply to your descendant
class).
X := [Link];
does not finish execution in this case, variable X is not assigned, so who will clean up
after a partially-constructed class?
The solution of Object Pascal is that, if an exception occurs within a constructor, then
the destructor is called. This is a reason why your destructor must be robust, which
means it should work in any circumstances, even on a half-created class instance.
Usually this is easy if you release everything safely, like by FreeAndNil .
A helpful property we can use to write robust destructors (that can handle half-created
instances) is that the memory of the class is guaranteed to be zeroed right before the
constructor code is executed. So we know that at the beginning, all class references
are nil , all integers are 0 and so on. The strategy for writing a robust destructor is
thus: "be prepared that any field may still be zero, and handle it without errors".
In effect, the code below works without any memory leaks, even though constructor
execution is interrupted, leaving only Gun1 but not Gun2 created:
113
Modern Object Pascal Introduction for Programmers
uses
SysUtils;
type
TGun = class
end;
TPlayer = class
Gun1, Gun2: TGun;
constructor Create;
destructor Destroy; override;
end;
constructor [Link];
begin
inherited;
Gun1 := [Link];
raise [Link]('Raising an exception from constructor!');
Gun2 := [Link];
end;
destructor [Link];
begin
{ in case since the constructor crashed, we can
have Gun1 <> nil and Gun2 = nil now. Deal with it.
...Actually, in this case, FreeAndNil deals with it without
any additional effort on our side, because FreeAndNil checks
whether the instance is nil before calling its destructor. }
FreeAndNil(Gun1);
FreeAndNil(Gun2);
inherited;
end;
begin
try
[Link];
except
on E: Exception do
WriteLn('Caught ' + [Link] + ': ' + [Link]);
end;
end.
114
Modern Object Pascal Introduction for Programmers
10. Interfaces
An interface declares an API, much like a class, but it does not define the
implementation. A class can implement many interfaces, but it can only have one
ancestor class. By convention, we start interface type names with letter I , like
IMyInterface .
You can cast a class to any interface it implements, and then call the methods through
that interface. This allows to treat in a uniform fashion the classes that don’t descend
from each other, but still share some common functionality. Useful when a simple class
inheritance is not enough.
{$ifdef FPC}
{$mode objfpc}{$H+}{$J-}
{$interfaces corba} // See below why we recommend CORBA interfaces
{$else}
{$message warn 'Delphi does not support CORBA interfaces, only COM, that
change how memory is managed. This example is not valid in Delphi.'}
begin end.
{$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses
SysUtils, Classes;
type
IMyInterface = interface
['{79352612-668B-4E8C-910A-26975E103CAC}']
procedure Shoot;
end;
TMyClass1 = class(IMyInterface)
procedure Shoot;
end;
TMyClass2 = class(IMyInterface)
procedure Shoot;
end;
TMyClass3 = class
procedure Shoot;
end;
115
Modern Object Pascal Introduction for Programmers
procedure [Link];
begin
WriteLn('[Link]');
end;
procedure [Link];
begin
WriteLn('[Link]');
end;
procedure [Link];
begin
WriteLn('[Link]');
end;
var
C1: TMyClass1;
C2: TMyClass2;
C3: TMyClass3;
begin
C1 := [Link];
C2 := [Link];
C3 := [Link];
try
if C1 is IMyInterface then
UseThroughInterface(C1 as IMyInterface);
if C2 is IMyInterface then
UseThroughInterface(C2 as IMyInterface);
// The "C3 is IMyInterface" below is false,
// so "UseThroughInterface(C3 as IMyInterface)" will not execute.
if C3 is IMyInterface then
UseThroughInterface(C3 as IMyInterface);
finally
FreeAndNil(C1);
FreeAndNil(C2);
FreeAndNil(C3);
end;
end.
116
Modern Object Pascal Introduction for Programmers
The GUIDs have no meaning if you don’t plan on integrating with communication
technologies like COM. But they are necessary, for implementation reasons. Don’t
be fooled by the compiler, that unfortunately allows you to declare interfaces without
GUIDs. Without the (unique) GUIDs, your interfaces will be treated equal by the is
operator. In effect, it will return true if your class supports any of your interfaces. The
magic function Supports(ObjectInstance, IMyInterface) behaves slightly
better here, as it refuses to be compiled for interfaces without a GUID.
To make inserting GUIDs easier, you can use Lazarus GUID generator ( Ctrl +
Shift + G shortcut in the editor). Alternatively, you can use uuidgen program on
Unix or use an online service like [Link] . Or you can write
your own tool for this, using the CreateGUID and GUIDToString functions in RTL.
See the example below:
uses
SysUtils;
var
MyGuid: TGUID;
begin
Randomize;
CreateGUID(MyGuid);
WriteLn('[''' + GUIDToString(MyGuid) + ''']');
end.
117
Modern Object Pascal Introduction for Programmers
UseThroughInterface(InterfacedVariable as IMyInterface);
2. Explicit typecasting:
UseThroughInterface(IMyInterface(InterfacedVariable));
3. Implicit typecasting:
UseThroughInterface(InterfacedVariable);
In this case, the typecast must be valid at compile-time. This will compile only if
the type of InterfacedVariable (either class or an interface) is implementing
IMyInterface .
In essence, this typecast looks and works just like for regular classes. Wherever an
instance of a class TSomeClass is required, you can always use a variable there
that is declared with a class of TSomeClass , or TSomeClass descendant. The
same rule applies to interfaces. No need for any explicit typecast in such situations.
118
Modern Object Pascal Introduction for Programmers
// {$interfaces corba} // note that "as" typecasts will not compile with
CORBA interfaces
uses Classes;
type
IMyInterface = interface
['{7FC754BC-9CA7-4399-B947-D37DD30BA90A}']
procedure One;
end;
IMyInterface2 = interface(IMyInterface)
['{A72B7008-3F90-45C1-8F4C-E77C4302AA3E}']
procedure Two;
end;
IMyInterface3 = interface(IMyInterface2)
['{924BFB98-B049-4945-AF17-1DB08DB1C0C5}']
procedure Three;
end;
procedure [Link];
begin
Writeln('[Link]');
end;
procedure [Link];
begin
Writeln('[Link]');
end;
procedure [Link];
begin
119
Modern Object Pascal Introduction for Programmers
Writeln('[Link]');
end;
var
MyInterface: IMyInterface;
MyClass: TMyClass;
begin
MyInterface := [Link](nil);
MyClass := [Link](nil);
120
Modern Object Pascal Introduction for Programmers
Writeln('Finished');
end.
This section is only relevant for FPC. Delphi has only COM
interfaces.
7
[Link]
121
Modern Object Pascal Introduction for Programmers
And I don’t advise using COM interfaces, especially if you’re looking for something
equivalent to interfaces from other programming languages. The CORBA interfaces
in Pascal are exactly what you expect if you’re looking for something equivalent to
the interfaces in C# and Java. While the COM interfaces bring additional features
that you possibly don’t want.
Note that the {$interfaces xxx} declaration only affects the interfaces
that do not have any explicit ancestor (just the keyword interface , not
interface(ISomeAncestor) ). When an interface has an ancestor, it has the
same type as the ancestor, regardless of the {$interfaces xxx} declaration.
• Requires that your classes define the _AddRef and _ReleaseRef methods.
Proper implementation of these methods can manage the lifetime of your objects
using the reference-counting.
• Adds the QueryInterface method.
• Allows to interact with the COM (Component Object Model) technology.
Why do you advise to not use the COM interfaces?
Because COM interfaces "entangle" two features that should be unrelated
(orthogonal) in my view: multiple inheritance and reference counting. Other
programming languages rightly use separate concepts for these two features.
122
Modern Object Pascal Introduction for Programmers
• If I want the feature of casting classes to a common interface API, but I don’t
want the reference-counting mechanism (I want to manually free objects), then
the COM interfaces are problematic. Even when reference-counting is disabled
by a special _AddRef and _ReleaseRef implementation, you still need to be
careful to never have a temporary interface reference hanging, after you have
freed the class instance. More about this in the next section.
• If I want the feature of reference counting, but I have no need for an interface
hierarchy to represent something different than the class hierarchy, then I have
to duplicate my classes API in interfaces. Thus creating a single interface for
each class. This is counter-productive. I would much rather have smart pointers
as a separate language feature, not entangled with interfaces (and luckily, it’s
coming:).
That is why I advise to use CORBA style interfaces, and the {$interfaces
corba} directive, in all modern code dealing with interfaces.
Only if you need both "reference counting" and "multiple inheritance" at the same
time, then use COM interfaces. Also, Delphi has only COM interfaces for now, so
you need to use COM interfaces if your code must be compatible with Delphi.
1. integration with COM (a technology from Windows, also available on Unix through
XPCOM, used by Mozilla),
2. reference counting (which gives you automatic destruction when all the interface
references go out of scope).
When using COM interfaces, you need to be aware of their automatic destruction
mechanism and relation to COM technology.
123
Modern Object Pascal Introduction for Programmers
{$ifdef FPC}
{$mode objfpc}{$H+}{$J-}
{$interfaces com}
{$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses
SysUtils, Classes;
type
IMyInterface = interface
['{3075FFCD-8EFB-4E98-B157-261448B8D92E}']
procedure Shoot;
end;
124
Modern Object Pascal Introduction for Programmers
TMyClass3 = class(TInterfacedObject)
procedure Shoot;
end;
procedure [Link];
begin
WriteLn('[Link]');
end;
procedure [Link];
begin
WriteLn('[Link]');
end;
procedure [Link];
begin
WriteLn('[Link]');
end;
var
C1: IMyInterface; // COM takes care of destruction
C2: IMyInterface; // COM takes care of destruction
C3: TMyClass3; // YOU have to take care of destruction
begin
C1 := [Link] as IMyInterface;
C2 := [Link] as IMyInterface;
C3 := [Link];
try
UseThroughInterface(C1); // no need to use "as" operator
UseThroughInterface(C2);
if Supports(C3, IMyInterface) then
UseThroughInterface(C3 as IMyInterface); // this will not execute
finally
{ C1 and C2 variables go out of scope and will be auto-destroyed now.
125
Modern Object Pascal Introduction for Programmers
You need to be careful in this case to not free the class instance when some interface
variable may refer to it. Remember that every typecast Cx as IMyInterface also
creates a temporary interface variable, which may be present even until the end of
the current procedure. For this reason, the example below uses a UseInterfaces
procedure, and it frees the class instances outside of this procedure (when we can be
sure that temporary interface variables are out of scope).
To avoid this mess, it’s usually better to use CORBA interfaces, if you don’t want
reference-counting with your interfaces.
{$ifdef FPC}
{$mode objfpc}{$H+}{$J-}
{$interfaces com}
{$endif}
{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}
uses
SysUtils, Classes;
type
IMyInterface = interface
['{3075FFCD-8EFB-4E98-B157-261448B8D92E}']
procedure Shoot;
end;
126
Modern Object Pascal Introduction for Programmers
TMyClass3 = class(TComponent)
procedure Shoot;
end;
procedure [Link];
begin
WriteLn('[Link]');
end;
procedure [Link];
begin
WriteLn('[Link]');
end;
procedure [Link];
begin
WriteLn('[Link]');
end;
var
C1: TMyClass1;
C2: TMyClass2;
C3: TMyClass3;
procedure UseInterfaces;
begin
// In FPC, you could also check using "is", like:
//if C1 is IMyInterface then ...
127
Modern Object Pascal Introduction for Programmers
UseThroughInterface(C3 as IMyInterface);
end;
begin
C1 := [Link](nil);
C2 := [Link](nil);
C3 := [Link](nil);
try
UseInterfaces;
finally
FreeAndNil(C1);
FreeAndNil(C2);
FreeAndNil(C3);
end;
end.
You can redistribute and even modify this document freely, under the same licenses
as Wikipedia [Link] :
8
[Link]
128