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.
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.)
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;