Как установить константу string (или AnsiString) в TVarRec?

#string #delphi #constant-expression #open-array-parameters

#строка #delphi #constant-выражение #open-array-параметры

Вопрос:

Я хочу передать аргументы форматирования Args в функцию Format. Я нашел несколько примеров этого, но я не могу выяснить, как назначить строковую константу в члене TVarRec. Следующий код завершается ошибкой при компиляции с E2089 Invalid typecast .

 procedure TForm1.Button1Click(Sender: TObject);
var Arguments: array of TVarRec;
begin
  SetLength(Arguments, 2);

  Arguments[0].VInteger := 111;
  Arguments[1].VAnsiString :=  PAnsiString('Text'); // I want to set this member

  ShowMessage(Format('Decimal: %d, String: %s', Arguments));
end;
  

Кто-нибудь может подсказать мне, как установить константу string (или AnsiString) для члена TVarRec? Я использую Delphi 2009.

Большое спасибо

Комментарии:

1. Актуальная статья для людей, которые никогда раньше не видели этот сложный аспект Delphi: rvelthuis.de/articles/articles-openarr.html

2. Я написал ответ и удалил его, и кто-то другой сделал то же самое. Это было СЛОЖНО. Хороший ТОндрей.

3. Просто для справки: вам не обязательно делать все это. Вы можете вызвать Format напрямую (и гораздо проще): ShowMessage(Format('Decimal: %d, String: %s', [111, 'Text'])); . Это работает везде, где TVarArgs требуется (обычно для array of const ) — вы можете создать VarArgs массив, просто передав его как [StringItem, IntegerItem, BooleanItem] и так далее.

4. Вы правы, однако я рассматривал возможность передачи Args как array of string с переменной длиной в качестве аргументов Format функции.

5. Вы правы, но трудно сказать, заметил ли пользователь комментарий до принятого ответа; однако все же было бы неплохо перефразировать его обычным способом, используя собственную функцию вместо Format

Ответ №1:

Кажется, это работает в XE:

 var
  Args: array[0..1] of TVarRec;
  S: AnsiString;
  U: UnicodeString;
begin
  S := 'Hello';
  U := 'world';
  Args[0].VType := vtAnsiString;
  Args[0].VAnsiString := Pointer(S);
  Args[1].VType := vtUnicodeString;
  Args[1].VUnicodeString := Pointer(U);

  Writeln(Format('%s, %s!', Args));
end;
  

Комментарии:

1. 1 и принять. Это работает также в Delphi 2009. Большое спасибо 🙂

2. Приведение типов Pointer(S) также должно работать (таким образом, позволяя использовать пустые строки). Похоже, проблема связана просто со строковыми литералами.

3. Я изменил код, чтобы показывать аргументы как AnsiString, так и UnicodeString.

4. Вау. Это было сложнее, чем я думал, что это будет! В другом предыдущем удаленном ответе использовалась SetLength для массива TVarRec, и, похоже, во время выполнения происходит сбой, хотя он нормально компилируется. Я был удивлен этим.

5. Это то, что я пробовал в первый раз, но из-за того, что я пропустил добавление VType , я получал нарушение доступа. Спасибо всем!

Ответ №2:

Просто мои два цента. Ответ ТОндрея правильный, но я просто хотел подчеркнуть некоторые моменты. И комментарии — не самое подходящее место для этого.

Пожалуйста, имейте в виду, что на AnsiString должно быть указано все время, TVarRec пока будет использоваться.

Например, если вы пишете функцию, задающую массив TVarRec , вы должны убедиться, что вы сделали временную копию AnsiString созданного в функции значения в переменную, которая будет оставаться до тех пор, пока не будет использоваться TVarRec массив. В противном случае может возникнуть нарушение произвольного доступа (не каждый раз, а только когда MM переназначает ansistring память).

Например, следующий код неверен:

 type
  TVarRec2 = array[0..1] of TVarRec;

procedure SetVarRec(a,b: integer; var Result: TVarRec2);
begin
  Result[0].VType := vtAnsiString;
  Result[0].VString := pointer(AnsiString(IntToStr(a)));
  Result[1].VType := vtUnicodeString;
  Result[1].VString := pointer(UnicodeString(IntToStr(b)));
end;
  

Потому что AnsiString и UnicodeString временные переменные будут освобождены по завершении процедуры и Results[].VString по-прежнему будут указывать на освобожденную память…

Использование класса или записи с некоторой временной частной строкой может помочь:

 type
  TMyVar2 = record
  private
    tmpA: AnsiString;
    tmpB: UnicodeString; 
  public
    VarRec: TVarRec2;
    procedure SetVarRec(a,b: integer);
  end;

procedure TMyVar2.SetVarRec(a,b: integer);
begin
  VarRec[0].VType := vtAnsiString;
  tmpA := AnsiString(IntToStr(a));
  VarRec[0].VString := pointer(tmpA);
  VarRec[1].VType := vtUnicodeString;
  tmpB := UnicodeString(IntToStr(b));
  VarRec[1].VString := pointer(tmpB);
end;
  

Конечно, в вашей программе у вас может быть уже существующий класс. В этом случае вам лучше использовать метод и некоторые частные временные строки. Для того, чтобы метод был многопоточным (т. Е. повторно вводимым), вы должны предоставить временные строки в качестве параметров…

Я использую этот трюк, чтобы иметь действительный динамический массив, TVarData содержащий некоторое AnsiString содержимое в классе. Фактически, TVarData и TVarRec оба используют такой указатель без ссылок на строки, что может сбивать с толку.

Имейте в виду, что проблемы, связанные с pointer(S) операторами like, могут быть трудно отследить.

Ответ №3:

вы должны использовать массив значений ansistring, потому что вы должны использовать point

 temps: array of AnsiString;
value: array of TVarRec;
...
max_count := 10;
setlength(temps,max_count);
setlength(value,max_count);
for i := 0 to max_count-1 do begin
  temps[i] := IntToStr(i);
  value[i].VType := vtAnsiString;
  value[i].VString := Pointer(temps[i]); 
end;
AdoTable.insertrecord(value);