Conditional Compilation

Conditional compilation provides a way to have one set of source code that can be compiled slightly differently depending on certain conditions, such as compiling for different platforms, compiling different versions/editions of your project, or, if you must, compiling the same code in Elements and other compilers, for example:

  • Oxygene vs. legacy Pascal compilers, such as Delphi or Free Pascal
  • RemObjects C# vs. Microsoft Visual C# or Mono's C# Compiler
  • RemObjects Silver vs. Apple's Swift compiler
  • .NET vs. Cocoa vs. Java

For example:

  • The ECHOES, TOFFEE, COOPER and ISLAND symbols can be used to distinguish between platforms, .NET, Cocoa, Java and Island, respectively.
  • The ELEMENTS symbol can be used to conditionally check for Elements vs. other compilers (e.g. when sharing code with Delphi, Visual C# or Apple's Swift Compiler).

Providing Conditional Defines

Conditional defines can originate from four sources:

  • The compiler will provide a set of predefined symbols based on compiler version and platform, as outlined in the Conditional Defines topics. Examples include ELEMENTS (always defined) or COCOA (defined for Apple's platforms).

  • Referenced libraries or frameworks can provide additional symbols that are active if the respective namespace is used. For example, the iOS SDK provides TARGET_OS_IPHONE to distinguish between Mac and iOS, in the implicitly used rtl namespace.

  • A list of Project-wide defines can be specified in the Project Settings and will be available to check for throughout the project. These defines can be configured separately for each configuration.

  • Finally, defines can be set (and removed) right inside the source code using {$DEFINE} (Oxygene) or #define (C#, Swift and Java) Compiler Directives. Defines set (or removed) with these directives will apply only to the code in the same file and below the directive.

Conditional Compilation w/ defined()

New in Elements 10, the defined() system function can be used to specify conditional compilation using regular if statements that integrate naturally with the flow of the code.

Conditional Compilation w/ Directives

Wherever possible, i.e. inside method bodies, using defined() is the preferred way for conditional compilation. That said, support for conditional directives such as $IF (Oxygene) and #if (C#, Swift and Java) is still provided, for use outside of method bodies (e.g. to conditionally omit whole methods or classes).

Conditional compilation is controlled by surrounding blocks of code with $IF/#if $ENDIF/#endif directives that check for specific conditions. These blocks can be nested within each other.

The $IF/if directive checks for the availability of one or more symbols, and begins a block that will conditionally be compiled if (and only if) the expression passed to the directive is found to be true (i.e. defined). Any block started with an "if" directive must be terminated with a matching $ENDIF/#endif directive to close it off (and can optionally also include additional $ELSE/#else sections to provide alternate blocks of code that will be considered if the original condition was not met).

New since Elements 5.2 release, the if (and elseif) directives accept not only a single symbol name to check for, but can also handle two or more symbols combined with the boolean logical operators (AND, OR, XOR and NOT in Oxygene, and &&, ||, ^ and ! in C#, Swift and Java). This allows for more complex checks against several symbols, without the need to awkwardly nest symbols.

In Oxygene, the $IF directive replaces the older $IFDEF and $IFNDEF directives, which are still supported, but considered legacy.

Examples:

{$IF COOPER} // Compile the following for Java only.
{$IF TOFFEE AND TARGET_OS_IPHONE} // Compile the following for Cocoa / iOS only.
{$IF ECHOES OR COOPER} // Compile the following for .NET and Java (but not Cocoa).
{$IF NOT TOFFEE} // Don't compile the following for Cocoa (but do for .NET and Java).
#if COOPER // Compile the following for Java only.
#if TOFFEE && TARGET_OS_IPHONE // Compile the following for Cocoa / iOS only.
#if ECHOES !! COOPER // Compile the following for .NET and Java (but not Cocoa).
#if !TOFFEE // Don't compile the following for Cocoa (but do for .NET and Java).
#if COOPER // Compile the following for Java only.
#if TOFFEE && TARGET_OS_IPHONE // Compile the following for Cocoa / iOS only.
#if ECHOES || COOPER // Compile the following for .NET and Java (but not Cocoa).
#if !TOFFEE // Don't compile the following for Cocoa (but do for .NET and Java).
#if COOPER // Compile the following for Java only.
#if TOFFEE && TARGET_OS_IPHONE // Compile the following for Cocoa / iOS only.
#if ECHOES || COOPER // Compile the following for .NET and Java (but not Cocoa).
#if !TOFFEE // Don't compile the following for Cocoa (but do for .NET and Java).

The "elseif" (or "elif" in C#) directive follows a previous "if" directive (and optional "elseif" directives). It closes the previous blocks and starts a new block of code that will be compiled if none of the previous conditions have been met and the condition provided in the directive itself is met.

"elseif"/"elif" allow the cascading of multiple cases, comparable to a case statement in regular code, without requiring a convoluted nesting of multiple "if"/"endif" directives.

Examples:

{$IF COOPER} // Compile the following for Java only.
{$ELSEIF ECHOES} // Compile the following for .NET only.
{$ELSEIF TOFFEE} // Compile the following for Cocoa only.
{$ELSE} // Compile if neither of the previous three were defined.
{$ENDIF} // Done.
#if COOPER // Compile the following for Java only.
#elif ECHOES // Compile the following for .NET only.
#elif TOFFEE // Compile the following for Cocoa only.
#else // Compile if neither of the previous three were defined.
#endif // Done.
#if COOPER // Compile the following for Java only.
#elseif ECHOES // Compile the following for .NET only.
#elseif TOFFEE // Compile the following for Cocoa only.
#else // Compile if neither of the previous three were defined.
#endif // Done.
#if COOPER // Compile the following for Java only.
#elif ECHOES // Compile the following for .NET only.
#elif TOFFEE // Compile the following for Cocoa only.
#else // Compile if neither of the previous three were defined.
#endif // Done.

The "else" directive follows a previous "if" directive (and optional "elseif"/"elif" directives). It closes the previous blocks and starts a new block of code that will be compiled if none of the previous conditions have been met. The block needs to be closed with a final "endif" directive.

Finally, the "endif" directive, as discussed in the previous sections, is used to close off a conditional section started with if. Afterwards, compilation will continue unconditionally (or based on any conditions set forth by a nested "if" directive) once again.

Within an ignored block (i.e. an if, elseif or else block that is not being compiled) all code and all compiler directives except if, else* and endif are ignored.

Oxygene Legacy Directives

  • {$IFDEF} — Legacy, use {$IF} instead.
  • {$IFNDEF} — Legacy, use {$IF NOT} instead.
  • {$IFOPT} — For Delphi compatibility, will always resolve as false.

Examples

begin
  {$IFDEF TRIAL}
  writeLn('This a trial version!');
  {$ELSE}
  writeLn('This is the full version');
  {$ENDIF}
{
  #if TRIAL
  Console.WriteLine("This a trial version!");
  #else
  Console.WriteLine("This is the full version");
  #endif
{
  #if TRIAL
  println("This a trial version!");
  #else
  println("This is the full version");
  #endif
{
  #if TRIAL
  Console.WriteLine("This a trial version!");
  #else
  Console.WriteLine("This is the full version");
  #endif
begin
  {$IFDEF ECHOES}
  writeLn('.NET');
  {$ELSEIF COOPER}
  writeLn('Java');
  {$ELSEIF TOFFEE AND TARGET_OS_IPHONE}
  writeLn('Cocoa on iOS');
  {$ELSEIF TOFFEE}
  writeLn('Cocoa and not iOS (i.e. OS X, tvOS or watchOS)');
  {$ELSE}
  writeLn("Some platform that hasn't been invented yet");
  {$ENDIF}
{
  #if ECHOES
  writeLn(".NET");
  #elif COOPER
  writeLn("Java");
  #elif TOFFEE && TARGET_OS_IPHONE}
  writeLn("Cocoa on iOS");
  #elif TOFFEE
  writeLn("Cocoa and not iOS (i.e. OS X, tvOS or watchOS)");
  #else
  writeLn("Some platform that hasn't been invented yet");
  #endif
{
  #if ECHOES
  writeLn(".NET");
  #elif COOPER
  writeLn("Java");
  #elif TOFFEE && TARGET_OS_IPHONE}
  writeLn("Cocoa on iOS");
  #elif TOFFEE
  writeLn("Cocoa and not iOS (i.e. OS X, tvOS or watchOS)");
  #else
  writeLn("Some platform that hasn't been invented yet");
  #endif
{
  #if ECHOES
  writeLn(".NET");
  #elif COOPER
  writeLn("Java");
  #elif TOFFEE && TARGET_OS_IPHONE}
  writeLn("Cocoa on iOS");
  #elif TOFFEE
  writeLn("Cocoa and not iOS (i.e. OS X, tvOS or watchOS)");
  #else
  writeLn("Some platform that hasn't been invented yet");
  #endif

Defining or Undefining Conditionals

The $DEFINE/#define directive defines a new symbol for the pre-processor; the defines are position dependent, so the symbol will only be defined for everything after the directive, in the same source file.

{$DEFINE TRIAL}
#define TRIAL
#define TRIAL
#define TRIAL

The $UNDEF/#undef directive removes a symbol for the pre-processor, if previously defined. Like "define", the directive is position dependent, so it will only affect code after the directive. Undefining can remove any pre-processor symbol, even ones defined by the compiler itself or the project options. When a symbol doesn't exist, the undefine will be ignored.

{$UNDEF TRIAL}
#undef TRIAL
#undef TRIAL
#undef TRIAL

See Also