How can i detect specific RTL features at compile time? - delphi

For sake of example lets check for infamous TStrings.StrictDelimiter:
{$IF Declared(TStrings.StrictDelimiter)}
{$MESSAGE WARN 'Beware of TStrings.StrictDelimiter which is False by default!'}
{$IFEND}
However, Declared compiler intrinsic reports syntax error on conditional line: E2029 ')' expected but '.' found. (tested on XE)

For Delphi XE2 I'm using this :
{$IFDEF BDS9}
Result.VersionString := 'Delphi XE2 ' +
{$IF NOT DECLARED(Consts.SStyleFeatureNotSupported)}
'(original release version)'
{$ELSE} {$IF NOT DECLARED(FireMonkeyVersion)} // D2010 chokes when scope (FMX.Types) is mentioned!
'Update 1'
{$ELSE} {$IF NOT DECLARED(System.TestSSE)}
'Update 2'
{$ELSE}
'Update 3'
// TODO : Update this for any following update!
{$IFEND} {$IFEND} {$IFEND}
;
{$ELSE}
{$IFDEF BDS7}
Result.VersionString := 'Delphi 2010';
{$ELSE}
{$MESSAGE ERROR 'Extend this!'}
{$ENDIF}
{$ENDIF}
In other words : I test for the existence of symbols that are introduced since any particular delphi-version. The same construct can be used to set a variable or constant or compiler define, so further code can use these instead.
Note : I keep a backup around of the Source folder for all Delphi versions that I've had installed in the past few years. Putting these folders through a tool like BeyondCompare and browsing through the differencing files, will quickly give you a few symbols that you can test for....

Unfortunately this kind of expressions aren't supported, you have to know in which RTL / compiler version some feature was introduced and then use predefined conditional symbols like VER<nnn>, RTLVersion, CompilerVersion etc.

Related

How to conditionally compile a newer Indy feature?

