3
votes

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.

4

4 Answers

10
votes

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;
0
votes

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;
0
votes

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;
0
votes

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.