I\'m trying to convert my current Delphi 7 Win32 code to Delphi XE5 Android with minimal changes, so that my project can be cross-compiled to Win32 from a range of Delphi versio
As LU RD told above Low
and High
functions for string were only introduced in XE3. So how can you use functions in earlier Delphi verions, that are missed? Just the same way as always do - if the function is missed - go and write it!
You should only activate those compatibility additions for Delphi beyond XE3 version, using conditional compilation. One way is described in other answers, using >= comparison. Another usual way would be reusing jedi.inc definitions file.
Then for earlier Delphi versions you would add your own implementations of those, like
function Low(const S: AnsiString): integer; overload;
Pay attention to the overload
specifier - it is what would make the trick possible, don't forget it!
You would have to write 4 functions for Delphi 7 till 2007, covering combinations of Low/High
fn name and AnsiString/WideString
data type.
For Delphi 2009 till XE2 you would have to add two more functions for UnicodeString
datatype.
And also mark those function inline
for those Delphi versions, that support it (this is where jedi.inc
comes handy again.
Hopefully you don't need supprot for UTF8String
, but if you do - you know what to do about it now (if compiler would manage to tell it from AnsiString when overloading...)
If you want to support versions that use one based strings then don't define ZEROBASEDSTRINGS
. That's the purpose of that conditional.
There's no indication that I am aware of that the conditional will be removed any time soon. It was introduced in XE3 and has survived two subsequent releases. If Embarcadero remove it, none of their Win32 customers will not upgrade and they will go bust. Embarcadero have a track record of maintaining compatibility. You can still use TP objects and short strings. Expect this conditional to live as long as the desktop compiler does.
In fact, all the evidence points towards the mobile compilers retaining support for one based string indexing. All the utility string functions like Pos use one based indices, and will continue to do so. If Embarcadero really are going to remove support for one based string indexing, they'll be removing Pos
too. I don't believe that is likely any time soon.
Taking your question at face value though it is trivial to write functions that return the low and high indices of a string. You just use an IFDEF
on the compiler version.
function StrLow(const S: string): Integer; inline;
begin
Result := {$IFDEF XE3UP}low(S){$ELSE}1{$ENDIF}
end;
function StrHigh(const S: string): Integer; inline;
begin
Result := {$IFDEF XE3UP}high(S){$ELSE}Length(S){$ENDIF}
end;
Update
As Remy points out, the above code is no good. That's because ZEROBASEDSTRINGS
is local and what counts is its state at the place where such functions would be used. In fact it's just not possible to implement these functions in a meaningful way.
So, I believe that for code that needs to be compiled using legacy compilers, as well as the mobile compilers, you have little choice but to disable. ZEROBASEDSTRINGS
.
All of the RTL's pre-existing functions (Pos()
, Copy()
, etc) are still (and will remain) 1-based for backwards compatibility. 0-based functionality is exposed via the new TStringHelper record helper that was introduced in XE3, which older code will not be using so nothing breaks.
The only real gotchas you have to watch out for are things like hard-coded indexes, such as your loop example. Unfortunately, without access to Low/High(String)
in older Delphi versions, the only way to write such code in a portable way is to use IFDEF
s, eg:
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 24}
{$DEFINE XE3_OR_ABOVE}
{$IFEND}
{$ENDIF}
function StripColor(aText: string): string;
begin
for I := {$IFDEF XE3_OR_ABOVE}Low(aText){$ELSE}1{$ENDIF} to {$IFDEF XE3_OR_ABOVE}High(AText){$ELSE}Length(aText){$ENDIF} do
DoSomething(aText, I);
end;
Or:
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF CompilerVersion >= 24}
{$DEFINE XE3_OR_ABOVE}
{$IFEND}
{$ENDIF}
function StripColor(aText: string): string;
begin
for I := 1 to Length(aText) do
begin
DoSomething(aText, I{$IFDEF XE3_OR_ABOVE}-(1-Low(AText)){$ENDIF});
end;
end;
Conditional Expressions were introduced in Delphi 6, so if you don't need to support version earlier than Delphi 7, and don't need to support other compilers like FreePascal, then you can omit the {$IFDEF CONDITIONALEXPRESSIONS}
check.
This is rather a sum up of the two answers:
As pointed out by Remy Lebeau, ZEROBASEDSTRINGS
is a per-block conditional. That means that the following code will not work as expected:
const
s: string = 'test';
function StringLow(const aString: string): Integer; inline; // <-- inline does not help
begin
{$IF CompilerVersion >= 24}
Result := Low(aString); // Delphi XE3 and up can use Low(s)
{$ELSE}
Result := 1; // Delphi XE2 and below can't use Low(s), but don't have ZEROBASEDSTRINGS either
{$ENDIF}
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
{$ZEROBASEDSTRINGS OFF}
Memo1.Lines.Add(Low(s).ToString); // 1
Memo1.Lines.Add(StringLow(s).ToString); // 1
{$ZEROBASEDSTRINGS ON}
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
{$ZEROBASEDSTRINGS ON}
Memo1.Lines.Add(Low(s).ToString); // 0
Memo1.Lines.Add(StringLow(s).ToString); // 1 <-- Expected to be 0
{$ZEROBASEDSTRINGS OFF}
end;
There are 2 possible solutions:
A. Every time there's string items access or iteration place an IFDEF
around it, which is indeed a lot of clutter for the code, but will work properly irregardless of ZEROBASEDSTRINGS
setting around it:
for I := {$IFDEF XE3UP}Low(aText){$ELSE}1{$ENDIF} to {$IFDEF XE3UP}High(aText){$ELSE}Length(aText){$ENDIF} do
B. Since the ZEROBASEDSTRINGS
conditional is per-block
it never gets spoiled by 3rd party code and if you don't change it in your code you are fine (above StringLow
will work fine as long as the caller code has the same ZEROBASEDSTRINGS
setting). Note that if target is mobile, you should not apply ZEROBASEDSTRINGS OFF
globally in your code since RTL functions (e.g. TStringHelper
) will return 0-based results because mobile RTL is compiled with ZEROBASEDSTRINGS ON
.
On a side note - One might suggest to write an overloaded versions of Low/High
for older versions of Delphi, but then Low(other type)
(where type is array of something) stops working. It looks like since Low/High
are not usual functions then can not be overloaded that simply.
TL;DR - Use custom StringLow
and don't change ZEROBASEDSTRINGS
in your code.
How about defining this as an inc file? Put additional ifdefs depending on what Delphi versions you want to support. Since this code is only for versions before the ZBS to make it possible to use Low
and High
on strings it will not run into the problem with the ZEROBASEDSTRINGS
define only being local.
You can include this code locally (as nested routines) then which reduces the risk of colliding with System.Low
and System.High
.
{$IF CompilerVersion < 24}
function Low(const s: string): Integer; inline;
begin
Result := 1;
end;
function High(const s: string): Integer; inline;
begin
Result := Length(s);
end;
{$IFEND}