Как передать динамический массив как нетипизированный 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
если массив пуст.
Как только вы отказываетесь от безопасности типов и используете нетипизированные параметры, разумно ожидать немного большего трения при вызове такой функции.