Как передать динамический массив как нетипизированный const?

Я вызываю функцию, которая принимает:

  • нетипизированный конст
  • длина байта

как это обычный шаблон Delphi. Например:

procedure DoStuff(const Data; DataLen: Integer);

В этом примере теста все DoStuff удостоверится, что получает четыре байта:

0x21 0x43  0x65  0x87

В целях тестирования мы будем интерпретировать эту последовательность байтов как 32-разрядное целое число без знака на нашей машине Intel с прямым порядком байтов: 0x87654321, Это делает полную функцию тестирования:

procedure DoStuff(const Data; DataLen: Integer);
var
    pba: PByteArray;
    plw: PLongWord;
begin
    //Interpret data as Longword (unsigned 32-bit)
    plw := PLongWord(@Data);
    if plw^ <> $87654321 then
        raise Exception.Create('Fail');

    //Interpret data as array of bytes
    pba := PByteArray(@Data);
    if (pba[0] <> $21) or (pba[1] <> $43) or (pba[2] <> $65) or (pba[3] <> $87) then
        raise Exception.Create('Fail');

//  ShowMessage('Success');
end;

Проверка ошибок была объяснена для пояснительных целей.

тестирование

Я могу начать тестирование, чтобы правильно передать байты DoStuff функция.

//Pass four bytes in a LongWord
var lw: LongWord;
lw := $87654321;
DoStuff(lw, 4); //works

И так способ пройти LongWord для функции, которая принимает нетипизированный const, это просто передать переменную. То есть:

  • Неправильно@lw
  • НеправильноAddr(lw)
  • ПлохоPointer(@lw)^
  • Исправить: lw

Я также могу передать 8-байтовый тип; так как я читаю только первые четыре байта:

//Pass four bytes in QuadWord
var qw: Int64;
qw := $7FFFFFFF87654321;
DoStuff(qw, 4); //works

Массивы

Теперь мы получим что-то более хитрое

//Pass four bytes in an array
var data: array[0..3] of Byte;
data[0] := $21;
data[1] := $43;
data[2] := $65;
data[3] := $87;
DoStuff(data[0], 4); //Works

//Pass four bytes in a dynamic array
var moreData: TBytes;
SetLength(moreData, 4);
moreData[0] := $21;
moreData[1] := $43;
moreData[2] := $65;
moreData[3] := $87;
DoStuff(moreData[0], 4); //works

Они оба работают правильно. Но прежде чем некоторые из вас возмутятся, давайте рассмотрим более сложные случаи:

//Pass four bytes at some point in an array
var data: array[0..5] of Byte;
data[2] := $21;
data[3] := $43;
data[4] := $65;
data[5] := $87;
DoStuff(data[2], 4); //Works

//Pass four bytes at some point in a dynamic array
var moreData: TBytes;
SetLength(moreData, 6);
moreData[2] := $21;
moreData[3] := $43;
moreData[4] := $65;
moreData[5] := $87;
DoStuff(moreData[2], 4); //works

Поскольку untyped const оператор неявно передает по ссылке, мы передаем ссылку, начиная с 3-го байта в массиве (и у нас нет расточительной временной копии массива).

Из этого я могу вывести правило, что если я хочу передать массив нетипизированному констату, вы передаете индексированный массив:

DoStuff(data[n], ...);

Две проблемы

Первая проблема заключается в том, как передать пустой динамический массив нетипизированной константной функции. Например:

var
   data: TBytes;
begin
   data := GetData;
   DoStuff(data[0], Length(0));

Этот общий код не работает, если data пусто (из-за ошибки проверки диапазона).

Другая проблема - это критика, которую некоторые имеют с синтаксисом передачи data[0] а не просто используя data:

//Pass four bytes in an array without using the array index notation
data[0] := $21;
data[1] := $43;
data[2] := $65;
data[3] := $87;
DoStuff(data, 4); //works

Это работает. Это означает, что я теряю способность, которую я имел прежде: прохождение индексированного массива. Но реальная проблема заключается в том, что он не работает при использовании с динамическим массивом:

//Pass four bytes in a dynamic array without using the array index notation
SetLength(moreData, 4);
moreData[0] := $21;
moreData[1] := $43;
moreData[2] := $65;
moreData[3] := $87;
DoStuff(moreData, 4); //FAILS

Проблема в том, что есть внутренняя деталь реализации того, как реализованы динамические массивы. Динамический массив на самом деле является указателем, тогда как массив на самом деле является массивом.

Означает ли это, что если я передаю массив, мне нужно выяснить, какой это тип, и использовать другой синтаксис обходного решения?

//real array
DoStuff(data, 4); //works for real array
DoStuff(data, 4); //fails for dynamic array
DoStuff(Pointer(data)^, 4); //works for dynamic array

Это действительно то, что я должен делать? Разве нет более правильного пути?

Бонусное решение

Потому что я не хотел бы потерять способность индексировать массив:

DoStuff(data[67], 4);

я мог бы сохранить запись индексации:

DoStuff(data[0], 4);

и просто обязательно обработайте крайний случай:

if Length(data) > 0 then
   DoStuff(data[0], 4)
else
   DoStuff(data, 0); //in this case data is dummy variable

Весь смысл всего этого состоит в том, чтобы не делать копии данных в памяти; но передать его по ссылке.

1 ответ

Решение

Это довольно просто на самом деле.

DoStuff(data, ...);           // for a fixed length array
DoStuff(Pointer(data)^, ...); // for a dynamic array

это правильный способ сделать это. Динамический массив - это указатель на первый элемент, или nil если массив пуст.

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

Другие вопросы по тегам