3

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

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

最初我使用Move用返回的数据填充静态字节数组,但我想切换到在调用时已知长度的动态数组。我已经尝试了几件事,结果各不相同,有些得到了数据,但有疯狂的访问冲突,有些没有错误,但得到了无效的数据。

setlength添加到数组,然后使用move,首先导致设置长度为空数组,然后第二次无法通过像OutputData[0]那样访问数据,就像我在静态时所做的那样,在移动后的调试器中一切都显示为无价之宝或其他任何东西。

下面是我在阅读一篇文章后尝试的,该文章确实反对采用动态数组并给出了该地址的指针。它提到了像孤立数据这样的错误。

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

4 回答 4

10

要在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. 将缓冲区声明为PBytePointer然后使用 Delphi 的新(自 Delphi 2009 起)支持将任意指针类型视为数组指针。在以前的版本中,只有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}使用新扩展语法的代码之前。


最后一点,您不需要一次构建一个字符的字符串。浪费在两个方面。首先,您正在构建大量字符串,每个字符串仅比前一个字符串大两个字节。其次,由于您将字符串结果存储在编辑控件中,因此您也强制该控件的操作系统实现分配一堆新字符串。将您的数据放入一个字符串中,然后一次将其全部附加到您的编辑控件中:

var
  OutputString: AnsiString;

SetString(OutputString, PAnsiChar(Buffer), OutputLength.Value);
edtString.Text := edtString.Text + OutputString;
于 2009-03-10T07:24:40.370 回答
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;
于 2009-03-10T05:40:44.173 回答
0

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

{$POINTERMATH ON}
var
  Output: Pointer;
  ar: PByte;
begin
  GetMem(Output, 100);
  ar:=Output;
  ShowMessage(IntToStr(ar[0])+IntToStr(ar[1])+'...');
end;
于 2009-03-10T06:35:15.900 回答
0

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

于 2009-11-02T08:08:17.070 回答