Difference between revisions of "Conditional compilation"

From Free Pascal wiki
Jump to navigationJump to search
m (→‎top: syntax; allegedly add new external links [spam detection false positive])
 
(8 intermediate revisions by 5 users not shown)
Line 1: Line 1:
{{Conditional_compilation}}
+
{{Conditional compilation}}
 +
 
 +
'''Conditional compilation''' refers to compiling or omitting parts of [[Source code|source code]] based on an [[compile time expressions|expression evaluated at compile-time]].
 +
This allows taking account of, for example, different interfaces or architectures of specific [[operating system|operating systems]] or platforms, while still being able to program in a generic way.
 +
 
 +
== Support ==
 +
 
 +
Conditional compilation needs to be supported in some way or other.
 +
Some [[Compiler|compilers]] need an additional tool called ''pre-processor'', [[FPC]] however has all required functionality built-in.
 +
 
 +
For FPC and in de-facto most compiled languages, conditional compilation is implemented by specially crafted comments that are then seen as [[Compiler directive|compiler directives]].
 +
These surround any arbitrary amount of code that may be ignored or remain included based on an expression provided as evaluated at [[Compile time|compile-time]].
  
== What is conditional compilation? ==
 
Conditional compilation is compiling or omitting part of the sourcecode based on if a condition exists or not.<br>
 
The features that make this possible in most compiled languages are called compile time directives.
 
Compile time directives make it possible to compile a block of code based on the presence or absense of a condition at compile time.
 
 
They can be used for a variety of purposes like:
 
They can be used for a variety of purposes like:
 +
 
* Platform specific code isolation
 
* Platform specific code isolation
* Natural language selection  
+
* Natural language selection (where <syntaxhighlight lang="delphi" inline>resourceString</syntaxhighlight>s do not suffice)
 
* Licensing opensource and closed source parts
 
* Licensing opensource and closed source parts
 
* Isolating experimental code
 
* Isolating experimental code
* Compiler version
+
* Compiler version: certain compiler features may have been present only since a certain version
* Library version
+
* Library version: interfaces may have changed with certain versions
* etc, etc.
+
* etc., etc.
 +
 
 +
== Relevant compiler directives ==
  
FreePascal supports four different styles of conditional compilation:
+
FPC supports four different styles of conditional compilation:
* TurboPascal and early Delphi style directives
+
 
* Mac Pascal style directives
+
* [[Turbo Pascal]] and early [[Delphi]] style directives
* Modern Freepascal and Delphi style directives
+
* [[Mac Pascal]] style directives
* Compile time Macro's
+
* Modern Free Pascal and Delphi style directives
Note the syntax here is not case sensitive as conforms to all Pascal syntax. We will use both lowercase and uppercase examples.
+
* Compile time Macros
 +
 
 +
Note the syntax here is not [[case-sensitive|case sensitive]] as conforms to all [[Pascal]] syntax.
 +
We will use both lowercase and uppercase examples.
 
We will show you the difference between the modes and how to efficiently use them.
 
We will show you the difference between the modes and how to efficiently use them.
== TurboPascal style directives ==
+
 
The TurboPascal style directives are {$DEFINE}, {$IFDEF}, {$ENDIF}, {$IFNDEF}, {$IFOPT}, {$ELSE}, {$ELSEIF} and {$UNDEF}.<br>
+
=== Turbo Pascal style directives ===
We will describe the directives in the context of the style. Some defines have an extended meaning in another style.<br>
+
 
That means later on we may expand the meaning of certain directives like e.g. {$DEFINE} in the context of Macro's.
+
The Turbo Pascal style directives are
===== $define =====
+
* <syntaxhighlight lang="pascal" inline>{$DEFINE}</syntaxhighlight>,
The {$DEFINE} directive simply declares a symbol that we later can use for conditional compilation:
+
* <syntaxhighlight lang="pascal" inline>{$IFDEF}</syntaxhighlight>,
<syntaxhighlight>{$DEFINE name} // This defines a symbol called "name"</syntaxhighlight>
+
* <syntaxhighlight lang="pascal" inline>{$ENDIF}</syntaxhighlight>,
  Note you can also define a symbol from the command line or the IDE <b>-dDEBUG</b> for example is the command line equivalent of {$DEFINE DEBUG} in the sourcecode.
+
* <syntaxhighlight lang="pascal" inline>{$IFNDEF}</syntaxhighlight>,
===== $undef =====
+
* <syntaxhighlight lang="pascal" inline>{$IFOPT}</syntaxhighlight>,
The {$UNDEF} directive undefines a previously defined symbol. Here is an example that the author uses in practice:
+
* <syntaxhighlight lang="pascal" inline>{$ELSE}</syntaxhighlight>,
<syntaxhighlight>
+
* <syntaxhighlight lang="pascal" inline>{$ELSEIF}</syntaxhighlight> and
// Some older sourcecode is polluted with {$IFDEF FPC}'s that are no  
+
* <syntaxhighlight lang="pascal" inline>{$UNDEF}</syntaxhighlight>.
// longer necessary depending on the Delphi version to which it it should be compatible.
+
 
 +
