Delphi - 从由非类型化指针填充的动态数组访问数据

发布于 2024-07-14 08:52:32 字数 1087 浏览 11 评论 0原文

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

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

最初我使用移动用返回的数据填充静态字节数组,但我想切换到动态数组其长度在调用时已知。 我尝试了几种结果各异的方法,有些得到了数据,但出现了严重的访问冲突,有些则没有错误,但得到了无效数据。

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;

输出数据还有各种其他用途,因为它以字符串和十六进制等形式输出。

无论如何,我如何使用指针将该数据放入动态数组中,然后以寻址数组的方式获取该数据。

谢谢。

I'm using Delphi 2009 not that it has a large affect on what I'm doing. I think I would run into the same if I was still on 2007.

I have a scsi call that outputs data to a pointer (wrong way of looking at it but i have trouble explaining that).

Originally I used Move to populate a Static Array of Byte with the data that came back, but I'd like to switch to a Dynamic Array to which the length of is known at the time of the call. I've tried several things with varied results some get the data but have mad access violations others have no errors but get invalid data.

Adding setlength to the array and then using move, causes first to have an empty array of set length and then second not be able to access the data via like OutputData[0] like I did when it was static, in the debugger after the move everything shows as innaccesable value or whatever.

Below is something I tried after reading an article that did the oposit took a dynamic array and gave a pointer that address. It mentioned making mistakes like orphaning data.

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;

There is various otherstuff that th eoutput data is used for it gets put out in string and hex and things.

Anyway, how can I Take a Pointer put that data into a dynamic array and then grab that data the way you would address an array.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

生寂 2024-07-21 08:52:33

通过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 之前的代码。例如:

    <前><代码>类型
    PByteArray = ^TByteArray;
    TByteArray = 字节数组[0..0];
    变量
    字节数组:PByteArray;

    ByteArray := PByteArray(输出);
    for i := 0 到 Pred(OutputLength.Value) 开始
    {$R-}
    edtString.Text := edtString.Text + Chr(ByteArray[i]);
    {$R+}
    结尾;

    $R 指令用于确保关闭该代码的范围检查,因为数组类型声明的长度为 1。该数组声明的大小部分是为了服务作为您实际上不应该声明该类型的变量的线索。 仅通过指针使用它。 另一方面,如果您知道数据的合适最大大小是多少,则可以使用该大小来声明数组类型,然后可以保持范围检查处于打开状态。 (如果您通常禁用范围检查,那么您只是自找麻烦。)

  2. 将缓冲区声明为 PByte 而不是 Pointer,然后使用 Delphi 的新功能(如Delphi 2009)支持将任意指针类型视为数组指针。 在之前的版本中,只有 PCharPAnsiCharPWideChar 支持此语法。 例如:

    <前><代码>变量
    输出:PByte;

    for i := 0 到 Pred(OutputLength.Value) 开始
    edtString.Text := edtString.Text + Chr(Output[i]);
    结尾;

    $POINTERMATH 编译器指令不需要为 PByte 启用此功能,因为该类型是在该指令生效时声明的。 如果您想对其他指针类型执行类似C的指针操作,请将{$POINTERMATH ON}放在使用新扩展语法的代码之前。


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

var
  OutputString: AnsiString;

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

To use a dynamic array with the Move procedure, you need to pass the first element of the array. For example:

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

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

Notice also that the second parameter dereferences the pointer. That's because Move takes the value that you're copying, not a pointer to the value. You're copying the stuff that your pointer points to, so that's what you need to pass to Move.

Incidentally, that same syntax works if Destination is a static array, too. And you're right that this is not specific to Delphi 2009. It's true all the way back to Delphi 4, which is when dynamic arrays were introduced. And Move has had the same strange untyped parameter syntax forever.


Do not allocate your own memory with GetMem and then type-cast to make the compiler think that what you have is a dynamic array. It's not. Dynamic arrays have reference counts and length fields that an ordinary byte buffer won't have, and since you're not in control of all the code the compiler generates to access the supposed dynamic array, there's a danger that your program will try to access the data structure's nonexistent data.

You could make the PSP function store its data directly into a dynamic array. Here's some code to do it:

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

No need to free the memory afterward; the compiler inserts code to deallocate the dynamic array when Output goes out of scope and there are no other references to the array. This code takes a dynamic array and passes it as though it were an ordinary buffer. This works and is safe because a dynamic array is, in effect, a subtype of a plain old buffer. The function will accept a pointer to the first element of the array and treat the pointer as a pointer to a bunch of bytes because that's exactly what it is. The function doesn't need to know that there happens to be additional stuff adjacent to those bytes that the program uses for dynamic-array bookkeeping.


If you have your data in a buffer and you want to treat that buffer as though it were the array, instead of copying the data into a separate data structure, then you have two options.

  1. Declare a static-array pointer, and then type-cast your buffer pointer to that type. This is the classic technique, and you can see it used in code all over the place, especially code that predates Delphi 4. For example:

    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;
    

    The $R directives are to make sure range checking is turned off for that code since the array type is declared to have a length of 1. The array is declared with that size in part to serve as a clue that you're not really supposed to declare a variable of that type. Only use it through a pointer. On the other hand, if you know what a suitable maximum size of the data will be, you can use that size to declare the array type instead, and then you can keep range checking turned on. (If you normally keep range checking disabled, you're just asking for trouble.)

  2. Declare your buffer as PByte instead of Pointer and then use Delphi's new (as of Delphi 2009) support for treating arbitrary pointer types as array pointers. In previous versions, only PChar, PAnsiChar, and PWideChar supported this syntax. For example:

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

    The $POINTERMATH compiler directive is not required to enable this feature for PByte because that type is declared while that directive is in effect. If you want to do C-like pointer operations with other pointer types, then place {$POINTERMATH ON} before the code that makes use of the new extended syntax.


As a final note, you don't need to build up your strings one character at a time. It's wasteful in two ways. First, you're constructing lots of strings, each one just two bytes larger than the previous one. Second since you're storing the string result in an edit control, you're forcing the OS implementation of that control to allocate a bunch of new strings, too. Put your data into one string, and then append it all at once to your edit control:

var
  OutputString: AnsiString;

SetString(OutputString, PAnsiChar(Buffer), OutputLength.Value);
edtString.Text := edtString.Text + OutputString;
萌无敌 2024-07-21 08:52:33

没关系……哈哈,经过两个半小时的搞乱,我终于想出了一些办法……我尝试过的事情有点混乱,但它也能工作。

    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;

Nevermind... lol after 2 and a half hours of messing with this i finally figured something out.... Its a bit messy from things ive tried but its working as well.

    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;
2024-07-21 08:52:33

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

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

You can use PByte. With {$POINTERMATH ON} directive you can use this pointer as array of byte.

{$POINTERMATH ON}
var
  Output: Pointer;
  ar: PByte;
begin
  GetMem(Output, 100);
  ar:=Output;
  ShowMessage(IntToStr(ar[0])+IntToStr(ar[1])+'...');
end;
冬天的雪花 2024-07-21 08:52:33

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

At any rate, the reason the output drawing takes time is because you are looking on the edtString.text assignment. This should only be assigned once, not in a loop. Every time you reassign it, many levels of stuff have to be processed, from the string concatenation all the way to the OS drawing on the screen. You can build up a string first and then just assign it at the end in the worst case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文