Delphi: ZEROBASEDSTRINGS and maintaining cross-version Delphi libraries
Posted by jpluimers on 2015/01/14
One of the features that bites me over and over again is the ZEROBASEDSTRINGS that got introduced in Delphi XE3 and is by default ON in mobile compilers and OFF in Desktop compilers.
Back then, Mark Edington showed a small example of the effects:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
procedure ZeroBasedTest; | |
const | |
S: string = '012'; | |
begin | |
{$ZEROBASEDSTRINGS OFF} | |
Writeln(S[1]); // shows "0" | |
Writeln(S.Chars[1]); // shows "1" | |
{$ZEROBASEDSTRINGS ON} | |
Writeln(S[1]); // shows "1" | |
Writeln(S.Chars[1]); // shows "1" | |
end; |
and then explained:
The XE3 RTL source code has been refactored to be string index base agnostic. In most cases this is done by utilizing string helper functions which are always zero based.
When it is necessary to traverse a string, the Char[] property is often used to access the individual characters without concern for the current state of the compiler with respect to zero based strings.In addition, the “Low” and “High” standard functions can now be passed a string variable to provide further flexibility as needed.
When zero based strings are enabled, Low(string) will return 0, otherwise it will return 1. Likewise, High() returns a bounds adjusted length variation.
The problem is the non-existent forward compatibility of the other compilers (Delphi XE2 and lower).
So if you have library code that needs to work in Delphi versions, you cannot use the High and Low to make the code ZEROBASEDSTRINGS neutral.
Many Delphi developers regularly skip many Delphi versions, so these are still popular:
- Delphi XE1 and XE2 (the last 2 compilers before Delphi really started to support mobile)
- Delphi 2007 (the last non-Unicode Delphi compiler)
- Delphi 7 (the last non-Galileo IDE)
The result is that library code is full of conditionan IF/IFDEF blocks like these:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{$ifdef GX_VER240_up} | |
for Index := Low(input) to High(input) do // for ZEROBASEDSTRINGS | |
{$else} | |
for Index := 1 to Length(input) do | |
{$endif GX_VER240_up} |
–jeroen
via: Mark Edington’s Delphi Blog : XE3 RTL Changes: A closer look at TStringHelper.
John said
ZEROBASEDSTRINGS made existing Delphi libraries pointless for future mobile developments, and made Delphi pointless as well.
Existing codebase was the only Delphi advantage: the compiler is outclassed, the new IDE is slow and ridden with bugs, and so are the new libraries. It’s simpler to start from scratch on a mainstream development language.
ZEROBASEDSTRINGS, Just Don’t | Words from a Delphi CodeSmith said
[…] me, but here is a case where Embarcadero seems to be out to get everyone. Jeroen provides an excellent little blog post about it and I recommend you read that first to understand the […]
Thomas Mueller said
Of course you to abstract from these differences by implementing functions like
function StrLow(const _s: string);
begin
{$ifdef GX_VER240_up}
Result := Low(_s); // for ZEROBASEDSTRINGS
{$else}
Result := 1;
{$endif GX_VER240_up}
end;
function StrHigh(const _s: string);
begin
{$ifdef GX_VER240_up}
Result := High(_s); // for ZEROBASEDSTRINGS
{$else}
Result := Length(_s);
{$endif GX_VER240_up}
end;
and using them as
for Index := StrLow(input) to StrHigh(input) do
reducing the ifdef pain to these two functions.
sglienke said
That is a wrong assumption as pointed out here: http://stackoverflow.com/a/19510379/587106
Krom Stern said
My research on the topic: http://stackoverflow.com/questions/19488010/how-to-work-with-0-based-strings-in-a-backwards-compatible-way-since-delphi-xe5
sglienke said
Or you just write {$ZEROBASEDSTRINGS OFF}.
HeartWare said
I use these two routines. They can easily be adapted to handle earlier versions of the compiler:
FUNCTION GetChar(CONST S : String ; OneBasedIndex : LongWord) : CHAR;
BEGIN
IF LOW(STRING)=0 THEN Result:=S[PRED(OneBasedIndex)] ELSE Result:=S[OneBasedIndex]
END;
PROCEDURE SetChar(VAR S : String ; OneBasedIndex : LongWord ; C : CHAR);
BEGIN
IF LOW(STRING)=0 THEN S[PRED(OneBasedIndex)]:=C ELSE S[OneBasedIndex]:=C
END;
This way, I isolate the various implementation details to these two routines.
gabr42 said
Agreed. ZEROBASEDSTRINGS was the most stupid addition to the Delphi language. Ever. Even worse than the ‘with’ statement.