-
-
Notifications
You must be signed in to change notification settings - Fork 41
Some differences betwen FPC ObjFpc mode and Delphi (and FPC Delphi mode)
FPC compiler offers a few "syntax modes". They can chosen by
-
directives in Pascal code (like
{$mode objfpc}
,{$mode delphi}
), -
or by command-line options to FPC compiler (
-Mobjfpc
,-Mdelphi
), -
or by Lazarus project settings (in .lpi file; applicable if you compile from Lazarus IDE),
-
or by
-Mxxx
option in<compiler_options>
inCastleEngineManifest.xml
, if you compile using CGE editor or CGE build tool (see https://castle-engine.io/project_manifest#_compiler_options_and_paths ).
There are a few syntax modes in FPC, but within this page we will focus on two which are most popular: ObjFpc and Delphi syntax. They offer 100% the same capabilities, and are very compatible… but not absolutely compatible, there are small differences, which we outline below.
Delphi doesn’t have this concept (though it has a few options that affect syntax as well, we mention some of them below).
As the names suggest, FPC "Delphi mode" is very compatible with the actual Delphi.
FPC "ObjFpc mode" is a bit more encouraged by FPC developers (though it may depend on who you ask :) ). More practically, new units created by Lazarus have {$mode objfpc}{$H+}
and new Lazarus projects start with "mode ObjFpc" setting. So Lazarus developers encourage and use by default ObjFpc mode.
In Castle Game Engine, we follow Lazarus, so we use ObjFpc mode by default for your CGE projects, if they are being compiled with FPC. Though you can change it, for your projects: just put <option>-Mdelphi</option>
in compiler options in CastleEngineManifest.xml
. We show this example on https://castle-engine.io/project_manifest#_compiler_options_and_paths .
What are the differences? Along with some personal (Michalis) thoughts, sometimes biased, about "what is better"
The procedural variables (pointers to functions) have slightly different syntax.
In FPC ObjFpc mode, changing "routine" to "address of routine" requires an explicit @
operator.
So e.g. you have to write
procedure TMyView.ButtonClick(Sender: TObject);
begin
end;
procedure TMyView.Start;
begin
inherited;
OnClick := @ButtonClick; // this is only OK in FPC ObjFpc
end;
On the other hand, Delphi decided that ButtonClick
, without @
, is already a valid "address of ButtonClick
". In effect, this line will fail to compile:
OnClick := @ButtonClick; // this is only OK in FPC ObjFpc
(since in Delphi it means you try to take an address of the address of ButtonClick
, so it’s an address of temp variable, not sensible).
Only this compiles:
OnClick := ButtonClick; // this is only OK in Delphi or FPC Delphi mode
Why this difference?
-
FPC ObjFpc approach is helpful to make some expressions unambiguous. E.g. what does it mean
if OnMyEvent = MyFunction then …
, when events are defined asfunction: Integer
→ does this compare assigned events, or calls them? It gets even more complicated when the function returns the address of another function (likefunction: TNotifyEvent
). In FPC ObjFpc mode, such expressions are simple to understand, because "without@
, you cannot take address of a procedure, so the above calls theMyFunction
". -
From what I know, the advantage of "Delphi syntax" is just brevity. You can forget about
@
, and most expressions are obvious to the compiler, i.e. it can tell when you want to call a function, and when to take a function address.
See our test https://github.com/michaliskambi/modern-pascal-introduction/blob/master/code-samples/delphi_and_fpc_differences/compare_function_pointers.dpr for some constructions that look really weird in Delphi/FPC Delphi mode, IMHO, because of Delphi decision to omit @
. But it is also true that in "real, practical code" you don’t bump into these cases so often.
What to do in your code?
-
In CGE code and examples, we decided to just "live with it" and write
{$ifdef FPC}@{$endif}
everywhere needed. We like the extra clarity that comes from FPC ObjFpc requiring@
. Also, many FPC / Lazarus users are most used to ObjFpc mode, since it’s the default mode set up in new Lazarus projects, so we abide to it. So we writeOnClick := {$ifdef FPC}@{$endif} ButtonClick;
To be perfectly valid, we should actually write
OnClick := {$ifdef FPC_OBJFPC}@{$endif} ButtonClick;
But for simplicity, we decided to assume "when CGE is compiled with FPC, we know it is compiled in FPC ObjFpc mode, since we set it ourselves in both command-line and by castleconf.inc".
-
You can also choose a different approach: use "Delphi mode" with FPC. To do this, put
-Mdelphi
in compiler options inCastleEngineManifest.xml
(Lazarus project settings also have a setting to change mode). See " 6.11. Compiler options and paths" in https://castle-engine.io/project_manifest#_compiler_options_and_paths , we explicitly show there and discuss using-Mdelphi
.Then you have more consistent code between FPC and Delphi.
Below is more discussion and arguments why FPC ObjFpc mode is a good idea:
Consider writing this, in Delphi or FPC Delphi mode:
if X = Y then ...
What does it do, if X
and Y
are procedural variables? In Delphi mode ({$mode delphi}
in FPC or in actual Delphi), this is a tricky question. It stems from the fact that in Pascal, you call parameterless function without parenthesis, i.e. without trailing ()
. If X
and Y
point to parameterless function returning e.g. integers, if X = Y then
will actually call them and compare results. Only in other cases, function pointers will be actually compared.
Proof: compare_function_pointers.dpr. It’s a valid Pascal program, for both FPC and Delphi. Yet the if X = Y then
does something different.
To be sure you compare function pointers in Delphi, you can add address operator, like
if @X = @Y then ...
Which is ugly, because in this case the @
doesn’t get an address of X (although "@X" is defined exactly as "get address of X variable" in cases of other types in Pascal). It merely indicates we really want the function pointer.
It’s even more fun if you want to really get "an address of the function pointer", as then you have to write
@@X
which is nonsense in normal circumstances (for other types, e.g. when X is integer). @@
gets an address of a temporary address, which is never useful and should actually generate compiler error. But in this case, it’s needed. Also when you assign to X, you do
@X := MyFunction;
which is also nonsense in normal circumstances --- @X
is usually a temporary result, not l-value, how can you assign something to it?
In ObjFpc mode, it’s all more consistent. X
and @
behave the same as for other types. The only new rule is "when you call a procedural variable holding a parameterless function, you do it like in C --- you add `()`". This means that calling X doesn’t look "Pascalish", as you have to add parenthesis:
X()
…But everything else is more natural. In FPC ObjFpc mode, this:
if X = Y then
compares function pointers, when X
and Y
are e.g. of type function: Pointer
. It intuitively compares X
and Y
contents --- regardless if X
, Y
are integers, function pointers or parameterless function pointers. Also
@X
is now always the address of X (regardless if X is of type Integer, or a function pointer). Assignment of one function variable to other is normal:
X := Y
and assignment of actual function to X explicitly uses @ operator on the right side, signifying that we take an address of function:
X := @MyFunction;
In ObjFpc it is not allowed to have field/variable/parameter names that would conflict, also between descendants and ancestors
This rule breaks the usual scoping rules for Pascal ("conflicts are allowed when they are in different namespaces, and the local-most namespace wins"), but it does so with a good purpose, protecting you from certain errors. Imagine this code:
TMyClass = class
X: Integer;
procedure Foo(X: Integer);
end;
This is allowed in Delphi mode. Inside Foo
implementation, resolution follows normal Pascal rules: the same name, X, is allowed if in different scopes, and more local scope takes precedence.
procedure TMyClass.Foo(X: Integer);
begin
X := 123; // changes the X parameter value, X field is untouched
end;
This is however dangerous: if you change Foo declaration to Foo(Y: Integer)
, the implementation will still be compiled… but it will do something totally different, probably wrong, since you now change the object field. To prevent this, ObjFpc disallows this: parameter names must have different names than fields/methods/properties of the class. TMyClass
above will simply not compile in ObjFPC mode, you have to change parameter name to something like AX, and you will probably remember to use AX inside Foo implementation. If you ever change Foo declaration, you will most likely get a compilation error until you correct all occurrences of AX.
This rule also goes for local variables. E.g.
procedure TMyClass.Foo;
var
X: Integer
begin
...
end;
is not allowed.
It also applies to descendants of TMyClass
, in TMyClassDescendant
you also cannot now declare X
(fields, local variables etc.)
In FPC ObjFpc mode, if you have
X: ^Integer;
and you use
X[2]
then ObjFPC treats this similar to C: it’s like X was a pointer to an array of Integer, and you want the 2nd one. So
Y: array [0..999] of Integer;
X: ^Integer;
...
X := @(Y[10]);
Assert(X^ = Y[10]);
Assert(X[0] = Y[10]);
Assert(X[2] = Y[12]);
TODO: restore my demo: array_ptrs.lpr
In Delphi, this is not allowed, you cannot treat a pointer to TTT like an array of TTT.
OTOH, in Delphi there’s a different shortcut. Namely, you can simply omit the ^ if the dereferenced type is already an array. That is, if X is a pointer and you write
X[2]
then Delphi treats it like
X^[2]
Obviously this makes sense only if X is a pointer to the array, like
type
TIntegers = array [0..999] of Integer;
var
X: ^TIntegers;
An often used trick in Delphi is declaring an "infinite" (in fact, as long as possible to compile, hence MaxInt trick) array, and use it as a comfortable pointer type:
type
TMyRecord = record A, B, C: Integer; ... whatever else ... end;
TMyRecords = array [0..MaxInt div SizeOf(TMyRecord) - 1] of TMyRecord;
PMyRecords = ^TMyRecords;
var
MyRecords: PMyRecords;
... // and for example you can do:
MyRecords := GetMem(SizeOf(TMyRecord) * 10);
// in Delphi (real Delphi or delphi mode in FPC) you can do:
MyRecords[1].A := 123;
// in both Delphi mode and objfpc mode you can also do it without omitting ^:
MyRecords^[1].A := 123;
In FPC ObjFpc mode, you would rather do it like this:
type
TMyRecord = record A, B, C: Integer; ... whatever else ... end;
PMyRecord = ^TMyRecord;
var
MyRecord: PMyRecord;
... // and for example you can do:
MyRecord := GetMem(SizeOf(TMyRecord) * 10);
// not writing ^ means that MyRecord is automatically treated as a pointer
// to an array of whatever it was declared.
MyRecord[1].A := 123;
There is a small catch in all this, place that both Delphi (real Delphi or {$mode delphi}
in FPC) and ObjFPC mode compile, but interpret differently. Recall previous example of infinite array:
type
TMyRecord = record A, B, C: Integer; ... whatever else ... end;
TMyRecords = array [0..MaxInt div SizeOf(TMyRecord) - 1] of TMyRecord;
PMyRecords = ^TMyRecords;
var
MyRecords: PMyRecords;
... // and for example you can do:
MyRecords := GetMem(SizeOf(TMyRecord) * 10);
// in Delphi (real Delphi or {$mode delphi} in FPC) you can do:
MyRecords[1].A := 123;
The catch is: actually you can use MyRecords[1].A
also in ObjFPC mode. It will compile, but it will mean something that you probably don’t want. MyRecords
is then treated as an array of whatever it actually points to… so MyRecords
is a pointer to an array of TMyRecords
arrays! MyRecords[1]
refers to the 2nd array. Most definitely not what you wanted, and outside the allocated memory.
Morale: do not omit the ^
operator. Or pay attention to your mode: both ObjFPC and Delphi have a different idea when you can omit it and how it’s interpreted.
Note: Later Delphi actually introduces something very similar to FPC, {$pointermath on}
. This actually makes Delphi behave quite close to what FPC does in this regard and it’s a good idea IMHO. Our castleconf.inc
(used in all CGE units) defines it now, I recommend you use it also in your applications where it makes sense. See https://docwiki.embarcadero.com/RADStudio/Sydney/en/Pointer_Math_(Delphi) .
Finally, a small difference that is actually a small annoyance of ObjFPC mode. In ObjFPC, by default string = ShortString. That’s why you should almost always do
{$mode objfpc}{$H+}
at the beginning of your sources, to use modern Object Pascal string.
Do not do this, as almost never you want to deal with ShortString
and it’s shortcomings in modern code:
{$mode objfpc} // you probably don't want do this, you probably want to add {$H+} as above
In contrast, {$mode delphi}
automatically changes string
to be alias to modern AnsiString
by default. So simply using
{$mode delphi}
is enough. You can of course also use {$mode delphi}{$H+}
, but then the {$H+}
is just superfluous.
ObjFPC behaves like this (doesn’t imply string=AnsiString) for backward compatibility. There were discussions about changing this default (and changing default FPC mode), but (so far) the backward compatibility argument wins.
Note that actual Delphi, recent versions does something even different nowadays: default String
is UnicodeString
, not ShortString
, not AnsiString
. In CGE, we deal with it by adjusting to both compilers defaults: Most code should use just String, and be prepared that it is 8-bit on FPC and 16-bit on Delphi.
In Fpc ObjFpc, you must use generic
and specialization
keywords, which you can omit in Delphi (or FPC Delphi mode).
Examples:
type
generic TMyCalculator<T> = class
Value: T;
procedure Add(const A: T);
end;
TMyFloatCalculator = specialize TMyCalculator<Single>;
(source: https://github.com/michaliskambi/modern-pascal-introduction/blob/master/code-samples/generics.lpr )
In Delphi, or FPC Delphi mode, you just remove these keywords. Or surround them in {$ifdef FPC_OBJFPC}…{$endif}
. In Castle Game Engine, we surround them in {$ifdef FPC}…{$endif}
, to simplify and rely on assumption "when the engine is compiled by FPC, it is always in ObjFpc mode".
Moreover, the FPC implementation (in both ObjFpc and Delphi modes) is, right now, a little less strict than Delphi implementation when it comes to checking type-correctness of generics. That is,
-
FPC will check 100% correctness at specialization and will allow to define some generics that may, or may not, be correct.
-
Delphi is more strict in this regard, more checks are performed already at generic definition. Using generic constraints is more often necessary in Delphi.