Delphi - доступ к данным из динамического массива, который заполняется из нетипизированного указателя - PullRequest
3 голосов
/ 10 марта 2009

Я использую Delphi 2009 не то, чтобы это сильно повлияло на то, что я делаю. Я думаю, что столкнулся бы с тем же самым, если бы я все еще был на 2007 .

У меня есть вызов scsi, который выводит данные на указатель (неправильный взгляд на это, но мне сложно объяснить это).

Первоначально я использовал Move для заполнения Статического массива байтов данными, которые вернулись, но я бы хотел переключиться на Dynamic Array длина которого известна на момент вызова. Я пробовал несколько вещей с различными результатами, некоторые из них получают данные, но имеют сумасшедшие нарушения доступа, другие не имеют ошибок, но получают неверные данные.

Добавление setlength к массиву с последующим использованием move приводит к тому, что сначала создается пустой массив заданной длины, а затем, во-вторых, невозможно получить доступ к данным через OutputData [0] , как я делал, когда он был статическим, в отладчике после перемещения все показывается как недоступное значение или как угодно.

Ниже я кое-что попробовал после прочтения статьи, в которой oposit взял динамический массив и дал указатель на этот адрес. В нем упоминались ошибки типа данных сирот.

var
  Output: Pointer;
  OutputData: Array of byte;
  I: Integer;
begin
GetMem(Output, OutputLength.Value);
if SendPSPQuery(Char(DriveLetter[1]), cbxQuery.Items.IndexOf(cbxQuery.Text), Output, OutputLength.Value) = 0 then
  begin
    OutputData := @Output;
    for I := 0 to OutputLength.Value - 1 do
    begin
      edtString.Text := edtString.Text + Char(OutputData[I]);
    end;

Существуют различные другие данные, которые используются для вывода данных, поскольку они выводятся в виде строки, шестнадцатеричного кода и прочего.

В любом случае, как я могу взять указатель, поместить эти данные в динамический массив, а затем получить эти данные так, как вы бы обращались к массиву.

Спасибо.

Ответы [ 4 ]

10 голосов
/ 10 марта 2009

Чтобы использовать динамический массив с процедурой Move , вам нужно передать первый элемент массива. Например:

var
  Source: Pointer;
  SourceSize: Integer;
  Destination: array of Byte;

SetLength(Destination, SourceSize);
Move(Source^, Destination[0], SourceSize);

Обратите внимание, что второй параметр разыменовывает указатель. Это потому, что Move принимает значение , которое вы копируете, а не указатель на значение. Вы копируете материал, на который указывает указатель, поэтому вам нужно передать его на Move.

Кстати, тот же синтаксис работает, если Destination также является статическим массивом. И вы правы, что это не относится к Delphi 2009. Это верно вплоть до Delphi 4, когда появились динамические массивы. И Move имеет такой же странный нетипизированный параметр синтаксис навсегда.


Не выделяйте собственную память с помощью GetMem, а затем приводите тип, чтобы заставить компилятор думать, что у вас есть динамический массив. Это не . Динамические массивы имеют счетчики ссылок и поля длины, которых не будет у обычного байтового буфера, и, поскольку вы не контролируете весь код, который генерирует компилятор для доступа к предполагаемому динамическому массиву, существует опасность, что ваша программа попытается получить доступ несуществующие данные структуры данных.

Вы можете заставить функцию PSP хранить свои данные непосредственно в динамическом массиве. Вот некоторый код, чтобы сделать это:

var
  Output: array of Byte;

SetLength(Output, OutputLength.Value);
if SendPSPQuery(Char(DriveLetter[1]),
                cbxQuery.Items.IndexOf(cbxQuery.Text),
                @Output[0],
                OutputLength.Value) = 0
then

Нет необходимости освобождать память впоследствии; компилятор вставляет код для освобождения динамического массива, когда Output выходит из области видимости, и нет других ссылок на массив. Этот код принимает динамический массив и передает его как обычный буфер. Это работает и безопасно, потому что динамический массив, по сути, является подтипом простого старого буфера. Функция примет указатель на первый элемент массива и обработает указатель как указатель на группу байтов, потому что это именно то, что есть. Функция не должна знать, что рядом с теми байтами, которые программа использует для учета динамических массивов, случается что-то дополнительное.