We will describe the directives in the context of the style.
 +
Some defines have an extended meaning in another style.
 +
 
 +
That means later on we may expand the meaning of certain directives like e.&#8239;g. <syntaxhighlight lang="pascal" inline>{$DEFINE}</syntaxhighlight>in the context of Macros.
 +
 
 +
==== <syntaxhighlight lang="text" inline>$define</syntaxhighlight> ====
 +
 
 +
The <syntaxhighlight lang="pascal" inline>{$DEFINE}</syntaxhighlight> directive simply declares a symbol that we later can use for conditional compilation:
 +
 
 +
<syntaxhighlight lang="pascal">{$DEFINE name} // This defines a symbol called "name"</syntaxhighlight>
 +
 
 +
Note you can also define a symbol from the [[Command-line interface|command line]] or the [[IDE]], for example
 +
<syntaxhighlight lang="bash">-dDEBUG</syntaxhighlight>
 +
is the command line equivalent of <syntaxhighlight lang="pascal">{$DEFINE DEBUG}</syntaxhighlight> in the source code.
 +
 
 +
==== <syntaxhighlight lang="text" inline>$undef</syntaxhighlight> ====
 +
 
 +
The <syntaxhighlight lang="pascal" inline>{$UNDEF}</syntaxhighlight> directive undefines a (presumably) previously defined symbol.
 +
Here is an example that the author uses in practice:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
// Some older source code is polluted with {$IFDEF FPC}
 +
// that are no longer necessary
 +
// depending on the Delphi version to which it it should be compatible.
 
// I always test this by trying this on top of the program or unit:
 
// I always test this by trying this on top of the program or unit:
 
{$IFDEF FPC}
 
{$IFDEF FPC}
Line 39: Line 76:
 
   {$UNDEF FPC}
 
   {$UNDEF FPC}
 
   {$DEFINE VER150}  
 
   {$DEFINE VER150}  
   // code will now compile as if it was Delphi 7, provided the original Delphi sourcecode was indeed written for Delphi 7 and up.
+
   // code will now compile as if it was Delphi 7,
{$ENDIF}</syntaxhighlight>
+
  // provided the original Delphi source code
 +
  // was indeed written for Delphi 7 and up.
 +
{$ENDIF}
 +
</syntaxhighlight>
 +
 
 +
==== <syntaxhighlight lang="text" inline>$ifdef</syntaxhighlight> and <syntaxhighlight lang="text" inline>$endif</syntaxhighlight> ====
  
===== $ifdef and $endif =====
 
 
The simplest way to define a block of conditional code is like this:
 
The simplest way to define a block of conditional code is like this:
<syntaxhighlight>unit cross;
 
{$IFDEF FPC}{$MODE DELPHI}{$ENDIF}</syntaxhighlight>
 
The above example is quite common for sourcecode that has to compile in both Delphi and Freepascal</br>
 
If the compiler is Delphi then nothing is done, but if the compiler is FreePascal it will switch Freepascal to compile and use Delphi syntax mode.<br>
 
This "FPC" conditional symbol is defined in system - there is a long list of those.
 
The {$IFDEF} and {$ENDIF} block syntax is symmetrical: every {$IFDEF} has its own {$ENDIF}.<br> To help you recognize the corresponding blocks you can use e.g. indentation, but you can also use the comment feature:
 
<syntaxhighlight>{$IFDEF FPC this part is Freepascal specific}
 
// some Freepascal specific code
 
{$ENDIF Freepascal specific code}</syntaxhighlight>
 
  
<b>warning</b><br>
+
<syntaxhighlight lang="pascal">
This comment feature is often not well understood.
+
unit cross;
Some people - as on an older version of this wiki entry - assumed you could nest {$IFDEF}'s because the compiler seems to accept the syntax.
+
{$IFDEF FPC}{$MODE DELPHI}{$ENDIF}
But the former is false and the latter is true:
+
</syntaxhighlight>
Yes the compiler accepts the syntax below, but it is not a nested {$IFDEF} but a single {$IFDEF} condition and the rest is a comment!"
+
 
The code below executes the writeln <b>if and only if red is defined.</b><br>In this example {$ifdef blue} is a comment! Even if the {$define blue} is valid.
+
The above example is quite common for source code that has to compile with both Delphi and FPC.
<syntaxhighlight>program completelywrong;
 
{$define blue}
 
begin
 
{$ifdef red or $ifdef blue}// everything after red is a comment
 
  writeln ('red or blue'); // this code is never reached
 
{$endif red or blue}       // everything after $endif is a comment.
 
end.</syntaxhighlight>
 
===== $ifndef =====
 
