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

16,984

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;
Share:
16,984
Admin
Author by

Admin

Updated on June 28, 2022

Comments

  • Admin
    Admin almost 2 years

    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.