Как работать со строками на основе 0 обратно совместимым способом после Delphi XE5?

18

Я пытаюсь преобразовать мой текущий код Delphi 7 Win32 в Delphi XE5 Android с минимальными изменениями, чтобы мой проект можно было кросс-компилировать в Win32 из ряда версий Delphi и Android из XE5.

Начиная с 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, а Android XE5 обрабатывает строки на основе 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. Я знаю, что все еще могу отключить определение ZEROBASEDSTRINGS в XE5, но это нежелательное решение, так как в XE6 это определение, вероятно, исчезнет, и все строки будут вынуждены быть основаны на 0.

    
задан Kromster 21.10.2013 в 08:56
источник
  • @ David: Серьезно, все мы видели графики, по которым наиболее популярны версии Delphi. Delphi 7 все еще находится рядом с вершиной;) Поскольку я работаю над проектом с открытым исходным кодом, я хочу, чтобы код был максимально совместим (включая Lazarus). –  Kromster 21.10.2013 в 09:07
  • Я считаю, что для Delphi-7 вы можете писать свои собственные функции и использовать их прозрачно. но я не имею D7 и не могу проверить. функция Low (const S: AnsiString): integer; перегрузки; begin ... end; - Вам понадобится 4 функции: Низкий / Высокий и AnsiString / WideString - и я надеюсь, что ключевое слово перегрузки сделало бы трюк, так что у вас будет как запас Low / High для массивов, так и ваш собственный Low / High для строк –  Arioch 'The 21.10.2013 в 21:38
  • @ Arioch'The: да, сделайте свое предложение в качестве ответа. –  Remy Lebeau 22.10.2013 в 03:09
  • @LURD нет, для Delphi 2009-XE5 он должен просто использовать встроенные функции и не объявлять свои собственные –  Arioch 'The 22.10.2013 в 09:28
  • @ Arioch'The: Я тестировал, и кажется, что Low / High не может быть перегружен так. –  Kromster 22.10.2013 в 10:42
Показать остальные комментарии

5 ответов

2

Это скорее сумма двух ответов:

Как отметил Реми Лебо, 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;

Есть 2 возможных решения:

а. Каждый раз, когда есть доступ к строковым элементам или итерация, поместите вокруг него IFDEF , что действительно является большим беспорядком для кода, но будет работать правильно, независимо от установки ZEROBASEDSTRINGS вокруг него:

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

В. Так как условием ZEROBASEDSTRINGS является per-block , оно никогда не будет испорчено сторонним кодом, и если вы не измените его в своем коде, у вас все хорошо (выше StringLow будет работать нормально, если код вызывающей стороны имеет такой же% настройка co_de%). Обратите внимание, что если target является мобильным, вы не должны применять ZEROBASEDSTRINGS глобально в своем коде, поскольку функции RTL (например, ZEROBASEDSTRINGS OFF ) будут возвращать результаты на основе 0, поскольку мобильный RTL компилируется с TStringHelper .

На заметку . Кто-то может предложить написать перегруженные версии ZEROBASEDSTRINGS ON для более старых версий Delphi, но тогда Low/High (где тип является массивом чего-либо) перестает работать. Похоже, что Low(other type) не являются обычными функциями, поэтому их нельзя просто перегрузить.

TL; DR - используйте пользовательский Low/High и не изменяйте StringLow в своем коде.

    
ответ дан Krom Stern 23.10.2013 в 09:58
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;

Обновление

Как указывает Реми, приведенный выше код не годится. Это связано с тем, что ZEROBASEDSTRINGS является локальным, и что важно, так это его состояние в месте, где будут использоваться такие функции. На самом деле просто невозможно реализовать эти функции осмысленно.

Итак, я считаю, что для кода, который должен быть скомпилирован с использованием устаревших компиляторов, а также мобильных компиляторов, у вас мало выбора, кроме как отключить. ZEROBASEDSTRINGS .     

ответ дан David Heffernan 21.10.2013 в 09:10
  • Дело в том, что у мобильных компиляторов, вероятно, есть строки на основе 0 с XE6. Таким образом, чтобы иметь тот же код для мобильных устройств и Win32, должен быть способ обработки строк на основе 0 и 1 с одним и тем же кодом. –  Kromster 21.10.2013 в 09:13
  • Действительно. С этим было официальное заявление? У вас есть ссылка? –  David Heffernan 21.10.2013 в 09:15
  • Официальный совет Embarcadero рекомендует переписывать 1-основанный код в 0-based для мобильных платформ (docwiki.embarcadero.com/RADStudio/XE4/en/... 0-строка). Я сомневаюсь, что они сделали бы 0-based только для того, чтобы сохранить его в качестве варианта, не имея планов по его обеспечению в будущем. –  Kromster 21.10.2013 в 09:26
  • @DavidHeffernan: Также имейте в виду, что ZEROBASEDSTRINGS является условным условным, а не условным условным или условным, для каждого блока, поэтому ваш метод StrLow / High () не гарантированно работает. Думаю об этом. Если блок, содержащий реализацию StrLow / High (), имеет ZEROBASEDSTRINGS, но единица, которая вызывает StrLow / High (), отключена ZEROBASEDSTRINGS, тогда вы будете возвращать неправильные значения. –  Remy Lebeau 21.10.2013 в 21:01
  • @DavidHeffernan: Я не знаю, будет ли это работать с inlining. Попробуй и посмотри. Но вы знаете, что вложение - это лишь намек, верно? Компилятор не обязан или не гарантирует фактическое включение кода, если он решил не делать этого. –  Remy Lebeau 21.10.2013 в 21:27
