Delphi - 从无类型指针填充的动态数组中访问数据

时间:2009-03-10 04:45:41

标签: delphi arrays dynamic pointers pascal

我正在使用 Delphi 2009 而不是它对我正在做的事情有很大的影响。如果我还在 2007 ,我想我会遇到同样的问题。

我有一个scsi调用,它将数据输出到一个指针(错误的查看方式,但我无法解释它)。

最初我使用移动来填充字节静态数组和回来的数据,但我想切换到动态数组< / strong>在通话时知道其长度。我尝试了几个不同结果的东西,有些得到了数据但有疯狂的访问权限,其他人没有错误,但得到的数据无效。

setlength 添加到数组然后使用 move ,首先导致设置长度为空数组,然后第二次无法通过< strong> 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 个答案:

答案 0 :(得分:10)

要使用带the Move procedure的动态数组,您需要传递数组的第一个元素。例如:

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永远具有相同的奇怪untyped parameter语法。


不要使用GetMem分配自己的内存,然后使用type-cast使编译器认为你拥有的是动态数组。 不是。动态数组具有普通字节缓冲区不具有的引用计数和长度字段,并且由于您无法控制编译器为访问假定的动态数组而生成的所有代码,因此您的程序可能会尝试访问数据结构不存在的数据。

您可以使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超出范围并且没有对该数组的其他引用时,编译器会插入代码以释放动态数组。此代码采用动态数组并将其传递为普通缓冲区。这是有效的,因为动态数组实际上是普通旧缓冲区的子类型。该函数将接受指向数组第一个元素的指针,并将指针视为指向一堆字节的指针,因为它正是它的本质。该函数不需要知道在程序用于动态数组簿记的那些字节附近会发生附加的东西。


如果您将数据放在缓冲区中并且想要将该缓冲区视为是数组,而不是将数据复制到单独的数据结构中,那么您有两个选项。 / p>

  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起)support for treating arbitrary pointer types as array pointers。在以前的版本中,只有PCharPAnsiCharPWideChar支持此语法。例如:

    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}放在使用新扩展语法的代码之前。


  3. 作为最后一点,您不需要一次为一个字符构建字符串。这在两个方面是浪费的。首先,你构建了许多字符串,每个字符串比前一个字符串大两个字节。第二,因为你将字符串结果存储在编辑控件中,所以你也强迫该控件的操作系统实现分配一堆新字符串。将您的数据放入一个字符串,然后将其全部附加到您的编辑控件:

    var
      OutputString: AnsiString;
    
    SetString(OutputString, PAnsiChar(Buffer), OutputLength.Value);
    edtString.Text := edtString.Text + OutputString;
    

答案 1 :(得分:0)

没关系......大声笑了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;

答案 2 :(得分:0)

您可以使用PByte。使用{$ POINTERMATH ON}指令,您可以将此指针用作byte的数组。

{$POINTERMATH ON}
var
  Output: Pointer;
  ar: PByte;
begin
  GetMem(Output, 100);
  ar:=Output;
  ShowMessage(IntToStr(ar[0])+IntToStr(ar[1])+'...');
end;

答案 3 :(得分:0)

无论如何,输出绘图需要时间的原因是因为您正在查看edtString.text分配。这应该只分配一次,而不是循环。每次重新分配时,都必须处理许多级别的内容,从字符串连接一直到屏幕上的OS绘图。您可以先构建一个字符串,然后在最坏的情况下最后分配它。