I've already found this answer on how to check the Indy version at run-time, and there are multiple different ways. However I'm looking how to use conditionals to check the Indy version at compile-time. There's a feature in newer versions of Indy, and I want my open-source project to use this feature if it's available. But I need to conditionally compile it.
I've found IdVers.inc, but this file only contains constants - no version conditionals.
More specifically, the TIdHTTP has a property HTTPOptions which has a new choice hoWantProtocolErrorContent. If this is available, I'd like to use it.
How can I conditionally use this option if it's available?
I think you can get the result you're wanting to achieve using the
{$if declared ...
construct. There is an example of its usage in SysInit.Pas in the rtl:
function GetTlsSize: Integer;
{$IF defined(POSIX) and defined(CPUX86) and (not defined(EXTERNALLINKER))}
asm
// Use assembler code not to include PIC base gain
MOV EAX, offset TlsLast
end;
{$ELSE}
begin
Result := NativeInt(#TlsLast);
{$IF DECLARED(TlsStart)}
Result := Result - NativeInt(#TlsStart);
{$ENDIF}
[...]
As well as the article I mentioned in a comment, $If Declared,
there is also this in the D2009 online help.
$if declared works with methods of classes, e.g.
procedure TMyClass.DoSomething;
begin
{$if declared(TMyClass.Added)} // Added being a procedure of TMyClass
Added;
{$endif}
end;

Why doesn't code from "The Tomes of Delphi" compile?

I'm trying to use the TDRecLst and TDSplyCm units from the code included with The Tomes of Delphi, but I get a compiler error in TDBasics.pas:
I get a similar error in TDStrRes.inc:
What's wrong, and how do I fix it?
The code is available from the author.
You're evidently using a Delphi version that's newer than Delphi 6. Despite being updated in 2005, the code from that book only detects up to that version of Delphi. TDDefine.inc defines a number of compiler symbols based on the version it detects, but when the version you're using isn't anything it recognizes, it defines no symbols. That eventually leads to problems later when the compiler encounters code like this in TDBasics.pas;
implementation
uses
{$IFDEF Delphi1}
WinTypes, WinProcs;
{$ENDIF}
{$IFDEF Delphi2Plus}
Windows;
{$ENDIF}
{$IFDEF Kylix1Plus}
Types, Libc;
{$ENDIF}
{$IFDEF Delphi1}
{$R TDStrRes.r16}
{$ENDIF}
{$IFDEF Delphi2Plus}
{$R TDStrRes.r32}
{$ENDIF}
{$IFDEF Kylix1Plus}
{$R TDStrRes.r32}
{$ENDIF}
const
UnitName = 'TDBasics';
Since none of Delphi1, Delphi2Plus, or Kylix1Plus is defined, the uses clause is empty. When we ignore all the compiler directives and inactive code blocks, the compiler ultimately sees code like this:
implementation
uses
const
UnitName = 'TDBasics';
That's why the compiler complains about expecting an identifier instead of const.
To fix it, you need to teach TDDefine.inc to recognize your version of Delphi. Easier, though, might be to ignore all the version-detection code and hard-code all the symbols that apply to the version you're using. As long as you never use any version older than Delphi 6, all the symbols will apply to all your versions.
Find the following block of code in TDDefine.pas:
{$IFDEF VER140}
{$DEFINE Delphi6}
{$DEFINE Delphi1Plus}
{$DEFINE Delphi2Plus}
{$DEFINE Delphi3Plus}
{$DEFINE Delphi4Plus}
{$DEFINE Delphi5Plus}
{$DEFINE Delphi6Plus}
{$DEFINE HasAssert}
{$ENDIF}
Remove the first and last lines so that the remaining $DEFINE instructions are processed unconditionally.

Are conditional expressions broken within packages?

Consider the following snippet:
requires
designide,
rtl,
vcl,
{$IF RTLVersion < 19.0} // E2026 Constant expression expected
//{$IF CompilerVersion = 22.0} // same as above
vcljpg;
{$ELSE}
vclimg;
{$IFEND}
It seems to be absolutely syntactically correct. However, the compiler chokes on it and reports Constant expression expected. What really happens here?
Technical: currently tested on XE (15.0.3953.35171) only.
Of course, workaround suggestions are welcome too.
I found the same issue in the past even with delphi 2007. As workaround, I use a inc file with the conditional defines and then use {$IFDEF} instead of {$IF}
something like so
{$I MyDefines.INC}
requires
designide,
rtl,
vcl,
{$IFDEF DELPHI_XE_UP} //the DELPHI_XE_UP is defineed inside of MyDefines.INC
uNewlib;
{$ELSE}
uOldLib;
{$ENDIF}
package modules are different from program and library modules. They do not contain executable code and you cannot use units. Therefore, symbols like RTLVersion are simply not visible from a package file. Your only option is to use $IFDEF.
I'm convinced what i just found the cause. Consider the following:
{$IF not Declared(RTLVersion)}
{$MESSAGE WARN 'There is no RTL'}
{$IFEND}
{$IF not Declared(CompilerVersion)}
{$MESSAGE WARN 'nor are compiler intrinsics at all'}
{$IFEND}
{$IF not Declared(System)}
{$MESSAGE ERROR 'Because package not uses System implicitly'}
{$IFEND}
So, it appears to be what compiler behaves correctly, but issues a rather misleading (if not erroneous) message about symbol not being a constant expression, while symbol in question actually is undeclared!

Can I generate a custom compiler error? If so, how?

Here is what I want to do. I have a project that must be compiled in some version of Delphi or later. I would like to use a conditional compiler directive to test the Delphi version, and then cause a custom compiler error to be generated with a custom message. Being able to generate a custom compiler warning or hint would also be adaquate if an error is not possible.
Sure, I could put some un-compilable giberish in the conditional code segment, and that's fine. But my question is "Can I generate, conditionally, a custom compiler error?"
Thank you Johan and Serg.
Here is the solution, and more details about the issue. I have an application that was originally built in Delphi 2007. It includes Internet Direct components to attach to a Web service. These use SSL. I recently upgraded my SSL libraries to a later version, and these don't play so well with the Delphi 2007 Indy components. I have now added the following compiler directives to ensure that this application will no longer be compiled with Delphi 2007 or earlier:
{$IF CompilerVersion <= 19.0} // Delphi 2007 = 19.0
{$MESSAGE Error 'This project must be compiled in Delphi 2009 or later'}
{$IFEND}
You can use:
{$Message HINT|WARN|ERROR|FATAL 'text string' }
{$MESSAGE 'Boo!'} emits a hint
{$Message Hint 'Feed the cats'} emits a hint
{$messaGe Warn 'Looks like rain.'} emits a warning
{$Message Error 'Not implemented'} emits an error, continues compiling
{$Message Fatal 'Bang. Yer dead.'} emits an error, terminates compiler
See: http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devcommon/compdirsmessagedirective_xml.html
This works in Delphi 6 and later.
Checking the Delphi version has become easy since CONDITIONALEXPRESSIONS directive was introduced in Delphi 6:
program requires2010;
{$APPTYPE CONSOLE}
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 21.0} // 21.0 is Delphi 2010
{$DEFINE DELPHI2010}
{$IFEND}
{$ENDIF}
begin
{$IFNDEF DELPHI2010}
{$MESSAGE Fatal 'Wrong Delphi Version'}
{$ENDIF}
Writeln('Continued');
Readln;
end.

{$IFOPT A4}?

In Delphi 2009 (or older versions), how do you check the "Align" compile option in the code?
The IFOPT directive seems to work only with pure switches ( {$IFOPT A4} does not compile ).
I couldn't find an equivalent constant or such defined ( {$IF Align = 4} or such )
You can do this by defining a record with known packing rules and check it using SizeOf. Tested in Delphi 2009:
type
TTestRec = record
A: Byte;
B: Int64;
end;
{$IF SIZEOF(TTestRec) = 9}
{$MESSAGE HINT '$A1'}
{$ELSEIF SIZEOF(TTestRec) = 10}
{$MESSAGE HINT '$A2'}
{$ELSEIF SIZEOF(TTestRec) = 12}
{$MESSAGE HINT '$A4'}
{$ELSEIF SIZEOF(TTestRec) = 16}
{$MESSAGE HINT '$A8'}
{$ELSE}
{$MESSAGE HINT 'Unknown alignment'}
{$IFEND}
Write code to test the actual runtime behavior. Only way I can think of.
There is {$IFOPT A+} directive, but it doesn't tell you alignment value.
I believe there is no way to do this :(

Resources