从Delphi XE5开始,如何以向后兼容的方式使用基于0的字符串?

Kro*_*ica 18 delphi delphi-7 delphi-xe5

我正在尝试将我当前的Delphi 7 Win32代码转换为Delphi XE5 Android,只需要很少的更改,这样我的项目就可以从一系列Delphi版本和XE5的Android交叉编译到Win32.

从XE5开始,针对未来的语言会发生重大变化.其中一个变化是从零开始的字符串.

在具有基于1的字符串的旧版本中,以下代码是正确的:

function StripColor(aText: string): string;
begin
  for I := 1 to Length(aText) do
Run Code Online (Sandbox Code Playgroud)

但现在这显然不对.建议的解决方案是使用:

for I := Low(aText) to High(aText) do
Run Code Online (Sandbox Code Playgroud)

这样,XE5 Win32处理基于1的字符串,而XE5 Android处理基于0的字符串.但是有一个问题 - 以前的Delphi版本(例如XE2)在这样的代码上输出错误:

E2198 Low cannot be applied to a long string
E2198 High cannot be applied to a long string
Run Code Online (Sandbox Code Playgroud)

我有很多字符串操作代码.我的问题是 - 如何修改和保持上面的代码可以在Delphi 7 Win32和Delphi XE5 Android中编译?

PS我知道我仍然可以在XE5中禁用ZEROBASEDSTRINGS定义,但这是不受欢迎的解决方案,因为在XE6中,这个定义可能会消失,并且所有字符串都将被强制为0.

Dav*_*nan 5

如果要支持使用基于一个字符串的版本,请不要定义ZEROBASEDSTRINGS.这是有条件的目的.

没有迹象表明我知道条件将很快被删除.它是在XE3中引入的,并且在随后的两个版本中幸存下来.如果Embarcadero删除它,他们的Win32客户都不会升级,他们将破产.Embarcadero拥有保持兼容性的记录.您仍然可以使用TP对象和短字符串.只要桌面编译器可以使用,就可以使用此条件.

实际上,所有证据都指向移动编译器保留对基于一个字符串索引的支持.所有实用程序字符串函数都Pos使用一个基于索引,并将继续这样做.如果Embarcadero真的要删除对基于一个字符串索引的支持,他们也将删除Pos.我不相信这很快就会发生.

尽管编写返回字符串的低和高索引的函数是微不足道的.您只需IFDEF在编译器版本上使用.

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;
Run Code Online (Sandbox Code Playgroud)

更新

正如雷米指出的那样,上面的代码并不好.那是因为ZEROBASEDSTRINGS是本地的,重要的是它在使用这些功能的地方的状态.事实上,以有意义的方式实现这些功能是不可能的.

因此,我认为对于需要使用传统编译器以及移动编译器进行编译的代码,您别无选择,只能禁用.ZEROBASEDSTRINGS.

  • @DavidHeffernan:另外,请记住`ZEROBASEDSTRINGS`是一个'per-block`条件,而不是每个单元条件或每个项目条件,所以你的`StrLow/High()`方法不能保证工作.想一想.如果包含`strLow/High()`的**实现**的单元有'ZEROBASEDSTRINGS`,但**调用**`StrLow/High()`的单元有'ZEROBASEDSTRINGS`,那么你将返回错误的值. (5认同)
  • @DavidHeffernan:我不知道它是否可以与内联一起使用。试试看。但是您知道内联只是一个提示,对吧?如果编译器选择不内联代码,则没有义务或不能保证实际上内联代码。 (2认同)

Kro*_*ica 2

这是两个答案的总结:

正如 Remy Lebeau 所指出的,ZEROBASEDSTRINGS是每个块的条件。这意味着以下代码将无法按预期工作

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;
Run Code Online (Sandbox Code Playgroud)

有两种可能的解决方案:

答:每次有字符串项访问或迭代时IFDEF,都会在其周围放置一个,这确实会让代码变得很混乱,但无论如何ZEROBASEDSTRINGS设置它都会正常工作:

for I := {$IFDEF XE3UP}Low(aText){$ELSE}1{$ENDIF} to {$IFDEF XE3UP}High(aText){$ELSE}Length(aText){$ENDIF} do
Run Code Online (Sandbox Code Playgroud)

B. 由于ZEROBASEDSTRINGS条件是per-block它永远不会被第 3 方代码破坏,如果您不在代码中更改它,那就没问题(StringLow只要调用者代码具有相同的ZEROBASEDSTRINGS设置,上述内容就可以正常工作)。请注意,如果目标是移动设备,则不应ZEROBASEDSTRINGS OFF在代码中全局应用,因为 RTL 函数(例如TStringHelper)将返回基于 0 的结果,因为移动 RTL 是使用ZEROBASEDSTRINGS ON.

附带说明- 有人可能建议Low/High为旧版本的 Delphi 编写一个重载版本,但随后Low(other type)(其中类型是某种数组)停止工作。看起来好像自从Low/High不是常用的函数,那么就不能那么简单地重载。

TL;DR - 使用自定义StringLow并且不要更改ZEROBASEDSTRINGS代码。