This is the opposite of {$IFDEF} and code will be included of a certain condition is <b>not</b> defined.<br>
 
A simple example is:<syntaxhighlight>{$IFNDEF FPC this part not for Freepascal}
 
// some specific code that Freepascal should not compile
 
{$ENDIF code for other compilers than Freepascal}</syntaxhighlight>
 
  
===== $else and $elseif =====
+
If the compiler is Delphi, then nothing is done, but if the compiler is the FPC, it will configure FPC to compile and use Delphi syntax mode.
{$ELSE} is used to compile code that does not belong to the codeblock that is defined by the corresponding {$IFDEF}. It is also valid in the context {$IFOPT}, {$IF} or {$IFC} that we will discuss later.
 
<syntaxhighlight>
 
{$IFDEF red} 
 
  writeln('Red is defined'); 
 
{$ELSE  no red} 
 
  {$IFDEF blue} 
 
  writeln('Blue is defined, but red is not defined'); 
 
  {$ELSE no blue} 
 
  writeln('Neither red nor blue is defined');
 
  {$ENDIF blue} 
 
{$ENDIF red}</syntaxhighlight>
 
Such nested conditional written in the above syntax can get very messy and unreadable. Luckily we can simplify it a lot by using {$ELSEIF}. The code below is an expanded equivalent of the first example:
 
<syntaxhighlight>
 
{$IFDEF red} 
 
  writeln('Red is defined'); 
 
{$ELSEIF blue} 
 
  writeln('Blue is defined'); 
 
{$ELSEIF green} 
 
  writeln('Green is defined'); 
 
{$ELSE}
 
  writeln('Neither red, blue or green. Must be black...or something else...');
 
{$ENDIF}</syntaxhighlight>
 
As you can see this is a lot more readable.
 
===== $ifopt =====
 
With {$IFOPT} we can check if a certain compile option is defined.
 
From the programmers manual:
 
The {$IFOPT switch} will compile the text that follows it if the switch switch is currently in the specified state. If it isn’t in the specified state, then compilation continues after the corresponding {$ELSE}
 
or {$ENDIF} directive.
 
As an example:
 
<syntaxhighlight>
 
{$IFOPT M+} 
 
  Writeln(’Compiled with type information’); 
 
{$ENDIF}</syntaxhighlight>
 
Will compile the Writeln statement only if generation of type information is on.
 
