Conditional compilation

From Lazarus wiki
Jump to navigationJump to search

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

Compile-Time Directives $DEFINE and $IFDEF

Why?

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:

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

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.

Another way of doing the same thing is to use IDE macros. IDE_Macros_in_paths_and_filenames.

All that remains is to know where the {$DEFINE WIN32} is placed in the code or the IDE.

There are three possible ways to do this.

Unit based {$DEFINE} and {$IFDEF} statements.

  //Insert $DEFINE symbol at an earlier point in the unit
  {$DEFINE Win32}
  var
  MyFilesize:
  {$ifdef Win32} 
    Cardinal 
  {$else}
    int64
  {$endif} 
  //Insert $UNDEF symbol
  {UNDEF $Win32}

Use the IDE

In the IDE go to Project | Project Options | Compiler Options | Other | Custom options and enter -dWin32 or Win32, depending on the version. In Lazarus 1.2.4 the -d is entered automatically.

In Custom options

-d is the same as #DEFINE
-u is the same as #UNDEF

These entries apply to the whole project.

Use an 'include' file

See the more detailed example below.

Symbols

Nested $IFNDEF, $IFDEF, $ENDIF, $ELSE, $DEFINE, $UNDEF are allowed. See http://wiki.lazarus.freepascal.org/local_compiler_directives#Conditional_compilation for a complete list.

Complex Examples

Unit based defines and Include (.inc) files must be done individually for each unit. A Custom Option entry applies to every unit.

Unit based {$DEFINE} and {$IFDEF} statements

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.

var
  Form1: TForm1;

implementation

{$R *.lfm}
{$DEFINE RED}
//{$DEFINE BLUE}
{ TForm1 }

procedure TForm1.FormClick(Sender: TObject);
begin
  {$IFDEF RED} Form1.Color := clRed; {$ENDIF}
  {$IFDEF BLUE} Form1.Color := clBlue; {$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;

Note that {$IFDEF} expects a single statement. The rest is comments, so in the above example only {$IFDEF BLUE} is evaluated and the AND $IFDEF RED is a comment. A better way to show that is:

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.

Be careful not to mis-interpret this feature.
The correct way to handle the above code is with the {$IF defined()} syntax, like so:

{$DEFINE RED}
//{$DEFINE BLUE}
{ TForm1 }

procedure TForm1.FormClick(Sender: TObject);
begin
  // the following code does do what you think it does
  {$IF Defined(BLUE) AND $Defined(RED)} Form1.Color := clYellow; {$IFEND}
  {$IF not (Defined(RED) OR  Defined(BLUE))} Form1.Color := clAqua; {$IFEND}
end;

Include files

Include files add code into any .pas unit.

Create a file called unit1.inc (It could be called anything.inc.) that contains:

{$DEFINE RED}
//{$DEFINE BLUE}

Create another called unit1a.inc that contains:

  {$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}

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.


var
  Form1: TForm1;

implementation

{$R *.lfm}
  {$INCLUDE unit1.inc}
{ TForm1 }

procedure TForm1.FormClick(Sender: TObject);
begin
  {$INCLUDE unit1a.inc}
end;

Now, we can extend to this:

var
  Form1: TForm1;

implementation

{$R *.lfm}
{$IFDEF ABC}  
  {$INCLUDE abc.inc}
{$ELSE}
  {$INCLUDE xyz.inc}
{$ENDIF}

{ TForm1 }

procedure TForm1.FormClick(Sender: TObject);
begin
 ... some code ...
{$IFDEF ABC}  
  {$INCLUDE abcCode.inc}
{$ELSE}
  {$INCLUDE xyzCode.inc}
{$ENDIF} 
... some more code ... 
end;

Project | Project Options | Compiler Options | Other | Custom options

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. 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.

-dRED
-dBLUE

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.

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

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