Appendix E: Free Pascal vs. Delphi Dialect Differences
Free Pascal (FPC) and Delphi are the two major Object Pascal compilers in active use today. While they share a common heritage and a great deal of syntax, they diverge in important ways. This appendix provides a systematic comparison so you can read Delphi code, port projects between the two compilers, and understand the implications of FPC's {$mode delphi} compatibility switch.
E.1 Compiler Modes in Free Pascal
Free Pascal supports multiple syntax modes, selected by a compiler directive at the top of each source file:
| Mode | Directive | Description |
|---|---|---|
| ObjFPC | {$mode objfpc} |
Free Pascal's native mode. Recommended for new FPC projects. Supports all FPC features including its native generics syntax. |
| Delphi | {$mode delphi} |
Delphi compatibility mode. Accepts most Delphi syntax including Delphi-style generics, anonymous methods (FPC 3.3+), and String as AnsiString by default. |
| FPC | {$mode fpc} |
Traditional Free Pascal mode (pre-ObjFPC). Rarely used for new code. |
| TP | {$mode tp} |
Turbo Pascal 7 compatibility mode. Useful for compiling vintage TP code. |
| MacPas | {$mode macpas} |
Macintosh Pascal compatibility. Very rarely used. |
Most code in this textbook uses {$mode objfpc}`. When porting Delphi examples, switch to `{$mode delphi} or translate the syntax differences described below.
E.2 Syntax Differences
Procedure Variable Assignment
In ObjFPC mode, assigning a procedure to a procedure variable requires the @ operator. Delphi mode does not.
{ ObjFPC mode }
type
TProc = procedure(X: Integer);
var
P: TProc;
begin
P := @MyProcedure; { @ required }
P(42);
end;
{ Delphi mode }
type
TProc = procedure(X: Integer);
var
P: TProc;
begin
P := MyProcedure; { No @ needed }
P(42);
end;
Method Pointers and @ Operator
The same distinction applies to method pointers. ObjFPC requires @; Delphi mode does not.
{ ObjFPC }
Button1.OnClick := @HandleClick;
{ Delphi }
Button1.OnClick := HandleClick;
Result Variable
Both modes support the Result variable in functions. ObjFPC also supports assigning to the function name (Turbo Pascal style):
function Add(A, B: Integer): Integer;
begin
Result := A + B; { Both modes }
Add := A + B; { Also valid, TP-compatible }
end;
Pointer Dereferencing
Both modes use ^ for dereferencing, but ObjFPC is stricter about requiring explicit dereferencing:
type
PRec = ^TRec;
TRec = record
Value: Integer;
end;
var
P: PRec;
begin
{ Both modes }
New(P);
P^.Value := 42;
{ Delphi mode also allows (auto-dereference): }
P.Value := 42; { Implicit dereference in Delphi mode }
{ ObjFPC requires the explicit ^ }
P^.Value := 42;
end;
String Type Default
| Mode | String resolves to |
Override |
|---|---|---|
ObjFPC with {$H+}` | `AnsiString` | `{$H-} for ShortString |
||
ObjFPC with {$H-}` | `ShortString` | `{$H+} for AnsiString |
||
| Delphi | AnsiString (always, {$H+} assumed) |
String[N] for ShortString |
Modern Delphi (2009+) defaults String to UnicodeString. FPC in {$mode delphi}` still defaults to `AnsiString`. You can switch with `{$ModeSwitch UnicodeStrings} in FPC trunk.
E.3 Generics
This is one of the largest syntax differences between FPC's ObjFPC mode and Delphi.
Declaration
{ ObjFPC mode }
type
generic TList<T> = class
procedure Add(const Item: T);
end;
{ Delphi mode }
type
TList<T> = class
procedure Add(const Item: T);
end;
Note: ObjFPC requires the generic keyword before the type name.
Implementation
{ ObjFPC mode }
procedure TList.Add(const Item: T);
begin
{ ... }
end;
{ Delphi mode }
procedure TList<T>.Add(const Item: T);
begin
{ ... }
end;
In ObjFPC mode, the implementation uses the unparameterized name (TList). In Delphi mode, it includes the type parameter (TList<T>).
Specialization / Instantiation
{ ObjFPC mode }
type
TIntList = specialize TList<Integer>;
var
List: specialize TList<String>;
{ Delphi mode }
type
TIntList = TList<Integer>;
var
List: TList<String>;
ObjFPC requires the specialize keyword whenever a generic type is instantiated. Delphi mode uses the angle brackets directly.
Generic Functions and Methods
{ ObjFPC mode }
generic function Max<T>(A, B: T): T;
begin
if A > B then Result := A else Result := B;
end;
{ Calling: }
X := specialize Max<Integer>(3, 7);
{ Delphi mode }
function Max<T>(A, B: T): T;
begin
if A > B then Result := A else Result := B;
end;
{ Calling: }
X := Max<Integer>(3, 7);
Generic Constraints
Both modes support constraints, with slightly different syntax in older FPC versions. Modern FPC (3.2+) supports constraints in both modes:
{ Both modes (FPC 3.2+) }
type
generic TComparer<T: class> = class
function Compare(A, B: T): Integer;
end;
Delphi supports additional constraint types (record, constructor) that FPC also implements in {$mode delphi}.
E.4 Anonymous Methods and Closures
Delphi
Delphi has supported anonymous methods since Delphi 2009:
{ Delphi }
type
TFunc = reference to function(X: Integer): Integer;
var
F: TFunc;
begin
F := function(X: Integer): Integer
begin
Result := X * X;
end;
WriteLn(F(5)); { 25 }
end;
Free Pascal
FPC added anonymous function support in the development branch (3.3.1+). In FPC 3.2.x (stable), anonymous methods are not available. The workaround is to use nested procedures with the is nested modifier:
{ FPC 3.2.x workaround }
type
TFunc = function(X: Integer): Integer is nested;
procedure Demo;
var
F: TFunc;
function Square(X: Integer): Integer;
begin
Result := X * X;
end;
begin
F := @Square;
WriteLn(F(5)); { 25 }
end;
In FPC trunk ({$mode delphi}` or `{$ModeSwitch AnonymousFunctions}), the Delphi syntax works:
{$ModeSwitch AnonymousFunctions}
{$ModeSwitch FunctionReferences}
{$ModeSwitch AdvancedRecords}
type
TFunc = reference to function(X: Integer): Integer;
E.5 String Handling Differences
| Feature | Delphi (Modern) | FPC (ObjFPC, {$H+}) |
|---|---|---|
Default String type |
UnicodeString (UTF-16) |
AnsiString (UTF-8 capable) |
| String indexing | 0-based optional ({$ZEROBASEDSTRINGS}) |
Always 1-based |
Char type |
WideChar (2 bytes) |
AnsiChar (1 byte) |
PChar |
PWideChar |
PAnsiChar |
| String helpers | S.Length, S.ToUpper, etc. |
Available with {$ModeSwitch TypeHelpers} |
String Helper Methods
Modern Delphi provides methods directly on the String type:
{ Delphi }
var
S: String;
begin
S := 'Hello World';
WriteLn(S.Length); { 11 }
WriteLn(S.ToUpper); { HELLO WORLD }
WriteLn(S.Contains('World')); { True }
WriteLn(S.Replace('World', 'Pascal'));
end;
FPC supports type helpers that provide similar functionality. The SysUtils unit in FPC 3.2+ includes TStringHelper:
{ FPC with type helpers }
{$mode objfpc}{$H+}
{$ModeSwitch TypeHelpers}
uses
SysUtils;
var
S: String;
begin
S := 'Hello World';
WriteLn(S.Length); { Works in FPC 3.2+ with TypeHelpers }
end;
However, the set of available methods may differ. Check the FPC documentation for the exact methods available on TStringHelper.
E.6 Unit and Package Differences
Unit Naming
| Feature | Delphi | FPC |
|---|---|---|
| Unit file extension | .pas |
.pas or .pp |
| Case sensitivity | Case-insensitive (Windows) | Case-insensitive on Windows, case-sensitive on Linux |
| Namespaced units | System.SysUtils, Vcl.Forms |
Not natively supported; use flat names |
Key Unit Name Differences
| Delphi Unit | FPC Equivalent | Notes |
|---|---|---|
System.SysUtils |
SysUtils |
FPC does not use dotted unit names |
System.Classes |
Classes |
|
System.Generics.Collections |
Generics.Collections or fgl |
FPC has its own generic containers |
Vcl.Forms |
Forms |
Lazarus LCL equivalent |
Vcl.Controls |
Controls |
|
Vcl.StdCtrls |
StdCtrls |
|
Vcl.Dialogs |
Dialogs |
|
Vcl.Graphics |
Graphics |
|
Data.DB |
DB |
|
FireDAC.* |
SQLDB / SQLite3Conn |
Different database frameworks |
Package System
Delphi uses .dpk packages. FPC/Lazarus uses .lpk packages. They are not interchangeable, but many Delphi packages have been ported to Lazarus or have Lazarus equivalents.
Resource Files
| Feature | Delphi | FPC/Lazarus |
|---|---|---|
| Form files | .dfm (binary or text) |
.lfm (text) |
| Resource compiler | brcc32 |
windres (Windows) or lazres |
| Resource include | {$R *.dfm}` | `{$R *.lfm} |
Lazarus can import Delphi .dfm files, but some property names and component types differ.
E.7 Compiler Directives
Shared Directives
Most compiler directives work identically in both compilers:
| Directive | Meaning |
|---|---|
{$R+}/{$R-} |
Range checking |
{$Q+}/{$Q-} |
Overflow checking |
{$I+}/{$I-} |
I/O checking |
{$H+}/{$H-} |
Long strings (AnsiString/ShortString) |
{$B+}/{$B-} |
Boolean evaluation |
{$IFDEF}/{$ENDIF} |
Conditional compilation |
{$WARNINGS OFF/ON} |
Suppress warnings |
{$HINTS OFF/ON} |
Suppress hints |
FPC-Only Directives
| Directive | Purpose |
|---|---|
{$mode objfpc} |
Set ObjFPC syntax mode |
{$mode delphi} |
Set Delphi compatibility mode |
{$ModeSwitch X} |
Enable specific language features |
{$INTERFACES CORBA} |
Use CORBA-style interfaces (no ref counting) |
{$INLINE ON} |
Enable inline function expansion |
{$OPTIMIZATION ON} |
Enable optimizations |
{$CODEPAGE UTF8} |
Set source file code page |
{$CALLING REGISTER} |
Set default calling convention |
Delphi-Only Directives
| Directive | Purpose |
|---|---|
{$LEGACYIFEND ON}` | Allow `{$IFEND} without matching {$IF} |
|
{$ZEROBASEDSTRINGS ON} |
0-based string indexing |
{$STRONGLINKTYPES ON} |
Force linking of RTTI types |
{$LIBPREFIX}`, `{$LIBSUFFIX} |
Library naming |
Predefined Symbols for Conditional Compilation
| Symbol | Defined When |
|---|---|
FPC |
Compiling with Free Pascal |
FPC_VERSION |
FPC major version number |
DELPHI |
Compiling with Delphi (not defined in FPC even in {$mode delphi}) |
MSWINDOWS / WINDOWS |
Target is Windows |
LINUX |
Target is Linux |
DARWIN |
Target is macOS |
UNIX |
Target is any Unix-like OS |
CPU64 |
64-bit target |
CPU32 |
32-bit target |
CPUX86_64 / CPUAMD64 |
x86-64 target |
CPUAARCH64 |
ARM64 target |
LCL |
Lazarus component library is available |
Use these to write code that compiles on both FPC and Delphi:
{$IFDEF FPC}
{$mode delphi}{$H+}
{$ENDIF}
uses
{$IFDEF FPC}
LCLType,
{$ENDIF}
{$IFDEF MSWINDOWS}
Windows,
{$ENDIF}
SysUtils, Classes;
E.8 Feature Availability Comparison
| Feature | Delphi | FPC (ObjFPC) | FPC (Delphi mode) |
|---|---|---|---|
| Classes and inheritance | Yes | Yes | Yes |
| Interfaces (COM) | Yes | Yes | Yes |
| Interfaces (CORBA) | No | Yes | Yes |
| Generics | Yes | Yes (different syntax) | Yes (Delphi syntax) |
| Anonymous methods | Yes (2009+) | Trunk only | Trunk only |
| Advanced records | Yes (2006+) | Yes (FPC 2.6+) | Yes |
| Operator overloading | Yes | Yes (different syntax) | Yes |
| Inline functions | Yes | Yes | Yes |
| Class helpers | Yes (2005+) | Yes | Yes |
| Attributes (RTTI annotations) | Yes (2010+) | Partial (FPC 3.2+) | Partial |
| Dynamic arrays | Yes | Yes | Yes |
for..in loops |
Yes (2005+) | Yes | Yes |
| Multi-platform targets | Windows, macOS, Linux, iOS, Android | 20+ OS targets, 15+ CPU architectures | Same |
| GUI framework | VCL (Windows), FMX (cross-platform) | LCL (Lazarus, cross-platform) | LCL |
| 64-bit support | Yes | Yes | Yes |
| WebAssembly target | Via FMX (limited) | FPC trunk | FPC trunk |
E.9 Operator Overloading
Operator overloading syntax differs between the two modes:
{ ObjFPC mode — uses 'operator' keyword at unit level }
type
TVector = record
X, Y: Double;
end;
operator + (A, B: TVector): TVector;
begin
Result.X := A.X + B.X;
Result.Y := A.Y + B.Y;
end;
{ Delphi mode — uses 'class operator' inside the record }
type
TVector = record
X, Y: Double;
class operator Add(A, B: TVector): TVector;
end;
class operator TVector.Add(A, B: TVector): TVector;
begin
Result.X := A.X + B.X;
Result.Y := A.Y + B.Y;
end;
E.10 Porting Delphi Code to Free Pascal
When you encounter Delphi code that you want to compile with FPC, follow this checklist:
-
Add
{$mode delphi}{$H+}at the top of each unit. This is the fastest way to get Delphi code compiling in FPC. -
Replace VCL unit names with LCL equivalents (see the unit name table above). Remove the
Vcl.,System.,Data.prefixes. -
Check for anonymous methods. If the code uses
reference tofunction types, you may need to refactor to regular function types or wait for FPC trunk support. -
Check for
Stringassumptions. If the Delphi code assumesStringisUnicodeString(2-byte characters), you may need to adjust for FPC's 1-byteAnsiString. -
Replace
{$IFDEF DELPHI}` with `{$IFDEF FPC}. Delphi definesDELPHIbut notFPC; FPC definesFPCbut notDELPHI. -
Check database components. Replace FireDAC with SQLDB or ZeosLib.
-
Check third-party libraries. Many popular Delphi libraries (Indy, Synapse, mORMot, Brook) have FPC support. Some (DevExpress, TMS) are Delphi-only.
-
Test on the target platform. Delphi code often has Windows-specific assumptions (
Windowsunit, registry access, COM objects). Replace with cross-platform equivalents.
E.11 Porting Free Pascal Code to Delphi
Going the other direction is usually easier because FPC code tends to be more portable. Key changes:
-
Remove
{$mode objfpc}and FPC-specific directives. Delphi ignores unknown directives with a warning, but it is cleaner to remove them. -
Remove
@from procedure variable assignments. -
Translate generics: Remove
genericandspecializekeywords, use Delphi's angle-bracket syntax. -
Replace
{$IFDEF FPC}` with `{$IFDEF DELPHI}or use{$IFNDEF FPC}. -
Replace LCL components with VCL/FMX equivalents.
-
Replace SQLDB with FireDAC or your preferred Delphi database library.
Understanding these differences allows you to work confidently with code from either ecosystem and to choose the right mode and syntax for your projects.