Remark:The {$IFOPT} directive accepts only short options, i.e. {$IFOPT TYPEINFO} will not be accepted.
 
  
A common use is this example to test if DEBUG mode is defined:<syntaxhighlight>{$IFOPT D+}{$NOTE debug mode is active}{$ENDIF}</syntaxhighlight>
+
This <syntaxhighlight lang="text" inline>FPC</syntaxhighlight> conditional symbol is defined by the compiler (cf. [https://svn.freepascal.org/cgi-bin/viewvc.cgi/tags/release_3_2_0/compiler/options.pas?view=markup#l3695 <tt>compiler/options.pas</tt>]).
Such defines can also reside in configuration files like fpc.cfg which also contains a full explanation on how to use:
+
The <syntaxhighlight lang="pascal" inline>{$IFDEF}</syntaxhighlight> and <syntaxhighlight lang="pascal" inline>{$ENDIF}</syntaxhighlight> frame syntax is symmetrical:
# ----------------------
+
Every <syntaxhighlight lang="pascal" inline>{$IFDEF}</syntaxhighlight> has a matching <syntaxhighlight lang="pascal" inline>{$ENDIF}</syntaxhighlight>.
# Defines (preprocessor)
 
# ----------------------
 
#
 
# nested #IFNDEF, #IFDEF, #ENDIF, #ELSE, #DEFINE, #UNDEF are allowed
 
#
 
# -d is the same as #DEFINE
 
# -u is the same as #UNDEF
 
#
 
#
 
# Some examples (for switches see below, and the -? helppages)
 
#
 
# Try compiling with the -dRELEASE or -dDEBUG on the commandline
 
#
 
# For a release compile with optimizes and strip debuginfo
 
#IFDEF RELEASE
 
  -O2
 
  -Xs
 
  #WRITE Compiling Release Version
 
#ENDIF
 
  
= CODE AFTER THIS IS SUBJECT TO CHANGE OR REMOVAL PENDING EDITS =
+
To help you recognize the corresponding blocks you can use e.&#8239;g. indentation, but you can also use the [[Comments|comment]] feature:
== Mac Pascal style directives ==
 
== Modern Delphi style directives ==
 
=== {$IF}{$IFEND}
 
  
= LEGACY entries subject to edits and rewrites =
+
<syntaxhighlight lang="pascal">
If you have an application that needs several variations - say for two customers, or for two operating systems then compile-time defines are just what you need. A practical example is when coding across several platforms. 32 bit Windows only allows 4Gb files because of the maximum size of an integer and other operating systems do not have this limitation. So a filesize definition may be as follows:
+
{$IFDEF FPC this part is Free Pascal specific}
 +
// some Free Pascal specific code
 +
{$ENDIF Free Pascal specific code}
 +
</syntaxhighlight>
  
<syntaxhighlight>
+
{{Warning|This comment feature is often not well understood.
var
+
Some people – as on an older version of this wiki entry – assumed you could nest <syntaxhighlight lang="pascal" inline>{$IFDEF}</syntaxhighlight> because the compiler seems to accept the syntax.
  MyFilesize:
+
But the former is false and the latter is true:
  {$ifdef Win32}  
+
Yes, the compiler accepts the syntax below, but it is not a nested <syntaxhighlight lang="pascal" inline>{$IFDEF}</syntaxhighlight> but a single {<syntaxhighlight lang="pascal" inline>$IFDEF}</syntaxhighlight> condition and the rest is a comment!
    Cardinal
 
  {$else}
 
    int64
 
  {$endif}
 
</syntaxhighlight>
 
  
None of the above is case sensitive. {$ifdef}, {$else}, etc are known as symbols. When they are put together to perform logic the resultant code is known as a macro. For another practical example see: [[Code_Conversion_Guide#Useful_compiler_variables_.2F_defines_.2F_macros]].
+
The code below executes the [[Write|<syntaxhighlight lang="pascal" inline>writeLn</syntaxhighlight>]] if and only if <syntaxhighlight lang="text" inline>red</syntaxhighlight> is defined.
 +
In this example <syntaxhighlight lang="pascal" inline>{$ifdef blue}</syntaxhighlight> is a comment!
 +
Even if the <syntaxhighlight lang="pascal" inline>{$define blue}</syntaxhighlight> is valid.
  
Another way of doing the same thing is to use IDE macros. [[IDE_Macros_in_paths_and_filenames]].
+
<syntaxhighlight lang="pascal">
 +
// program completely wrong;
 +
{$define blue} 
 +
begin
 +
{$ifdef red or $ifdef blue} // everything after red is a comment
 +
  writeLn ('red or blue');  // this code is never reached
 +
{$endif red or blue}        // everything after $endif is a comment.
 +
end.</syntaxhighlight>
 +
}}
  
All that remains is to know where the {$DEFINE WIN32} is placed in the code or the IDE.
+
{{Note|The comment feature is non-standard and the [[GNU Pascal|GP]]C for instance will emit a warning “<syntaxhighlight lang="text" inline>garbage at end of `$ifdef' argument</syntaxhighlight>”.}}
  
There are three possible ways to do this.
+
==== <syntaxhighlight lang="text" inline>$ifndef</syntaxhighlight> ====
==== Unit based {$DEFINE} and {$IFDEF} statements.====
 
  
  <syntaxhighlight>
+
This is the opposite of <syntaxhighlight lang="pascal" inline>{$IFDEF}</syntaxhighlight> and code will be included of a certain condition is ''not'' defined.
  //Insert $DEFINE symbol at an earlier point in the unit
+
A simple example is:
  {$DEFINE Win32}
 
  var
 
  MyFilesize:
 
  {$ifdef Win32}  
 
    Cardinal
 
  {$else}
 
    int64
 
  {$endif}
 
  //Insert $UNDEF symbol
 
  {UNDEF $Win32}
 
  </syntaxhighlight>
 
  
==== Use the IDE====
+
<syntaxhighlight lang="pascal">
In the IDE go to Project | Project Options | Compiler Options | Other | Custom options and enter -dWin32 or Win32, depending on the version.
+
{$IFNDEF FPC this part not for Free Pascal}
In Lazarus 1.2.4 the -d is entered automatically.
+
// some specific code that Free Pascal should not compile
 +
{$ENDIF code for other compilers than Free Pascal}
 +
</syntaxhighlight>
  
In Custom options
+
==== <syntaxhighlight lang="text" inline>$else</syntaxhighlight> and <syntaxhighlight lang="text" inline>$elseif</syntaxhighlight> ====
-d is the same as #DEFINE
 
-u is the same as #UNDEF
 
  
These entries apply to the whole project.
+
<syntaxhighlight lang="pascal" inline>{$ELSE}</syntaxhighlight> is used to compile code that does not belong to the code block that is defined by the corresponding <syntaxhighlight lang="pascal" inline>{$IFDEF}</syntaxhighlight>.
 +
It is also valid in the context <syntaxhighlight lang="pascal" inline>{$IFOPT}</syntaxhighlight>, <syntaxhighlight lang="pascal" inline>{$IF}</syntaxhighlight> or <syntaxhighlight lang="pascal" inline>{$IFC}</syntaxhighlight> that we will discuss later.
  
==== Use an 'include' file====
+
<syntaxhighlight lang="pascal">
See the more detailed example below.
+
{$IFDEF red}
 +
    writeLn('Red is defined');
 +
{$ELSE  no red}
 +
  {$IFDEF blue}
 +
    writeLn('Blue is defined, but red is not defined');
 +
  {$ELSE no blue}
 +
    writeLn('Neither red nor blue is defined');
 +
  {$ENDIF blue}
 +
{$ENDIF red}
 +
</syntaxhighlight>
  
=== Symbols ===
+
Such nested conditional written in the above syntax can get very confusing and thus is prone to errors.
Nested $IFNDEF, $IFDEF, $ENDIF, $ELSE, $DEFINE, $UNDEF are allowed. See http://wiki.lazarus.freepascal.org/local_compiler_directives#Conditional_compilation for a complete list.
+
Luckily we can simplify it a lot by using <syntaxhighlight lang="pascal" inline>{$ELSEIF}</syntaxhighlight>.
 +
The code below is an expanded equivalent of the first example:
  
=== Complex Examples ===
+
<syntaxhighlight lang="pascal">
 +
{$IF defined(red)}
 +
  writeLn('Red is defined');
 +
{$ELSEIF defined(blue)}
 +
  writeLn('Blue is defined');
 +
{$ELSEIF defined(green)}
 +
  writeLn('Green is defined');
 +
{$ELSE}
 +
  writeLn('Neither red, blue or green. Must be black...or something else...');
 +
{$ENDIF}
 +
</syntaxhighlight>
  
Unit based defines and Include (.inc) files must be done individually for each unit. A Custom Option entry applies to every unit.
+
As you can see this is a lot more readable.
  
==== Unit based {$DEFINE} and {$IFDEF} statements ====
+
==== <syntaxhighlight lang="text" inline>$ifopt</syntaxhighlight> ====
Create a single form project as below. Comment and uncomment the two {$DEFINE) statements in turn and see what happens. If you add a second form (Form2) which opens when the first form (Form1) is clicked, similar statements will work independently of the {$DEFINE} statements in Form1.
 
  
<syntaxhighlight>
+
With <syntaxhighlight lang="pascal" inline>{$IFOPT}</syntaxhighlight> we can check if a certain compile option is defined.
var
 
  Form1: TForm1;
 
  
implementation
+
From the programmers’ manual:
 +
<blockquote>
 +
The <syntaxhighlight lang="pascal" inline>{$IFOPT switch}</syntaxhighlight> will compile the text that follows it if the switch switch is currently in the specified state.
 +
If it isn’t in the specified state, then compilation continues after the corresponding <syntaxhighlight lang="pascal" inline>{$ELSE}</syntaxhighlight> or <syntaxhighlight lang="pascal" inline>{$ENDIF}</syntaxhighlight> directive.
 +
</blockquote>
  
{$R *.lfm}
+
As an example:
{$DEFINE RED}
 
//{$DEFINE BLUE}
 
{ TForm1 }
 
  
procedure TForm1.FormClick(Sender: TObject);
+
<syntaxhighlight lang="pascal">
begin
+
{$IFOPT M+}
  {$IFDEF RED} Form1.Color := clRed; {$ENDIF}
+
   writeLn('Compiled with type information');
  {$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}
+
{$ENDIF}
   // the following code, however does not do what you think
 
  {$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}
 
  {$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}
 
end;
 
 
</syntaxhighlight>
 
</syntaxhighlight>
Note that {$IFDEF} expects a single statement. The rest is comments, so in the above example on line 4/5 only {$IFDEF BLUE  resp. {$IFNDEF RED is evaluated and the rest of those lines are comments.
 
A better way to show that is:
 
<syntaxhighlight>program untitled;
 
begin
 
{$ifdef CPUARM this code is for arm only} // everything after CPUARM is a comment
 
  writeln ('arm');
 
{$endif arm specific code} // everything after endif is a comment.
 
end.</syntaxhighlight>
 
 
Be careful not to mis-interpret this feature.<br><b>
 
The correct way to handle the above code is with the {$IF defined()} syntax, like so:</b>
 
<syntaxhighlight>{$DEFINE RED}
 
//{$DEFINE BLUE}
 
{ TForm1 }
 
  
procedure TForm1.FormClick(Sender: TObject);
+
Will compile the <syntaxhighlight lang="pascal" inline>writeLn</syntaxhighlight> statement only if generation of type information is enabled.
begin
+
{{Note|The <syntaxhighlight lang="pascal" inline>{$IFOPT}</syntaxhighlight> directive accepts only short options, i.&#8239;e. <syntaxhighlight lang="pascal" inline>{$IFOPT TYPEINFO}</syntaxhighlight> will not be accepted.}}
  // the following code does do what you think it does
 
  {$IF Defined(BLUE) AND $Defined(RED)} Form1.Color := clYellow; {$IFEND}
 
  {$IF not (Defined(RED) AND Defined(BLUE))} Form1.Color := clAqua; {$IFEND}
 
end;</syntaxhighlight>
 
  
==== Include files ====
+
A common use is this example to test if <syntaxhighlight lang="text" inline>DEBUG</syntaxhighlight> mode is defined:
Include files add code into any .pas unit.
 
  
Create a file called unit1.inc (It could be called anything.inc.) that contains:
+
<syntaxhighlight lang="pascal">{$IFOPT D+}{$NOTE debug mode is active}{$ENDIF}</syntaxhighlight>
  
<syntaxhighlight>
+
Such defines can also reside in configuration files like <syntaxhighlight lang="text" inline>fpc.cfg</syntaxhighlight> which also contains a full explanation on how to use:
{$DEFINE RED}
+
<syntaxhighlight lang="text">
//{$DEFINE BLUE}
+
# ----------------------
 +
# Defines (preprocessor)
 +
# ----------------------
 +
#
 +
# nested #IFNDEF, #IFDEF, #ENDIF, #ELSE, #DEFINE, #UNDEF are allowed
 +
#
 +
# -d is the same as #DEFINE
 +
# -u is the same as #UNDEF
 +
#
 +
#
 +
# Some examples (for switches see below, and the -? help pages)
 +
#
 +
# Try compiling with the -dRELEASE or -dDEBUG on the command line
 +
#
 +
# For a release compile with optimizes and strip debug info
 +
#IFDEF RELEASE
 +
  -O2
 +
  -Xs
 +
  #WRITE Compiling Release Version
 +
#ENDIF
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Create another called unit1a.inc that contains:
+
== What not to do ==
  
<syntaxhighlight>
+
This is a short tutorial:
  {$IFDEF RED} Form1.Color := clRed; {$ENDIF}
 
  {$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}
 
  {$IF Defined(BLUE) AND Defined(RED)} Form1.Color := clYellow; {$IFEND}
 
  {$IF NOT (Defined(RED) AND Defined(BLUE))} Form1.Color := clAqua; {$IFEND}
 
</syntaxhighlight>
 
  
Add them to the project folder. When compiled, these lines will replace the $INCLUDE statements below. Both methods present the same code to the compiler. However, using the include file method makes it easier to handle more complex requirements.
+
=== <!-- treat all --> Cases ===
 +
What is wrong with this code? Can you spot it?
  
 
+
<syntaxhighlight lang="pascal">
<syntaxhighlight>
 
 
var
 
var
   Form1: TForm1;
+
   MyFilesize:
 +
  {$ifdef Win32}
 +
    Cardinal
 +
  {$else}
 +
    int64
 +
  {$endif}
 +
  ;
 +
</syntaxhighlight>
  
implementation
+
{| class="wikitable mw-collapsible mw-collapsed"
 +
|+ answer
 +
|-
 +
|
 +
The answer is:
  
{$R *.lfm}
+
* that Free Pascal compiles for more CPU types than [[32 bit|32]] an [[64 bit|64 bit]], also for e.&#8239;g. 8 and 16 bit.
  {$INCLUDE unit1.inc}
+
* on most 64-bit platforms the maximum file size is a [[QWord]], not an [[Int64]].
{ TForm1 }
 
  
procedure TForm1.FormClick(Sender: TObject);
+
That programmer fell into a trap that is common:
begin
+
If you use a define, make sure your logic is solid.
  {$INCLUDE unit1a.inc}
+
Otherwise such code can easily cause accidents.
end;
+
The compiler will not catch your logic errors!
</syntaxhighlight>
 
  
Now, we can extend to this:
+
It is always good to realize such things especially that such things can easily be fixed.
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
var
 
var
   Form1: TForm1;
+
   MyFilesize:
 +
  {$if defined(Win32)}
 +
    Cardinal
 +
  {$elseif defined(Win64)}
 +
    Qword;
 +
  {$else}
 +
    {$error this code is written for win32 or win64}
 +
  {$endif}
 +
</syntaxhighlight>
  
implementation
+
As an aside of course there is a solution for this particular example that does not use conditionals at all:
 +
<syntaxhighlight lang="pascal">
 +
var
 +
  MyFilesize: NativeUint;
 +
</syntaxhighlight>
 +
|}
  
{$R *.lfm}
+
=== Understanding <!-- understanding comments features --> ===
{$IFDEF ABC} 
+
What is wrong with this code?
  {$INCLUDE abc.inc}
+
Can you spot it?
{$ELSE}
 
  {$INCLUDE xyz.inc}
 
{$ENDIF}
 
  
{ TForm1 }
+
<syntaxhighlight lang="pascal">
 
+
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}
procedure TForm1.FormClick(Sender: TObject);
+
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}
begin
 
... some code ...
 
{$IFDEF ABC}
 
  {$INCLUDE abcCode.inc}
 
{$ELSE}
 
  {$INCLUDE xyzCode.inc}
 
{$ENDIF}  
 
... some more code ...
 
end;
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== Project | Project Options | Compiler Options | Other | Custom options ====
+
{| class="wikitable mw-collapsible mw-collapsed"
Comment out, or remove, the {$DEFINE} symbols in your code and add the directives as below. In this case the directives will apply to all units.
+
|+ answer
This defines FPC Symbols. For example, -dDEBUG -dVerbose will define the FPC symbols DEBUG and Verbose, so you can use {$IFDEF Debug}. See http://wiki.freepascal.org/IDE_Macros_in_paths_and_filenames.
+
|-
 
+
|
Note the -d prefix.
+
The Answer is:
-dRED
+
* Well, I have already wrote a ''comment'' that warned you.. so look at the warning.... You should be able to spot it...
-dBLUE
+
* Compiler directives override the compiler... be careful with that ax Eugene.
 
+
|}
A similar approach can be used when compiling with FPC instead of Lazarus: specify the -d... argument on the command line calling FPC (e.g. in a batch file).
 
  
From Lazarus 1.2, the new Options GUI offers a choice: either enter the directive with -d as before, or use the GUI. The GUI internally stores all entered directives and activates the ones you select. These are then displayed as in pre version 1.2 with the -d prefix.
 
  
{{Template:Directives, Defines and Conditionals}}
+
{{Directives, Defines and Conditionals}}

Latest revision as of 01:32, 15 July 2020

Deutsch (de) English (en) suomi (fi) français (fr) русский (ru)

Conditional compilation refers to compiling or omitting parts of source code based on an expression evaluated at compile-time. This allows taking account of, for example, different interfaces or architectures of specific operating systems or platforms, while still being able to program in a generic way.

Support

Conditional compilation needs to be supported in some way or other. Some compilers need an additional tool called pre-processor, FPC however has all required functionality built-in.

For FPC and in de-facto most compiled languages, conditional compilation is implemented by specially crafted comments that are then seen as compiler directives. These surround any arbitrary amount of code that may be ignored or remain included based on an expression provided as evaluated at compile-time.

They can be used for a variety of purposes like:

  • Platform specific code isolation
  • Natural language selection (where resourceStrings do not suffice)
  • Licensing opensource and closed source parts
  • Isolating experimental code
  • Compiler version: certain compiler features may have been present only since a certain version
  • Library version: interfaces may have changed with certain versions
  • etc., etc.

Relevant compiler directives

FPC supports four different styles of conditional compilation:

  • Turbo Pascal and early Delphi style directives
  • Mac Pascal style directives
  • Modern Free Pascal and Delphi style directives
  • Compile time Macros

Note the syntax here is not case sensitive as conforms to all Pascal syntax. We will use both lowercase and uppercase examples. We will show you the difference between the modes and how to efficiently use them.

Turbo Pascal style directives

The Turbo Pascal style directives are

  • {$DEFINE},
  • {$IFDEF},
  • {$ENDIF},
  • {$IFNDEF},
  • {$IFOPT},
  • {$ELSE},
  • {$ELSEIF} and
  • {$UNDEF}.

We will describe the directives in the context of the style. Some defines have an extended meaning in another style.

That means later on we may expand the meaning of certain directives like e. g. {$DEFINE}in the context of Macros.

$define

The {$DEFINE} directive simply declares a symbol that we later can use for conditional compilation:

{$DEFINE name} // This defines a symbol called "name"

Note you can also define a symbol from the command line or the IDE, for example

-dDEBUG

is the command line equivalent of

{$DEFINE DEBUG}

in the source code.

$undef

The {$UNDEF} directive undefines a (presumably) previously defined symbol. Here is an example that the author uses in practice:

// Some older source code is polluted with {$IFDEF FPC}
// that are no longer necessary
// depending on the Delphi version to which it it should be compatible.
// I always test this by trying this on top of the program or unit:
{$IFDEF FPC}
  {$MODE DELPHI}
  {$UNDEF FPC}
  {$DEFINE VER150} 
  // code will now compile as if it was Delphi 7,
  // provided the original Delphi source code
  // was indeed written for Delphi 7 and up.
{$ENDIF}

$ifdef and $endif

The simplest way to define a block of conditional code is like this:

unit cross;
{$IFDEF FPC}{$MODE DELPHI}{$ENDIF}

The above example is quite common for source code that has to compile with both Delphi and FPC.

If the compiler is Delphi, then nothing is done, but if the compiler is the FPC, it will configure FPC to compile and use Delphi syntax mode.

This FPC conditional symbol is defined by the compiler (cf. compiler/options.pas). The {$IFDEF} and {$ENDIF} frame syntax is symmetrical: Every {$IFDEF} has a matching {$ENDIF}.

To help you recognize the corresponding blocks you can use e. g. indentation, but you can also use the comment feature:

{$IFDEF FPC this part is Free Pascal specific}
// some Free Pascal specific code
{$ENDIF Free Pascal specific code}
Warning-icon.png

Warning: This comment feature is often not well understood. Some people – as on an older version of this wiki entry – assumed you could nest {$IFDEF} because the compiler seems to accept the syntax. But the former is false and the latter is true: Yes, the compiler accepts the syntax below, but it is not a nested {$IFDEF} but a single {$IFDEF} condition and the rest is a comment! The code below executes the writeLn if and only if red is defined. In this example {$ifdef blue} is a comment! Even if the {$define blue} is valid.

// program completely wrong;
{$define blue}  
begin
{$ifdef red or $ifdef blue} // everything after red is a comment
  writeLn ('red or blue');  // this code is never reached
{$endif red or blue}        // everything after $endif is a comment.
end.

Light bulb  Note: The comment feature is non-standard and the GPC for instance will emit a warning “garbage at end of `$ifdef' argument”.

$ifndef

This is the opposite of {$IFDEF} and code will be included of a certain condition is not defined. A simple example is:

{$IFNDEF FPC this part not for Free Pascal}
// some specific code that Free Pascal should not compile
{$ENDIF code for other compilers than Free Pascal}

$else and $elseif

{$ELSE} is used to compile code that does not belong to the code block that is defined by the corresponding {$IFDEF}. It is also valid in the context {$IFOPT}, {$IF} or {$IFC} that we will discuss later.

{$IFDEF red}
     writeLn('Red is defined');
{$ELSE  no red}
  {$IFDEF blue}
    writeLn('Blue is defined, but red is not defined');
  {$ELSE no blue}
    writeLn('Neither red nor blue is defined');
  {$ENDIF blue}
{$ENDIF red}

Such nested conditional written in the above syntax can get very confusing and thus is prone to errors. Luckily we can simplify it a lot by using {$ELSEIF}. The code below is an expanded equivalent of the first example:

{$IF defined(red)}
  writeLn('Red is defined');
{$ELSEIF defined(blue)}
  writeLn('Blue is defined');
{$ELSEIF defined(green)}
  writeLn('Green is defined');
{$ELSE}
  writeLn('Neither red, blue or green. Must be black...or something else...');
{$ENDIF}

As you can see this is a lot more readable.

$ifopt

With {$IFOPT} we can check if a certain compile option is defined.

From the programmers’ manual:

The {$IFOPT switch} will compile the text that follows it if the switch switch is currently in the specified state. If it isn’t in the specified state, then compilation continues after the corresponding {$ELSE} or {$ENDIF} directive.

As an example:

{$IFOPT M+}
  writeLn('Compiled with type information');
{$ENDIF}

Will compile the writeLn statement only if generation of type information is enabled.

Light bulb  Note: The {$IFOPT} directive accepts only short options, i. e. {$IFOPT TYPEINFO} will not be accepted.

A common use is this example to test if DEBUG mode is defined:

{$IFOPT D+}{$NOTE debug mode is active}{$ENDIF}

Such defines can also reside in configuration files like fpc.cfg which also contains a full explanation on how to use:

# ----------------------
# Defines (preprocessor)
# ----------------------
#
# nested #IFNDEF, #IFDEF, #ENDIF, #ELSE, #DEFINE, #UNDEF are allowed
#
# -d is the same as #DEFINE
# -u is the same as #UNDEF
#
#
# Some examples (for switches see below, and the -? help pages)
#
# Try compiling with the -dRELEASE or -dDEBUG on the command line
#
# For a release compile with optimizes and strip debug info
#IFDEF RELEASE
  -O2
  -Xs
  #WRITE Compiling Release Version
#ENDIF

What not to do

This is a short tutorial:

Cases

What is wrong with this code? Can you spot it?

var
  MyFilesize:
  {$ifdef Win32}
    Cardinal
  {$else}
    int64
  {$endif}
  ;
answer

The answer is:

  • that Free Pascal compiles for more CPU types than 32 an 64 bit, also for e. g. 8 and 16 bit.
  • on most 64-bit platforms the maximum file size is a QWord, not an Int64.

That programmer fell into a trap that is common: If you use a define, make sure your logic is solid. Otherwise such code can easily cause accidents. The compiler will not catch your logic errors!

It is always good to realize such things especially that such things can easily be fixed.

var
  MyFilesize:
  {$if defined(Win32)} 
    Cardinal 
  {$elseif defined(Win64)}
    Qword;
  {$else}
    {$error this code is written for win32 or win64}
  {$endif}

As an aside of course there is a solution for this particular example that does not use conditionals at all:

var
  MyFilesize: NativeUint;

Understanding

What is wrong with this code? Can you spot it?

{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}
answer

The Answer is:

  • Well, I have already wrote a comment that warned you.. so look at the warning.... You should be able to spot it...
  • Compiler directives override the compiler... be careful with that ax Eugene.


Directives, definitions and conditionals definitions
global compiler directives • local compiler directives

Conditional Compiler Options • Conditional compilation • Macros and Conditionals • Platform defines
$IF