简体   繁体   English

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

[英]Delphi - Accessing data from dynamic array that is populated from an untyped Pointer

I'm using Delphi 2009 not that it has a large affect on what I'm doing.我正在使用Delphi 2009并不是说它对我正在做的事情有很大影响。 I think I would run into the same if I was still on 2007 .如果我还在2007 年,我想我会遇到同样的情况。

I have a scsi call that outputs data to a pointer (wrong way of looking at it but i have trouble explaining that).我有一个将数据输出到指针的 scsi 调用(错误的查看方式,但我无法解释)。

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.最初我使用Move用返回的数据填充Static 字节数组,但我想切换到在调用时已知长度的动态数组 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.setlength添加到数组,然后使用move ,首先导致设置长度的空数组,然后第二个无法像我在 static 时那样通过OutputData[0]访问数据,在移动后的调试器中一切都显示为无价之宝或其他任何东西。

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.谢谢。

To use a dynamic array with the Move procedure , you need to pass the first element of the array.要将动态数组与Move过程一起使用,您需要传递数组的第一个元素 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.那是因为Move采用您正在复制的,而不是指向该值的指针。 You're copying the stuff that your pointer points to, so that's what you need to pass to Move .您正在复制指针指向的内容,因此这就是您需要传递给Move的内容。

Incidentally, that same syntax works if Destination is a static array, too.顺便说一句,如果Destination也是 static 数组,则相同的语法也有效。 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.你说得对,这不是 Delphi 2009 所特有的。一直到 Delphi 4 时都是如此,这是引入动态 arrays 的时候。 And Move has had the same strange untyped parameter syntax forever.并且Move永远具有相同的奇怪的无类型参数语法。


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.不要使用 GetMem 分配您自己的GetMem然后进行类型转换以使编译器认为您拥有的是动态数组。 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.动态 arrays 具有普通字节缓冲区所没有的引用计数和长度字段,并且由于您无法控制编译器为访问假定的动态数组而生成的所有代码,因此您的程序存在尝试访问的危险数据结构中不存在的数据。

You could make the PSP function store its data directly into a dynamic array.您可以让 PSP function 将其数据直接存储到动态数组中。 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;之后无需释放 memory; the compiler inserts code to deallocate the dynamic array when Output goes out of scope and there are no other references to the array.Output超出 scope 并且没有其他对数组的引用时,编译器会插入代码以解除分配动态数组。 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. function 将接受指向数组第一个元素的指针,并将指针视为指向一堆字节的指针,因为这正是它的本质。 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. function 不需要知道与程序用于动态数组簿记的那些字节相邻的其他内容。


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:这是经典的技术,您可以在各处的代码中看到它,尤其是早于 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;

    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. $R指令是为了确保该代码的范围检查被关闭,因为数组类型被声明为长度为 1。该数组被声明为具有该大小的部分原因是作为一个线索,你不是真的应该声明该类型的变量。 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 .将缓冲区声明为PByte而不是Pointer ,然后使用 Delphi 的新功能(从 Delphi 2009 起)支持将任意指针类型视为数组指针 In previous versions, only PChar , PAnsiChar , and PWideChar supported this syntax.在以前的版本中,只有PCharPAnsiCharPWideChar支持这种语法。 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. $POINTERMATH编译器指令不需要为PByte启用此功能,因为该类型是在该指令有效时声明的。 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.如果您想对其他指针类型执行类似 C 的指针操作,请将{$POINTERMATH ON}放在使用新扩展语法的代码之前。


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:将您的数据放入一个字符串中,然后将 append 一次全部放入您的编辑控件:

var
  OutputString: AnsiString;

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

At any rate, the reason the output drawing takes time is because you are looking on the edtString.text assignment.无论如何,output 绘图需要时间的原因是因为您正在查看 edtString.text 分配。 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.您可以先建立一个字符串,然后在最坏的情况下在最后分配它。

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.没关系...哈哈,在搞砸了 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;

You can use PByte.您可以使用 PByte。 With {$POINTERMATH ON} directive you can use this pointer as array of byte.使用 {$POINTERMATH ON} 指令,您可以将此指针用作字节数组。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM