Tuesday, 2 October 2012

New compiler directives in Delphi XE3

There are a few changes/additions to the compiler in Delphi XE3. One of them is record helpers, much the same as class helpers but for records [UPDATE: actually the new thing about record helpers is mentioned a little further below]. This has been discussed and demonstrated on a few sites, such as Zarko Gajic’s delphi.about.com, Marco Cantù’s blog and Alister Christie’s LearnDelphi.tv.

These look quite interesting, but primarily from what Embo’s R&D will use them for rather than what you can use them for. I say this because the primary drawback of class or record helpers is that only one can be active on a class or record at a time, and it’s the last one found in scope.

Record helpers are poorly named really, as they can be used to add methods to primitive types [UPDATE: this is what is new in XE3 in relation to record helpers]. This is what TStringHelper does – it adds in a bunch of functions and properties to the basic string type, which incidentally are all 0-based in their behaviour, as opposed to regular string routines, which are all 1-based.

If you check in System.SysUtils.pas you will see that the way that these record helper methods manage to work zero-based is not by a lot of manual index offsetting in the code, but by a new and undocumented compiler directive that’s used around the implementation: {$ZEROBASEDSTRINGS ON}. [Edit: You can read more on this in Mark Edington’s October blog post].

Browsing through more of the XE3 source I noticed that the rules for $if have changed. All the cases of $if I bumped into are terminated by $endif, rather than $ifend as has previously been the case since its introduction in Delphi 6. A quick check shows that $if can now be closed with either $ifend or $endif. At the time of writing the documentation for $IF does not indicate this new flexibility so some investigation was required.

It turns out that there is another new (and currently undocumented) compiler directive that I was informed about, and then found had already cropped up in a discussion on the Embo fora. The directive is $LEGACYIFEND and the default state of this conditional is {$LEGACYIFEND OFF}. Using {$LEGACYIFEND ON} will take things back to how they were, forcing $IF to be closed by $IFEND and $IFDEF to be closed by $ENDIF.

If using the command-line compiler the --legacy-ifend command-line switch will also bring back the compiler behaviour of Delphi XE2 with regard to closing a $IF conditional compilation block.

So, to recap, this works in Delphi XE3 but does not compile in Delphi XE2 or earlier:

{$if defined(VER240)}
procedure foo;
begin
//blah blah
end;
{$endif}

but this throws up the same error in Delphi XE3 as it does in XE2:

{$ifdef CONDITIONALEXPRESSIONS}
  {$if CompilerVersion >= 24.0}
    {$LEGACYIFEND ON}
  {$ifend}
{$endif}

{$if defined(VER240)}
procedure foo;
begin
//blah blah
end;
{$endif}

Oh, but talking of compiler directives something else cropped up. As is implied in the snippet just above Delphi XE3 adds in the new VER240 conditional define (there’s a list of these tucked away on this blog). This is present and correct in the compiler version table in Embo’s doc wiki, but hasn’t yet made it into the conditional defines page, though I’m sure that’s just a matter of time. However, the point I was getting round to is to emphasise something that can be found in a comment in the RTL’s System unit source code:

CompilerVersion is assigned a value by the compiler when the system unit is compiled.  It indicates the revision level of the compiler features / language syntax, which may advance independently of the RTLVersionCompilerVersion can be tested in $IF expressions and should be used instead of testing for the VERxxx conditional define. Always test for greater than or less than a known revision level. It's a bad idea to test for a specific revision level.

The code above therefore shows the approved way of checking for Delphi XE3 or higher, using CompilerVersion (see here for a list of these values), as well as the wrong way to check for Delphi XE3, using VER240.

Back to $IF/$ENDIF, it should be noted that ErrorInsight has not been updated to be aware of this new flexibility for $IF, so if you use the new pairing to set up a conditional compilation block it will flag up an error in ErrorInsight and give a red squiggle in the source editor. This has already been reported as a bug.