Если у вас есть данные в буфере и вы хотите обработать этот буфер, как если бы это был массивом, вместо того, чтобы копировать данные в отдельную структуру данных, тогда у вас есть две опции.

  1. Объявите статический массив указатель , а затем приведите тип буфера к этому типу указателя. Это классический метод, и вы можете увидеть, что он используется повсеместно в коде, особенно в коде, предшествующем Delphi 4. Например:

    type
      PByteArray = ^TByteArray;
      TByteArray = array[0..0] of Byte;
    var
      ByteArray: PByteArray;
    
    ByteArray := PByteArray(Output);
    for i := 0 to Pred(OutputLength.Value) do begin
      {$R-}
      edtString.Text := edtString.Text + Chr(ByteArray[i]);
      {$R+}
    end;
    

    Директивы $R предназначены для того, чтобы убедиться, что проверка диапазона отключена для этого кода, поскольку тип массива объявлен с длиной, равной 1. Массив объявлен с таким размером частично, чтобы служить подсказкой того, что вы ' на самом деле не предполагается объявлять переменную этого типа. Используйте его только через указатель. С другой стороны, если вы знаете, какой будет максимальный размер данных, вы можете использовать этот размер для объявления типа массива, а затем оставить проверку диапазона включенной. (Если вы обычно отключаете проверку диапазона, вы просто напрашиваетесь на неприятности.)

  2. Объявите ваш буфер как PByte вместо Pointer, а затем используйте новую поддержку Delphi (начиная с Delphi 2009) для обработки произвольных типов указателей как указателей на массивы . В предыдущих версиях этот синтаксис поддерживали только PChar, PAnsiChar и PWideChar. Например:

    var
      Output: PByte;
    
    for i := 0 to Pred(OutputLength.Value) do begin
      edtString.Text := edtString.Text + Chr(Output[i]);
    end;
    

    Директива компилятора $POINTERMATH не требуется для включения этой функции для PByte, поскольку этот тип объявлен , пока эта директива действует. Если вы хотите выполнять C-подобные операции с указателями с другими типами указателей, поместите {$POINTERMATH ON} перед кодом, использующим новый расширенный синтаксис.


Как последнее замечание, вам не нужно создавать строки по одному символу за раз. Это расточительно двумя способами. Во-первых, вы создаете много строк, каждая из которых на два байта больше, чем предыдущая. Во-вторых, поскольку вы сохраняете строковый результат в элементе управления edit, вы заставляете реализацию этого элемента в ОС также выделять кучу новых строк. Поместите ваши данные в одну строку, а затем добавьте их все сразу к вашему редактору:

var
  OutputString: AnsiString;

SetString(OutputString, PAnsiChar(Buffer), OutputLength.Value);
edtString.Text := edtString.Text + OutputString;
0 голосов
/ 02 ноября 2009

Во всяком случае, причина, по которой выходной чертеж занимает много времени, заключается в том, что вы просматриваете назначение edtString.text. Это должно быть назначено только один раз, а не в цикле. Каждый раз, когда вы переназначаете его, нужно обрабатывать много уровней, от конкатенации строк до рисования ОС на экране. Сначала вы можете создать строку, а затем просто назначить ее в конце в худшем случае.

0 голосов
/ 10 марта 2009

Вы можете использовать PByte. С директивой {$ POINTERMATH ON} вы можете использовать этот указатель как массив байтов.

{$POINTERMATH ON}
var
  Output: Pointer;
  ar: PByte;
begin
  GetMem(Output, 100);
  ar:=Output;
  ShowMessage(IntToStr(ar[0])+IntToStr(ar[1])+'...');
end;
0 голосов
/ 10 марта 2009

Не берите в голову ... лол после 2 с половиной часов работы с этим, я наконец кое-что понял ... Это немного грязно из вещей, которые я пробовал, но это также работает.

    type
  PDynByteArray = ^TDynByteArray;
  TDynByteArray = array of byte;

procedure TfrmMain.btnQueryClick(Sender: TObject);
var
  Output: Pointer;
  OutputData: PDynByteArray;
  WorkingData: Array of byte;
  DriveLetter: ShortString;
  I: Integer;
  HexOutput: String;
begin
edtSTRING.Clear;
memHEX.Clear;
GetMem(Output, OutputLength.Value);
DriveLetter := edtDrive.Text;
if SendPSPQuery(Char(DriveLetter[1]), cbxQuery.Items.IndexOf(cbxQuery.Text), Output, OutputLength.Value) = 0 then
  begin
    //Move(Output^,OutputData,56);
    OutputData := PDynByteArray(@Output);
    for I := 0 to OutputLength.Value - 1 do
    begin
      edtString.Text := edtString.Text + Char(OutputData^[I]);
    end;
    for I := 0 to OutputLength.Value - 1 do
    begin
      HexOutput := HexOutput + InttoHex(OutputData^[I],2) + ' ';
    end;
    memHex.Lines.Append(HexOutput);
    FreeMem(Output);
    memHex.SelStart := 0;
  end
else edtSTRING.Text := 'SCSI Command Failed';
end;
...