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

时间:2013-10-21 06:56:56

标签: 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

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

for I := Low(aText) to High(aText) do

这样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

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

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

5 个答案:

答案 0 :(得分:5)

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

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

事实上,所有证据都指向移动编译器保留对基于一个字符串索引的支持。所有utility string functions like Pos use one based indices, and will continue to do so。如果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;

<强>更新

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

所以,我相信对于需要使用传统编译器以及移动编译器进行编译的代码,除了禁用之外别无选择。 ZEROBASEDSTRINGS

答案 1 :(得分:3)

为了向后兼容,所有RTL的预先存在的函数(Pos()Copy()等)仍然(并将保持)1。基于0的功能通过新的TStringHelper record helper that was introduced in XE3公开,旧的代码将不会使用,因此没有任何中断。

您必须注意的唯一真正的问题是硬编码索引,例如循环示例。遗憾的是,在旧的Delphi版本中无法访问Low/High(String),以可移植的方式编写此类代码的唯一方法是使用IFDEF s,例如:

{$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;

或者:

{$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;

条件表达式是在Delphi 6中引入的,因此如果您不需要支持早于Delphi 7的版本,并且不需要支持其他编译器(如FreePascal),那么您可以省略{$IFDEF CONDITIONALEXPRESSIONS}检查。

答案 2 :(得分: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;

有两种可能的解决方案:

一个。每次有字符串项访问或迭代时都会在其周围放置一个IFDEF,这对代码来说确实很麻烦,但无论ZEROBASEDSTRINGS设置它是什么都会正常工作:

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

B中。由于ZEROBASEDSTRINGS条件为per-block,它永远不会被第三方代码破坏,如果您不在代码中更改它,那么就可以了(上面StringLow将会很好地工作因为调用者代码具有相同的ZEROBASEDSTRINGS设置)。请注意,如果target是mobile,则不应在代码中全局应用ZEROBASEDSTRINGS OFF,因为RTL函数(例如TStringHelper)将返回基于0的结果,因为移动RTL是使用ZEROBASEDSTRINGS ON编译的。 / p>

旁注 - 有人可能建议为旧版本的Delphi编写Low/High的重载版本,但是Low(other type)(其中type是某些数组)停止工作。看起来好像Low/High不是通常的函数,因此不能简单地重载。

TL; DR - 使用自定义StringLow,不要在代码中更改ZEROBASEDSTRINGS

答案 3 :(得分:2)

如何将此定义为inc文件?根据您想要支持的Delphi版本添加额外的ifdef。由于此代码仅适用于ZBS之前的版本,因此可以在字符串上使用LowHigh,因此ZEROBASEDSTRINGS定义仅限于本地,因此不会遇到问题。

您可以在本地包含此代码(作为嵌套例程),这样可以降低与System.LowSystem.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}

答案 4 :(得分:1)

由于LU RD told above LowHigh字符串函数仅在XE3中引入。那么如何在早期的Delphi版本中使用函数呢?就像往常一样 - 如果错过了这个功能 - 去写吧!

您应该只使用条件编译激活超出XE3版本的Delphi的兼容性添加。使用&gt; =比较在其他答案中描述了一种方式。另一种常用方法是重用jedi.inc definitions file.

然后对于早期的Delphi版本,您将添加自己的实现,如

function Low(const S: AnsiString): integer; overload;

注意overload说明符 - 这是什么使得这个技巧成为可能,不要忘记它!

您必须为Delphi 7编写4个函数,直到2007年,涵盖Low/High fn name和AnsiString/WideString数据类型的组合。

对于Delphi 2009,直到XE2,您必须为UnicodeString数据类型添加另外两个函数。

并为那些支持它的Delphi版本标记那些函数inline(这是jedi.inc再次派上用场的地方。

希望你不需要UTF8String的supprot,但如果你这样做 - 你现在知道该怎么做(如果编译器在重载时会设法从AnsiString告诉它)。