Показать остальные комментарии
3

Все ранее существующие функции RTL ( Pos() , Copy() и т. д.) по-прежнему (и останутся) основаны на 1 для обратной совместимости. Функциональность, основанная на 0, предоставляется с помощью нового помощника по записи TStringHelper , который был представлен в XE3 , какой старый код будет не использовать, чтобы ничего не ломалось.

Единственные реальные ошибки, на которые вы должны обратить внимание, - это такие вещи, как жестко закодированные индексы, такие как пример вашего цикла. К сожалению, без доступа к Low/High(String) в более старых версиях Delphi единственный способ написать такой код в переносимом режиме - это использовать 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} .

    
ответ дан Remy Lebeau 21.10.2013 в 21:10
  • Я не сказал, что это будет довольно, только функционально. И я не думал о перегрузке Low / High () в то время. –  Remy Lebeau 22.10.2013 в 03:06
2

Как насчет определения этого как inc-файла? Добавьте дополнительные ifdef в зависимости от того, какие версии Delphi вы хотите поддерживать. Поскольку этот код предназначен только для версий до ZBS, чтобы можно было использовать Low и High в строках, он не столкнется с проблемой, поскольку определение ZEROBASEDSTRINGS является только локальным.

Вы можете включить этот код локально (как вложенные подпрограммы), что снижает риск столкновения с System.Low и 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}
    
ответ дан Stefan Glienke 08.05.2014 в 15:34
1

Как LU RD как сказано выше Функции Low и High для строки были введены только в XE3. Итак, как вы можете использовать функции в более ранних версиях Delphi, которые пропущены? Точно так же, как и всегда - если функция пропущена - иди и напиши ее!

Вы должны активировать эти дополнения совместимости только для Delphi после версии XE3, используя условную компиляцию. Один способ описан в других ответах, используя > = сравнение. Другим обычным способом было бы повторно использовать файл определений jedi.inc .

Тогда для более ранних версий Delphi вы бы добавили свои собственные реализации таких, как

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

Обратите внимание на спецификатор overload - это то, что сделает трюк возможным, не забывайте об этом!

Вы должны написать 4 функции для Delphi 7 до 2007 года, включая комбинации типа данных Low/High fn и AnsiString/WideString .

Для Delphi 2009 до XE2 вам нужно добавить еще две функции для типа данных UnicodeString .

А также пометьте те функции inline для тех версий Delphi, которые его поддерживают (вот где jedi.inc снова пригодится.

Надеюсь, вам не нужен supprot для UTF8String , но если вы это сделаете - вы знаете, что с этим делать сейчас (если компилятору удастся сообщить об этом из AnsiString при перегрузке ...)

    
ответ дан Arioch 'The 22.10.2013 в 10:09
  • Я тестировал это, и Low (type) (где type - массив чего-то) перестало работать. Похоже, что Low / High не являются обычными функциями и не могут быть просто перегружены. –  Kromster 22.10.2013 в 10:38
  • @KromStern, да. Перегрузка скрывает другие внутренние функции Low / High. Они должны быть квалифицированы с помощью System.Low. Делает все немного запутанным. –  LU RD 22.10.2013 в 10:52
  • :-( Это плохо .... Вы можете использовать System.Low для arrya и т. д., но этот подход потребует много работы и оставляет вам плохо читаемый код ... –  Arioch 'The 22.10.2013 в 11:03
  • @ Arioch'The: Предлагаю вам обновить ответ с помощью этих пояснений или удалить их как вводящие в заблуждение. –  Kromster 23.10.2013 в 07:26
  • @KromStern Именно поэтому я попросил вас обновить свой вопрос. На данный момент этот ответ необходим, так же как и знак «DEAD END», на этот раз тратить свое время на эту идею. Вы можете видеть, что многие очень опытные люди вскочили на фургон и заверили меня: «О, да, это работает» после того, как я сделал первоначальное предложение. Таким образом, эта идея является убедительной и очевидной, и никто не ожидал неудачи. Вот почему это должно быть записано для всех, чтобы спасти свое время и не вернуть эту неудачную идею еще раз. Но я согласен, что неудачный ответ - это не ответ, так что - снова - вот почему я попросил обновить Q. –  Arioch 'The 23.10.2013 в 08:46
Показать остальные комментарии